6 ms·
I was surprised that you can't apply & to any value. I thought it was gut an ordinary operator and it would ensure that the value it was applied to would be put
by kevincox 6y ago
I was surprised that you can't apply & to any value. I thought it was gut an ordinary operator and it would ensure that the value it was applied to would be put onto the heap.
s := S{}
sp := &s // Works
_ = sp
_ = &S{} // Works
i := int32(1)
ip := &i // Works!
_ = ip
_ = &int32(1) // Doesn't work!
https://play.golang.org/p/fdgvbEwJWgh https://play.golang.org/p/fdgvbEwJWgh
It seems odd that you can't apply & to a function's return value. I think the best approach would be making & work in basically any scenario. For example the following also doesn't currently work.
&(int32(1) + int32(1))
It seems like it should be possible to "desugar" &X to `_tmp = X; &_tmp` and solve this weirdness.
- jerf 6y agoOne of the things I think the Go tutorials don't make a big enough deal of is that Go is relatively explicit about allocations. := isn't just a shortcut for declaring variables, it's an allocation, and an error to use it when it doesn't allocate. var X Sometype isn't just a declaration, it's an allocation. := kinda smears the clarity by not allocating if you have a variable on the left that is already allocated, and there's some other places where it kinda smears things up, but at the core, Go makes you explicitly allocate.
- gowld 6y agoI think the way to say it is that Go requires you to declare every allocation, but allows over-declaration in the case of copying. > := [...] an error to use it when it doesn't allocate. > := [...] not allocating if you have a variable on the left that is already allocated, This appears to be a contradiction. I suppose you mean something like "error to use it when there's no possible context where that line of code would allocate"; what's an example of that?
- catlifeonmars 6y agoI’m not sure I understood this correctly. Does the following allocate (on the heap)? foo := MyStruct{}
- kevincox 6y agoNo. In this case foo will live on the stack (unless you take its address later). foo := MyStruct{} // Could live on the stack _ := &foo // Oh, now foo must live on the heap.
- coder543 6y agoThis isn't even true. No matter what syntax you write inside a function, the Go compiler always has the final say on what is stack allocated and what is heap allocated. Taking the address of foo will not cause foo to be heap allocated unless Go is unable to prove that the pointer will live for less time than the current stack frame. Look up "escape analysis". Basically the only way to guarantee that something will always be heap allocated is to assign it to a global variable. Even returning a pointer to that object from the current function is not a strong guarantee, since the compiler could inline this function into the caller and determine that everything can live happily inside the newly inlined stack frame without heap allocation.
- kevincox 6y agoGood point. It isn't "must", I should have said "may".
- coder543 6y agoNo, that does not cause a heap allocation on its own. If other lines of code in that function caused a pointer to that value to escape the lifetime of the current function's stack frame, the compiler would determine that it has to be heap allocated instead. I believe the person you are replying to was making a confusing point about some hand wavy notion of "any kind of allocation", which includes stack allocations... which are determined at compile time, not with "alloca".
- kevincox 6y agoI don't see how: temp := someFunc() p := &temp is any less explicit than p := &someFunc() It seems that the `&` is still required to put something onto the heap.
- zeeboo 6y agoWhat about `&m[x]` where m is some map? Does that heap allocate and create a copy, or is it a pointer to the actual storage slot? If the former, that's a hidden copy/allocation that didn't exist before, and if it's the latter, resizing the map invalidates the pointer, so it must be updated somehow.
- kevincox 6y ago`&` will "move" something to the heap if it isn't already on the heap. The simpler way to think about it is that in Golang everything is on the heap. However the optimizer will move things to the stack if they don't have their address taken. I think the point about explicitness is that if you don't use `&` then it will be able to be put on the stack. So `&` doesn't cause a heap allocation but lack of `&` (or new()) confirms that there isn't one. (I don't actually know if that is true but I can't think of any counterexamples)
- zeeboo 6y agoI think I didn't communicate my point clearly. Consider this hypothetical program: x := make(map[int]int) x[0] = 5 y := &x[0] *y = 10 print(x[0]) // 5 or 10? x[0] = 6 print(*y) // 6 or 10? // force the map to grow and reallocate the buckets for j := 1; j < 100; j++ { x[j] = j } *y = 11 print(x[0]) // 5, 6, 10, or 11? The crux of the problem is answering what y actually points at: the value in the map bucket, or some freshly allocated value? There are problems with whichever one you pick. edit: changed the second print to *y instead of x[0]. thanks masklinn for catching this error.
- masklinn 6y ago
- coder543 6y ago> it's an allocation Most people probably think "heap allocation" when you say this. Go doesn't do dynamic allocations within a stack frame (alloca in C), so when you say "it's an allocation", what does that mean? It could be a stack allocation that occurred at compile time as a reservation in the size of the stack frame for that function. It could be a heap allocation. Only the compiler knows! The Go compiler is the ultimate authority on what becomes a heap allocation. It tries to make everything into a stack allocation when possible, and stack allocations are "free". Beyond that, a sufficiently smart compiler can reuse stack "allocations" within a single function as certain values become "dead" (never used again). So there isn't even guaranteed to be a 1:1 correspondence between "stack allocations" and variables that you declared inside the function. So, I completely disagree with your statement about Go being "relatively explicit about allocations." It's one of the least explicit compiled languages in that regard. Go makes a distinction between declaration and assignment, which is the syntax you're talking about. It really has nothing to do with allocations.
- jerf 6y agoYou file a good complaint, and I should clarify. What I mean is more like Go is secretly quite explicit about its allocations, if I may. It superficially looks like it doesn't really care with a variety of syntax glosses that can make it look like it's more like a scripting language where it doesn't care, but it actually does care quite a lot even at the syntax level, and if you dig past the syntax glosses, it is actually explicit about what gets allocated. It doesn't successfully hide it from you like a scripting language does. Also, allocations are just... allocations. Go qua Go doesn't have stack vs. heap, and it's a mistake to care except when optimizing. So in Go qua Go, it isn't an issue that it may "reuse" a particular address, because in Go qua Go you can't witness that anyhow. (If you try to keep a pointer around to witness it with, you'll keep the thing pointed to alive.) From Go's perspective, it's still an allocation even if the implementation manages to re-use a particular memory address to do so. I'm talking about the runtime Go implements here, not the implementation. This actually took me some years to correctly internalize, for what it's worth. It does a "good" job of glossing over things. However, if you really poke at it, allocations are still explicit. They just may not look like what you are used to from other languages.
- liendolucas 6y ago>It seems odd that you can't apply & to a function's return value. Offtopic: Surprisingly I was asking myself this question but if possible in C... Is it?
- gowld 6y agoNope. You can only take a reference to an lvalue, which is (essentially) an expression that is legal to use in the form `my_lvalue = .... Otherwise, there's nothing to take the reference of. int* ref1() { return &1; } -> error: lvalue required as unary '&' operand // #include <stdlib.h> int alloc() { return *(int*)malloc(sizeof(int)); } int* ref() { return &alloc(); } -> error: lvalue required as unary '&' operand You can still be unsafe though, by making a reference to a stack-allocated object and letting it go out of scope : int* make_unsafe_ref() { int a; return &a; } -> warning: function returns address of local variable
- masklinn 6y ago> You can only take a reference to an lvalue, which is (essentially) an expression that is legal to use in the form `my_lvalue = …. I mean it could implicitly allocate, that's what Rust does for instance. Your second and third attempts would not compile though, the first would by returning a `&'static T`.
- steveklabnik 6y ago"implicitly allocate" is slightly misleading, imho. It's promoted to a static. There's no malloc involved.
- masklinn 6y ago> "implicitly allocate" is slightly misleading Maybe. I just meant that storage is created implicitly (static or stackframe depending on the case), then a reference is created to that.,
- anonacct38 6y ago& doesn't always imply a value is on the heap. Escape analysis will ensure that pointers to the stack are safe. Here's an example with a bit of explanation: if you pass a value to fmt.Println it will escape. The raw println builtin does not cause values to escape. So calling the first function twice and seeing the same address for the value strongly implies stack allocation while calling the 2nd function twice and getting different addresses implies heap allocation. https://play.golang.org/p/PSb1wj1-x1c https://play.golang.org/p/PSb1wj1-x1c
- effdee 6y agoMinutes ago I was wondering about https://play.golang.org/p/9C0puRUstrP https://play.golang.org/p/9C0puRUstrP (via https://github.com/golang/go/issues/23440 https://github.com/golang/go/issues/23440). Thank you for explaining what's going on there! :) And btw, compiling the above example with -gcflags="-m" (which I learned about earlier today) proves you right.
- Laremere 6y agoI asked that in the Github issue, response here: https://github.com/golang/go/issues/45624#issuecomment-822590091 https://github.com/golang/go/issues/45624#issuecomment-82259... With my reply (and further exploration) here: https://github.com/golang/go/issues/45624#issuecomment-822632731 https://github.com/golang/go/issues/45624#issuecomment-82263...
- thomasahle 6y agoRuss Cox has some nice examples of the issues with this: Otherwise the meaning of &f().x is different for f() returning pointer-to-struct and f() returning struct. Similarly &m["x"] is a compile error today but would silently make a copy tomorrow rather than produce a pointer to the value in a map. All of that would be incredibly confusing and the source of many subtle bugs.