Как бы вы реализовали функцию, аналогичную встроенной `append` для добавления одного слайса к другому?

Ответ

Если бы мне нужно было реализовать собственную функцию append для слайсов, я бы сосредоточился на эффективности управления памятью и обработке capacity (ёмкости) слайса. Вот пример реализации с использованием дженериков Go.

// AppendSlices добавляет все элементы из 'src' в 'dst'.
// Возвращает новый слайс, который может указывать на новый базовый массив.
func AppendSlices[T any](dst, src []T) []T {
    totalLen := len(dst) + len(src)

    // 1. Проверяем, достаточно ли ёмкости в dst.
    if totalLen <= cap(dst) {
        // Ёмкости достаточно, переиспользуем существующий массив.
        // Расширяем слайс dst до нужной длины.
        dst = dst[:totalLen]
    } else {
        // 2. Ёмкости недостаточно, нужно выделить новый массив.
        // Рассчитываем новую ёмкость с запасом, чтобы избежать частых аллокаций.
        // Стратегия роста может быть разной, здесь для простоты удваиваем требуемую длину.
        newCap := totalLen
        if newCap < 2*len(dst) {
            newCap = 2 * len(dst)
        }
        if newCap < totalLen {
            newCap = totalLen
        }

        newDst := make([]T, totalLen, newCap)
        copy(newDst, dst) // Копируем старые данные
        dst = newDst
    }

    // 3. Копируем элементы из src в свободное место в dst.
    copy(dst[len(dst)-len(src):], src)

    return dst
}

Ключевые моменты реализации:


  1. Проверка ёмкости (capacity): Это самый важный шаг. Если в базовом массиве dst достаточно места, мы избегаем дорогостоящей операции выделения новой памяти и копирования старых элементов.



  2. Стратегия роста: Если нужна новая аллокация, ёмкость нового массива должна быть больше, чем просто необходимая длина. Стандартная библиотека Go использует сложную стратегию (удвоение для маленьких слайсов, затем рост на ~25% для больших), чтобы найти баланс между использованием памяти и количеством аллокаций.



  3. Использование copy: Встроенная функция copy — самый эффективный способ копирования данных между слайсами.



  4. Возврат нового слайса: Функция обязательно должна возвращать слайс, так как в случае аллокации нового массива указатель на него изменится. Вызывающий код должен всегда использовать результат: slice = AppendSlices(slice, anotherSlice).