5 ms·
I haven't written any Go in many years (way before generics), but I'm shocked that something so implicit and magical is now valid Go syntax. I didn't look up t
by ragnese 9mo ago
I haven't written any Go in many years (way before generics), but I'm shocked that something so implicit and magical is now valid Go syntax.
I didn't look up this syntax or its rules, so I'm just reading the code totally naively. Am I to understand that the `user` variable in the final return statement is not really being treated as a value, but as a reference? Because the second part of the return (json.NewDecoder(resp.Body).Decode(&user)) sure looks like it's going to change the value of `user`. My brain wants to think it's "too late" to set `user` to anything by then, because the value was already read out (because I'm assuming the tuple is being constructed by evaluating its arguments left-to-right, like I thought Go's spec enforced for function arg evaluation). I would think that the returned value would be: `(nil, return-value-of-Decode-call)`.
I'm obviously wrong, of course, but whereas I always found Go code to at least be fairly simple--albeit tedious--to read, I find this to be very unintuitive and fairly "magical" for Go's typical design sensibilities.
No real point, here. Just felt so surprised that I couldn't resist saying so...
- jtbaker 9mo agoyeah, not really an expert but my understanding is that naming the return struct automatically allocates the object and places it into the scope. I think that for the user example it works because the NewDecoder is operating on the same memory allocation in the struct. I like the idea of having named returns, since it's common to return many items as a tuple in go functions, and think it's clearer to have those named than leaving it to the user, especially if it's returning many of the same primitive type like ints/floats: ``` type IItem interface { Inventory(id int) (price float64, quantity int, err error) } ``` compared to ``` type IItem interface { Inventory(id int) (float64, int, error) } ``` but feel like the memory allocation and control flow implications make it hard to reason about at a glance for non-trivial functions.
- Someone 9mo ago> My brain wants to think it's "too late" to set `user` to anything by then, because the value was already read out It doesn’t set `user`, it returns the User passed to the function. Computing the second return value modifies that value. Looks weird indeed, but conceptually, both values get computed before they are returned.
- deleted 9mo ago[deleted]
- masklinn 9mo ago> I would think that the returned value would be: `(nil, return-value-of-Decode-call)`. `user` is typed as a struct, so it's always going to be a struct in the output, it can't be nil (it would have to be `*User`). And Decoder.Decode mutates the parameter in place. Named return values essentially create locals for you. And since the function does not use naked returns, it's essentially saving space (and adding some documentation in some cases though here the value is nil) for this: func fetchUser(id int) (User, error) { var user User var err Error resp, err := http.Get(fmt.Sprintf("https://api.example.com/users/%d", id)) if err != nil { return user, err } defer resp.Body.Close() return user, json.NewDecoder(resp.Body).Decode(&user) } https://godbolt.org/z/8Yv49Yvr5 https://godbolt.org/z/8Yv49Yvr5 However Go's named return values are definitely weird and spooky: func foo() (i int) { defer func() { i = 2 }() return 1 } returns 2, not 1.