Как в Go передаются аргументы в функции?

Ответ

В Go все аргументы в функции передаются исключительно по значению (pass by value). Это означает, что функция всегда получает копию передаваемой переменной, а не саму переменную.

Однако поведение зависит от типа данных:

  1. Простые типы (int, string, bool, struct): Создается полная копия значения. Изменения этой копии внутри функции не влияют на исходную переменную.

  2. Ссылочные типы (слайсы, мапы, каналы, указатели): Копируется не сам базовый массив данных или структура, а значение-дескриптор (для слайсов, мап) или адрес в памяти (для указателей). Этот дескриптор или указатель по-прежнему ссылается на ту же самую область памяти, что и оригинал.

    • Слайсы и мапы: Изменение элементов слайса или мапы внутри функции будет видно снаружи, так как копия дескриптора указывает на тот же базовый массив данных.
    • Указатели: Изменение значения, на которое указывает указатель, также будет видно снаружи.

Пример для наглядности:

package main

import "fmt"

// Принимает копию int. Изменения не видны снаружи.
func modifyValue(val int) {
    val = 100
}

// Принимает копию указателя. Изменения по указателю видны снаружи.
func modifyPointer(ptr *int) {
    *ptr = 100
}

// Принимает копию дескриптора слайса. Изменения элементов видны снаружи.
func modifySlice(slice []int) {
    slice[0] = 100
}

func main() {
    // Простой тип
    num := 10
    modifyValue(num)
    fmt.Println("num после modifyValue:", num) // Вывод: 10

    // Указатель
    modifyPointer(&num)
    fmt.Println("num после modifyPointer:", num) // Вывод: 100

    // Слайс
    s := []int{1, 2, 3}
    modifySlice(s)
    fmt.Println("slice после modifySlice:", s) // Вывод: [100 2 3]
}

Вывод: Хотя технически передача всегда идет по значению, для ссылочных типов эффект похож на передачу по ссылке, так как копируется не сама структура данных, а указатель на нее.

Ответ 18+ 🔞

А, ну вот, опять про эту передачу по значению в Go, блядь! Сидят, сука, дядьки с бородами, в свитерах, и такие: "Всё по значению, всё честно!" А потом новичок приходит, меняет в функции слайс, а снаружи всё поменялось — и он нихуя не понимает, ёпта! Сейчас разжуем, как бабушка котлету.

Слушай сюда, главное правило, которое вбить надо, как гвоздь в жопу: в Go всё, абсолютно всё, передаётся в функцию по значению. То есть, всегда делается копия. ВСЕГДА. Но вот тут-то, сука, и начинается магия, а точнее — ёбаный подвох, потому что копируется не всегда то, что ты думаешь.

Разберём по полочкам, как в армии гимнастёрку:

  1. Простые типы (int, string, bool, struct): Тут всё прозрачно, как слёзы ребёнка. Копируется само число, сама строка, сама структура. Поменял внутри функции — и похуй, снаружи всё как было. Оригинал в полной безопасности, как член в портмоне.

  2. А вот ссылочные типы (слайсы, мапы, каналы, указатели): А вот тут, блядь, ёперный театр! Копируется не сам массив данных или эта здоровенная мапа, а дескриптор (для слайса/мапы) или адрес памяти (для указателя). Это как если бы ты дал другу не свою квартиру, а копию ключа от неё. Квартиру-то он не унесёт (значение дескриптора скопировалось), но зайти и нассáть в твой горшок с фикусом — запросто!

    • Слайсы и мапы: Меняешь элементы внутри — меняются и снаружи, потому что копия дескриптора тычет в тот же самый базовый массив, в ту же самую мапу. Это и создаёт иллюзию "передачи по ссылке", хотя технически — нет, блядь, по значению!
    • Указатели: Ну тут вообще просто, как три копейки. Копируется адрес. По этому адресу заходишь и меняешь что хочешь. Оригинал, естественно, меняется. Волшебства ноль, чистая механика.

Ладно, хватит трепаться, вот тебе живой пример, смотри и вникай:

package main

import "fmt"

// Принимает копию числа. Меняй — не хочу, снаружи хоть трава не расти.
func modifyValue(val int) {
    val = 100 // Поменяли копию. И что? Ничего.
}

// Принимает копию АДРЕСА. По этому адресу лезем и крушим всё нахуй.
func modifyPointer(ptr *int) {
    *ptr = 100 // А вот это уже больно. Меняем значение по оригинальному адресу.
}

// Принимает копию ДЕСКРИПТОРА слайса. Дескриптор тычет в старый массив. Меняем элементы в нём.
func modifySlice(slice []int) {
    slice[0] = 100 // Бах! И оригинальный слайс снаружи уже с другим первым элементом.
}

func main() {
    // Простой тип — копия значения, тихо и мирно.
    num := 10
    modifyValue(num)
    fmt.Println("num после modifyValue:", num) // 10. Как был паинькой, так и остался.

    // Указатель — копия адреса, но по нему бьём точно в цель.
    modifyPointer(&num)
    fmt.Println("num после modifyPointer:", num) // 100. Вот тебе и "по значению", ёпта!

    // Слайс — копия дескриптора, но доступ к данным тот же.
    s := []int{1, 2, 3}
    modifySlice(s)
    fmt.Println("slice после modifySlice:", s) // [100 2 3]. Сюрприз, блядь! Данные-то поменялись!
}

Итог, блядь, какой? Да, передача всегда по значению, это свято. Но для "хитрых" типов (слайсы, мапы, указатели) копируется не груда данных, а лишь доступ к ней. Поэтому внутри функции можешь навести шороху в оригинальных данных. Запомни это, а то будешь потом, как Герасим, мучаться: "Му-му, а почему у меня слайс изменился?!"