Классический приём это два указателя, которые идут навстречу друг другу. Один смотрит на начало, второй на конец, мы меняем элементы местами и сдвигаем оба к середине. Как только указатели встретились или пересеклись, срез развёрнут.
Дополнительная память тут не нужна, мы переиспользуем уже выделенный массив, а в Go обмен значений делается одной строкой через множественное присваивание, без временной переменной.
func reverse[T any](s []T) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}🐸 Библиотека Go для собеса