This seems to be a persistent source of confusion. Escape analysis is just an optimization. You don't need to think about it to understand why your Go code behaves the way it does. Just imagine that everything is allocated on the heap and you won't have any surprises.
I am currently learning go and your comment made me sort some things out, but probably in a counterintuitive way. Assuming to everything allocates on the heap, will solve this specific confusion. My understanding is that C will let you crash quite fast if the stack becomes too large, go will dynamically grow the stack as needed. So it's possible to think you're working on the heap, but you are actually threshing the…
Go's escape analysis and why my function return worked
21–30 of 76 posts
Re: Go's escape analysis and why my function return worked
#22It's not confusing that this works in Go. (In my opinion). A straightforward reading of the code suggests that it should do what it does. The confusion here is a property of C, not of Go. It's a property of C that you need to care about the difference between the stack and the heap, it's not a general fact about programming. I don't think Go is doing anything confusing.
I like Go a lot, but I often wish we could be more explicit about where allocations are. It’s often important for writing performant code, but instead of having semantics we have to check against the stack analyzer which has poor ergonomics and may break at any time. But yeah, to your point, returning a slice in a GC language is not some exotic thing.
I commented elsewhere on this post that I rarely have to think about stacks and heaps when writing Go, so maybe this isn’t my issue to care about either.
Re: Go's escape analysis and why my function return worked
#23I am so glad I never taken up C. This sound like a nightmare of a DX to me.
Re: Go's escape analysis and why my function return worked
#24I don’t think this is confusing to the vast majority of people writing Go. In my experience, the average programmer isn’t even aware of the stack vs heap distinction these days. If you learned to write code in something like Python then coming at Go from “above” this will just work the way you expect. If you come at Go from “below” then yeah it’s a bit weird.
even in C, the concept of returning a pointer to a stack allocated variable is explicitly considered undefined behavior (not illegal, explicitly undefined by the standard, and yes that means unsafe to use). It be one thing if the the standard disallowed it.
but that's only because the memory location pointed to by the pointer will be unknown (even perhaps immediately). the returning of the variable's value itself worked fine. In fact, one can return a stack allocated struct just fine.
TLDR: I don't see what the difference between returning a stack allocated struct in C and a stack allocated slice in Go is to a C programmer. (my guess is that the C programmer thinks that a stack allocated slice in Go is a pointer to a slice, when it isn't, it's a "struct" that wraps a pointer)
Re: Go's escape analysis and why my function return worked
#25Earlier quoted context omitted.
I like Go a lot, but I often wish we could be more explicit about where allocations are. It’s often important for writing performant code, but instead of having semantics we have to check against the stack analyzer which has poor ergonomics and may break at any time. But yeah, to your point, returning a slice in a GC language is not some exotic thing.
I think I would like a “stackvar” declaration that works the same as “var” except my code won’t compile if escape analysis shows it would wind up on the heap. I say that knowing I’m not a language designer and have never written a compiler. This may be an obviously bad idea to somebody experienced in either of those. I commented elsewhere on this post that I rarely have to think about stacks and heaps when writing Go…
Re: Go's escape analysis and why my function return worked
#26This seems to be a persistent source of confusion. Escape analysis is just an optimization. You don't need to think about it to understand why your Go code behaves the way it does. Just imagine that everything is allocated on the heap and you won't have any surprises.
Why? It is the same as in C.
#include
#include
struct slice {
int *data;
size_t len;
size_t cap;
};
struct slice readLogsFromPartition() {
int *data = malloc(2);
data[0] = 1;
data[1] = 2;
return (struct slice){ data, 2, 2 };
}
int main() {
struct slice s = readLogsFromPartition();
for (int i = 0; i Re: Go's escape analysis and why my function return worked
#27> In C, you can't assign a value in a local function and then return it I am so glad I never taken up C. This sound like a nightmare of a DX to me.
And these days, if you're bothering with C you probably care about these things. Accidentally promoting from the stack to the heap would be annoying.
Re: Go's escape analysis and why my function return worked
#28I don’t think this is confusing to the vast majority of people writing Go. In my experience, the average programmer isn’t even aware of the stack vs heap distinction these days. If you learned to write code in something like Python then coming at Go from “above” this will just work the way you expect. If you come at Go from “below” then yeah it’s a bit weird.
I don't see how this is coming at go "from below". even in C, the concept of returning a pointer to a stack allocated variable is explicitly considered undefined behavior (not illegal, explicitly undefined by the standard, and yes that means unsafe to use). It be one thing if the the standard disallowed it. but that's only because the memory location pointed to by the pointer will be unknown (even perhaps immediately…
The following Go code also works perfectly well, where it would obviously be UB in C:
func foo() *int {
i := 7
return &i
}
func main() {
x := foo()
fmt.Printf("The int was: %d", *x) //guaranteed to print 7
}Re: Go's escape analysis and why my function return worked
#29The thing being returned is a slice (a fat pointer) that has pointer, length, capacity. In the code linked you'll see the fat pointer being returned from the function as values. in C you'd get just AX (the pointer, without length and cap)
command-line-arguments_readLogsFromPartition_pc122:
MOVQ BX, AX // slice.ptr -> AX (first result register)
MOVQ SI, BX // slice.len -> BX (second)
MOVQ DX, CX // slice.cap -> CX (third)
The gargabe collection is happening in the FUNCDATA/PCDATA annotations, but I don't really know how that works.Re: Go's escape analysis and why my function return worked
#30This seems to be a persistent source of confusion. Escape analysis is just an optimization. You don't need to think about it to understand why your Go code behaves the way it does. Just imagine that everything is allocated on the heap and you won't have any surprises.
> This seems to be a persistent source of confusion. Why? It is the same as in C. #include #include struct slice { int *data; size_t len; size_t cap; }; struct slice readLogsFromPartition() { int *data = malloc(2); data[0] = 1; data[1] = 2; return (struct slice){ data, 2, 2 }; } int main() { struct slice s = readLogsFromPartition(); for (int i = 0; i
func foo() {
x := []int { 1 }
//SNIP
}
Could translate to C either as: void foo() {
int* x = malloc(1 * sizeof(int));
x[0] = 1;
//...
}
Or as void foo() {
int data[1] = {1};
int *x = data;
//...
}
Depending on the content of //SNIP. However, some people think that the semantics can also match the semantics of the second version in C - when in fact the semantics of the Go code always match the first version, even when the actual implementation is the second version.