5 ms·
I've written a tiny bit of Go and am aware of the general problem this solves. I don't get their more subtle examples (the letsencrypt one or "range c.informerM
by scottlamb 3y ago
I've written a tiny bit of Go and am aware of the general problem this solves. I don't get their more subtle examples (the letsencrypt one or "range c.informerMap" vs "range alarms".
When you do "for k, v := range someMap", is "v" of the map's value type (and one binding for the whole loop, copied before each iteration)? This would explain the problem, but I would have expected "v" to be a reference into the map, and I couldn't find the answer in a quick skim of the "For statements with range clause" in the spec. I'm probably looking in the wrong place because I touch Go rarely...
[1] https://go.dev/ref/spec#For_statements https://go.dev/ref/spec#For_statements
edit: oh, the answer is in the "code block"-formatted table. Guess I had banner blindness. "v" is the copied value, not a reference. I'm surprised!
- tedunangst 3y agoIf you have a map of string to int, then v is of type int. It's a value. It's not pointer to int.
- scottlamb 3y agoIs the expectation that you simply won't create a `map[...]expensivetocopyvalue`, but instead always do `map[...]*expensivetocopyvalue`?
- konart 3y agoNo expectations whatsoever. You can use pointer or a values. It's up to you.
- scottlamb 3y agoThat's silly. Language constructs and APIs are always made with expectations for how they're used, stated or not. You can write code that compiles without understanding and matching those expectations but it probably won't be good code. I'm asking because I think if it were expected that folks used large/expensive-to-copy map values, this construct would return a reference instead of copying. In Rust for example, the std library's "normal" [1] iterators return references. [1] not those returned by into_* or drain.
- deleted 3y ago[deleted]
- morelisp 3y agoReturning references to storage within the map would be a substantial footgun without borrowing.
- scottlamb 3y agoThanks, useful reply. I just realized that myself: https://news.ycombinator.com/item?id=37576558 https://news.ycombinator.com/item?id=37576558 The peer comments along the lines of "the expecation is it does what it does" are not so helpful from a perspective of learning to write code that is in harmony with the language philosophy.
- konart 3y agoHere an example: https://go.dev/play/p/He0lBEYZJ03 https://go.dev/play/p/He0lBEYZJ03 Value is always a copy. Either a copy of a struct (in case of map[T]struct{}) or a copy of a pointer (in case of map[T]*struct{})
- tedunangst 3y agoThe expectation is the compiler does what you tell it? If you want pointers in your map, you can do that too.
- Arnavion 3y agoThey're asking that, if the programmer wants the map to store expensivetocopyvalue semantically but also doesn't want to have iteration generate expensive copies, does the programmer have to change the map to store *expensivetocopyvalue instead? Anyway I believe the answer is that expensivetocopyvalue is not a type that exists in golang, because golang's "copy" operation is always a simple bitwise copy ala C struct copy / Rust's Copy trait, not like C++ copy ctor / Rust's Clone trait that can be arbitrarily expensive.
- morelisp 3y agoIn Go, `expensivetocopyvalue` can still be achieved via an enormous (e.g. multi-KB/MB) structure (which is most literally expensive to copy) or something containing a lot of pointers (which is not really expensive to copy but will start to pressure the GC).
- deleted 3y ago[deleted]
- deleted 3y ago[deleted]
- erik_seaberg 3y agoGo doesn’t support pointers to map keys or values. It does support pointers to array slots, but for-range copies each slot rather than giving you a pointer to it.
- scottlamb 3y agoI suppose that makes sense when I think about it for a bit. My recent expectations come from work in Rust. There the language prevents you from mutating a map while holding a reference into it. Go doesn't have a mechanism to prevent that (except the one you said, simply not supporting those references at all). If you had a reference into a map that was resized because of a subsequent mutation, your reference would have to keep the whole previous map alive and point to different memory than a reference acquired since then. Both seem undesirable. With array slots, the same issue is present but is a bit more explicit because those resizes happen with `mySlice = append(mySlice, ...)`.
- erik_seaberg 3y agoI think the slice append semantics are very error-prone, and it would have been better if a slice was a shareable reference to a single mutable thing, like a map (or a list from Python or Java or …)
- konart 3y ago>If you had a reference into a map Maps in golang are of reference type, just to be clear. https://go.dev/blog/maps https://go.dev/blog/maps implementation: https://go.dev/src/runtime/map.go https://go.dev/src/runtime/map.go
- foldr 3y agoIndeed, removing values from a map while iterating over it in Go is safe and guaranteed to have the expected behavior.
- assbuttbuttass 3y agoI managed to track down these code snippets. Feel free to check out these links if you're curious: https://github.com/adobe/kratos/blob/93246f92d53feba73743dbf3384e7044e9a05ea9/scale/scale_target.go#L98 https://github.com/adobe/kratos/blob/93246f92d53feba73743dbf... https://github.com/StalkR/goircbot/blob/6081ed5d1d74f01767d70a1b3d5052d2fab42478/plugins/df/handlers.go#L110 https://github.com/StalkR/goircbot/blob/6081ed5d1d74f01767d7... Basically the compiler is translating go a.Monitor(b) into (&a).Monitor(b) due to automatic dereferencing