In Go, by default, all function arguments are passed by value: the value of the variable is copied. However, some types (for example, slices, maps, channels) are "wrappers" around internal structures (pointers). Passing a slice by value only copies the slice descriptor, not the data; both variables refer to the same array. In the case of structs — the entire structure is copied.
If you want to avoid copying and work with the original structure, you use pointer passing (*Struct).
type User struct { Name string Age int } func updateUser(u User) { u.Age = 30 // will only change the copy } func updateUserPtr(u *User) { u.Age = 30 // will change the original } func main() { u := User{"Ivan", 25} updateUser(u) fmt.Println(u.Age) // 25 updateUserPtr(&u) fmt.Println(u.Age) // 30 }
Are changes to a slice passed into a function always visible outside the function?
No!
slice[i] = ...), it is visible outside.slice = append(slice, ...)), and the result is not returned from the function — the new elements will be in a local copy, and you will lose them.func addElem(s []int) { s = append(s, 100) } func main() { arr := []int{1,2,3} addElem(arr) fmt.Println(arr) // [1 2 3] — 100 was not added }
Story
In one project, data structures with large struct fields (200+ bytes) were passed by value through channels between goroutines, causing huge overheads in copying and performance loss. After switching to pointer passing, latency decreased significantly.
Story
In an audit logging service, a developer passed a map between functions without explicit cloning. Changes made by one function unexpectedly altered data in another part of the program, causing confusion in the log.
Story
In a function for dynamically increasing a slice, the new slice was forgotten to be returned. As a result, changes were not reflected in the calling code, leading to the loss of some transactions. It was decided to return the new slice from the function.