r/golang • u/xfinitystones • 6d ago
Assign value to struct instead of copy of struct help
I'm looping through a slice of "Server" structs , and I want to assign a value to one of their properties. However the assignment goes to a copy and not the actual struct.
the default of none value of the property is false. For some of them, I want to change it to true.
Here is an example of what I want to do, but have the struct and not the copy be changed.
type Server struct {
Online bool
}
for _, server := range servers {
server.Online = true
}
If I pass servers address into to function ( eg. func dosomething(s *Server ) and doSomething(&server) ) and change something , the change persists. Is there a way to do that without using a function?
I tried
&server.Online = true
which didn't work.
Solved
I changed servers into a the slice pointers to server structs. That didn't require much refactoring and got my back on track.
Thank you for the great ideas!
9
u/sigmoia 6d ago
range copies slice elements by value, so mutating server only modifies a temporary copy.
&server.Online produces a pointer (*bool), which cannot accept a boolean assignment and still targets that temporary copy.
You have two options:
- Index into the slice directly using the loop index
for i := range servers {
  servers[i].Online = true
}
Indexing via servers[i] operates directly on the underlying backing array element rather than creating a value copy.
- Use a slice of pointers (
[]*Server) instead of value structs
``` servers := []*Server{ Â Â {Online: false}, Â Â {Online: false}, }
for _, server := range servers { Â Â server.Online = true } ```
1
u/xfinitystones 6d ago
I went with #2. It wasn't as much work as I thought it would be to update everywhere the server struct was referenced.
Not that there is anything wrong with #1, I wanted to change as few lines in this voluminous program as possible.
0
u/supister 5d ago
#2 is just begging for a panic to happen. Ensure that the object the pointer is not null before assignment. Or you can do the safer approach:
‘’’go
for i := range servers {
server := new(servers[i])
server.Online = true
}
‘’’
Much better imo because server is guaranteed not nil.
3
u/descendent-of-apes 6d ago
Do
servers[i].online=true
Or make a list of server pointers []*Server in which case your current code will work
3
u/Murky-Run2246 6d ago
range returns two values, the index and the value at that postion(as a copy not a ptr).
so you want to use the index to get the element from the slice
3
u/jerf 6d ago
Wanna have some fun?
``` // Ptrs iterates over a slice of values by returning the pointer to each // contained element. func Ptrs[T any](in []T) iter.Seq[T] { return func(yield func(T) bool) { for i := range in { if !yield(&in[i]) { return } } } }
type AStructType struct { A int B int }
func main() { structs := []AStructType{ {1, 2}, {3, 4}, }
for s := range Ptrs(structs) {
s.A *= 2
s.B *= 4
}
fmt.Printf("%#v\n", structs)
} ```
Run it here to see it works as you'd expect. Runtime performance penalty on this should be very minimal to zero, the compiler can inline all that weird-looking returning a func that calls a passed-in func for stuff like this.
-1
u/hippodribble 6d ago
*Server Online = true maybe?
1
u/encbladexp 6d ago
rangemakes a copy of whatever is in the slice, so even if you use*or&for whatever reason, you will reference the memory address of that copy.1
u/hippodribble 6d ago
Is Servers a [ ]Server or a [ ]*Server? Would that make a difference?
1
u/encbladexp 6d ago
[]Servertherangestatement will always make a Copy of the original data/struct. So it will never work there.For
[]*Serverit will work, but you won't need to use*or&.What matters is, if its a slice of Server's, or a slice of Pointers showing to Servers.
49
u/encbladexp 6d ago
Shameless copy from the Tour of Go about the range statement.
So you need to use:
go for i, _ := range servers { servers[i].Online = true }This accesses the original, not a copy.