Hacker News new | past | comments | ask | show | jobs | submit login
Benefits of named return values in Go (minio.io)
200 points by y4m4b4 on June 30, 2017 | hide | past | web | favorite | 136 comments



I normally hate it when people immediately trot out the old "premature optimization" quote, but it really applies here.

Please don't go around naming all your returns just because today's compiler happens to generate better code with them. This is a compiler issue that I'm confident will be fixed one day, especially if you do the right thing and file an issue.

But by all means, if you're profiling and your inner loops are actually slowed down by this, then make the change. And add a comment so that someone might be able to change it back some day when the compiler's improved.


I filed a Go bug: https://github.com/golang/go/issues/20859

We should just fix the compiler.


I'm surprised Go doesn't compile down to an IR language where these differences in syntax are represented in a single manner. Seems like different ways to write the same thing.


It increasingly does.

But it's been a process.

Go 1.5 was the first self-hosting release, with the Go compiler auto-translated from C to Go. But it was still fundamentally Ken's C compiler in Go syntax.

Every release since (Go 1.6, Go 1.7, Go 1.8, Go 1.9) has been cleaning it up and making it more Go like and less C like.

Meanwhile, the backend was also retrofitted. Go 1.7 included an SSA backend for amd64 (https://golang.org/doc/go1.7#compiler).

Go 1.8 included it for all architectures and added more SSA goodness.

Go 1.9 adds yet more, but some things are still not pushed down into SSA as well as they could be. (e.g. https://github.com/golang/go/issues/5147#issuecomment-247685...)

Nowadays we can add optimizations much more easily, including writing matching rules like in https://github.com/golang/go/blob/master/src/cmd/compile/int...

Meanwhile the whole toolchain keeps getting cleaned up and more hackable.

In Go 1.9, the compiler is now parallelized, which would've been impossible earlier. (https://tip.golang.org/doc/go1.9#parallel-compile)

So, it keeps improving. Just remember the Hello World compiler we started with.

Also amusing in retrospect is that when Go first came out, despite having a very basic compiler at the time, people coming from scripting languages thought we were so fast.


Just for what it's worth, the way I'd fix that problem in the compiler would be to implement dead store elimination via global value numbering. With trivial alias analysis, the compiler would be able to detect that the result of the "duffzero" instruction (which I assume is a memset) is always killed by the "duffcopy" instructions and would eliminate it.

See this article for how it's done in LLVM: http://blog.llvm.org/2009/12/introduction-to-load-eliminatio...


Not my area of expertise and it is yours, but if you eliminate a zero instruction before a copy instruction how can you be sure that doesn't affect other threads?

  var x Int
  // Pass x to a thread by reference
  x = 0
  time.Sleep(1000)
  x = 1


How can you be sure that the other threads ever see it in time? they might be suspended for a whole second because a HDD needs to spin up or something like that.

So threads never seeing the value is already a valid outcome, so the compiler might as well always do that.


time.Sleep(1000) could be replaced with a real syncing mechanism of some kind -- it's only for illustration.


a real syncing mechanism would introduce compiler barriers that prevent such an optimization.


The answer to that one would be to embed thread-safety in the type system, aka. Rust.

For languages with less sophisticated type systems you get a choice between inefficiency (Go), or complicated rules which state that the programmer is wrong for coding that way (C).


Your "solution" is possible in many languages. It's just to give threads complete ownership of data they use. It doesn't require a special type system.

In general I don't think Rust actually adds much abstraction that isn't already in say Python. What it does is enforce tight constraints.


The constraints are important. They're what let you code safely, and also give the compiler the ability to optimize better.


Not sure those optimisations are worth much. And it's only safety by forcing lowest common denominator code and making you justify everything to a dumb compiler. Rust serves a niche, but it is a tight niche IMO.


Aliasing info is hugely important in optimization. In fact, it's needed to solve this very bug.

Aliasing info is, fundamentally, what allows Rust to have memory safety without GC.


The memory dependence analysis must prove the memory is unaliased, which ensures among other things that no other thread can have a reference to it. Presumably in Go return pointers are guaranteed to be unaliased.


Why does Go not use LLVM? Are there technical reasons to reinvent the wheel, or is it just because LLVM is Apple's pet?


I hope this won't come out harsher than I intend to, but I'm so tired to hear this expression "not reinventing the wheel" to justify using third party code. This is not what it means.

Note that there is not a single wheel that was built once in prehistory and now every human gets it lent when they need it. People build wheels everyday to fit their needs, reusing the concept of wheel, that is, knowing that a circular object allows for smooth movements with less friction. The analogy in software development means that you've better know of designs that help you solve your problem, not that you should blindly use code built by someone else to bypass the whole problem solving. This is basically trying to use a bicycle wheel for everything. This may work well on an other bicycle, not on a car.


See this answer from Russ Cox: https://news.ycombinator.com/item?id=8817990


Thank you for an insightful answer.


First part of the "Implementation" section: they thought it was too large and slow for their compiler speed goals.

https://golang.org/doc/faq#Implementation


I'm pretty sure Go does compile down to an IR, but it's little more than an abstraction over different architectures. I could be wrong.


Originally it compiled to Plan9 assembly which is cross platform. They have an SSA backend for some architectures now.


I thought the SSA backend was not replacing the Plan9 assembly, but that it was a phase that happened before the assembly was output (presumably SSA is a phase and not an IR?).


Oh. That actually make more sense.


It does if you use gcc though - both variants of the code in question here compiles to the same assembly (at least with gcc 7.1 on x86-84)


I think accusations of premature optimisation might be a little unfair here.

Ignoring the style issues for a second (I'll pick that up later), if I'm looking at some code and there are two equally viable alternative ways of writing it, one of which saves a chunk of memory* or is faster then it's just perverse to choose the path of larger/slower code. I do this with regular expressions/string functions. I see people use regexes a lot, but the tool I reach for first when doing string operations are the built-in string functions, eg. https://golang.org/pkg/strings/#Contains or https://ruby-doc.org/core-2.4.0/String.html#method-i-start_w.... I'm not optimising, I'm just not de-optimising.

Back to the style issue, at this point if you really feel strongly about the way it looks in the editor, or in documentation then I can see why you would choose one method over the other, choosing the less desirable, but theoretically faster code would absolutely be a case of premature optimisation. I personally don't have a particular preference either way however, and feel like the reasons outlined in the style guide are rather fragile. So ultimately, if I pick up some code full of named return values I don't think it would bother me, in the same way that code that uses none, or mixes them where someone thought it appropriate doesn't bother me either.

* There may be benefits other than just disk/distribution size. Many years ago I read about the benefits of small binaries, something relating to CPU caches, though that may be out of date now and I forget the details.


The issue is that with the string/regex issue, there's a good reason that the string operation should be faster. Maybe a "sufficiently smart compiler" could optimize it in some cases of static regexes, but it's at least complicated. In cases where the regex is dynamically determined, even the sufficiently smart compiler probably can't optimize away the regex.

In contrast, the case in this article seems like table stakes for an optimizing compiler. It's just not eliminating common subexpressions. There's no reason to contort your code around something that should automatically happen.


When I've benchmarked regexes before (Ruby ISTR) there have been situations where the regex engine optimised the code to be comparable to string functions, I can't remember the details though. Just for fun I benchmarked =~ /\Asomething/ and start_with?("something"), a situation that seems could be optimisable by the regex engine, but the string function is still faster (Ruby 2.3.1):

start_with: 5703143.1 i/s regex: 2821224.4 i/s - 2.02x slower

That aside:

> There's no reason to contort your code around something that should automatically happen.

Absolutely, but it's a question of style at that point. "Contorting your code" suggests using a less desirable style/syntax for some gain, and I'd agree would be premature optimisation. If you're just making a choice between two styles that you consider to be pretty much equal then it's just pragmatic.

edit: Forgot to add, yes, I agree that this should happen automatically in the compiler :)


My point of view is that if you're two equally good styles based on the compiler, it's contorting your code. One doesn't have to be better than the other, if you can't make that choice between semantically identical options, you're conforming to an arbitrary standard.


I dunno. A 30% code size win is non-trivial. I'm all for filing an issue first and seeing how likely it is that there is uptake from the dev team and desire to fix the problem. However, if no fix is forthcoming . . . code size has fairly well known effects on performance.


A 30% code size reduction in code that does little other than construct and return a value. I have certainly seen individual functions where this is the case, but across an entire program, you will not get anywhere near 30% size reduction.

Having said that, this is certainly something that should be fixed in the compiler.

On a related note, in the final assembly, the compiler could also have optimized the 4 RETs into 1, then optimized away all of the conditionals, turning the sample code into the equivilent of "return objectInfo()"). Of course, in a real example, these optimizations would not be possible; but they do show that these reduced cases are not the best way of benchmarking performance.


Fixing it in the compiler will fix it for everyone, however, which is a big argument for fixing it upstream.


Unless it actually impacts the use case of the application, and has been confirmed by a profiler that is indeed the case, it is just cargo cult optimizations.


I'm not the most proficient Go developer, but aren't the two examples doing two different things?

In the first way, each branch allocates its own objectInfo struct. In the second way, all the branch exits share the same single objectInfo struct which I assume is implicitly initialized to the zero value by the compiler.

Functionally these are the same, but couldn't you achieve the same results without named return values by just allocating a single objectInfo at the top of the function and returning it?


This was my observation as well, but even more importantly, in the given examples they're naming a value to get this benefit, but then never actually referring to the value by name. This is contorted at best.


Yes, you are correct. Doing var return MyStruct {} at the beginning of the function would do exactly the same thing as naming return value.


Yes, you can, but just naming the return variable takes care of this.


Yes it will work.


I look at these result functions and all I can think is, "Wow, it must be really delightful when you get a zero record back and what to know why your parser has a bug." I really hope these are didactic in nature and not a representation of industry best practice.

Why not just declare a record in scope and return that? Why rely on a sneaky little piece of syntax tucked away in the return value? Aren't gophers supposed to be all about a regular language with few surprises?


> Why not just declare a record in scope and return that? Why rely on a sneaky little piece of syntax tucked away in the return value? Aren't gophers supposed to be all about a regular language with few surprises?

It's not a sneaky piece of syntax, in fact it is required to use that syntax if you want to reference or modify the return value of a function in a deferred function call.

I don't like the syntax and hate that Go forces you to use it in the above case, but it's definitely not sneaky syntax. Any regular Go developer should be expected to know about this construct.


If you have long function and the addition of a single character up top at the right changes the behavior of the return keyword?

I've hated this feature since I learned about it. Its existence is one of the many reasons I dismiss arguments about Go being easy to read.


For the most part named returns are frowned upon, and while it is admittedly an oddity in the language, having one oddity is still achieving the stated goal of "few surprises". :)


Are they actually frowned upon? I've been writing Go for a few years now and while I acknowledge there will be blind spots in my knowledge, this is still the first I've heard against using that language feature.

In fact named returns appear all over the Go source code itself[0] and the Effective Go section on named returns[1] seems positive about their usage.

[0] https://github.com/golang/go/blob/master/src/os/file.go (file taken at random)

[1] https://golang.org/doc/effective_go.html#named-results

Personally I use named returns heavily because it helps document what each return is used for. Which is particularly useful in IDEs like IntelliJ where you get the tooltip popup with the function parameters and returns. But frankly I find named returns just as useful when reading back old code on GitHub or even just in vi.

I also notice I'm not the only one who uses them in production code. Here are some popular packages by other talented Go developers (all .go files picked at random)[2][3]:

[2] https://github.com/kr/pty/blob/master/util.go (this package creates UNIX PTY files)

[3] https://github.com/chzyer/readline/blob/master/operation.go (readline package used by cockroachdb and Netflix)

In fact of the packages I checked, I could only find one package that didn't make heavy use of named returns[4]

[4] https://github.com/go-sql-driver/mysql/

So if they are frowned upon, I'd be interested to know more about who and why.


"Are they actually frowned upon? I've been writing Go for a few years now and while I acknowledge there will be blind spots in my knowledge, this is still the first I've heard against using that language feature."

/r/golang consensus is against using named values. I disagree with it, but the consensus is there. Or at least I think that at the very least named return values should indicate that someone is doing something with a defer.

As for why, I don't get a clear sense. I hate to characterize an argument I disagree with, but I have to admit I just sort of get the sense that it was one of those things that just sorta happened. Sometimes one person says something, another person half-heartedly agrees, and before you know it it's getting parroted around by an entire community. I acknowledge again that as I don't agree with it this may be a rather harsh read on my part.


> /r/golang consensus is against using named values.

/r/golang is a toxic subreddit and I wouldn't take anything said there for face value. Dave Cheney deleted his account there and the go team itself wanted to delete that subreddit. So no, /r/golang represents only /r/golang and certainly not the go community as a whole.

/r/golang favorite pass time is to take some piece of code or a library and humiliate its author(s) publicly for not writing go like /r/golang wants go to be written.


Dramatic much? There's nothing "toxic" about /r/golang. It's mostly boring content, and the criticism is rarely worse than what's going on in this very thread (which is itself mild by HN standards). The official rationale for why Dave and the Go team left has changed several times, but it has always been over-dramatic or vague (e.g., "We need to shut down /r/golang because the CEO of reddit might edit our posts like he did with /r/The_Donald!"). At any rate, their opinions of /r/golang don't make it toxic, and /r/golang is a reasonable approximation of the broader community's opinions--at least as good as anywhere except maybe the mailing list.


"and the criticism is rarely worse than what's going on in this very thread (which is itself mild by HN standards)."

I'll echo that. If /r/golang is "toxic" this HN conversation is outright radioactive. This is far worse than any day-to-day thread on /r/golang.

As I understand it, the basic reason the Golang authors themselves decided to leave wasn't the "golang" part of "r/golang", but the "r" part; Brad Fitzpatrick, having run LiveJournal, was incredibly and IMHO justifiably offended by the revelation that Reddit admins had been editing people's posts, because of his personal experiences and ethics. It also turned out that the Golang people had not started the community, but accidentally thought they owned it none-the-less, which IMHO puts a new light on how the /r/golang community reacted when the Go authors attempted to lay down rules on how it works. Of course the community reacted poorly to what amounted to people just dropping by and asserting tons of ownership over the community they hadn't created and only marginally participated in. Nobody should expect that to just go swimmingly. But at the core it was all a misunderstanding as to who owned the community, which has since been resolved.


> There's nothing "toxic" about /r/golang

The fact that go maintainers themselves wanted to get rid of /r/golang actually proves otherwise. As for Dave Cheney, he left that sub long before any Trump drama on reddit.

Obviously the people who act in a toxic fashion don't think themselves as such and don't see the toxicity even when it blatantly exists.

But that's not my point. /r/golang is officially outside Go community since even go maintainers disowned it and want nothing to do with it. Whatever drama happening there is not representative of the go community, nor whatever /r/golang thinks as "idiomatic".


It doesn't "prove" anything. It only proves that the maintainers are intolerant of the community, for whatever reason.

> Obviously the people who act in a toxic fashion don't think themselves as such and don't see the toxicity even when it blatantly exists.

Maybe I "act in a toxic fashion" or maybe your standards for "toxic" are just extremely low. Fortunately, we don't have to agree on the absolute threshold for toxic, we can make relative comparisons; as /u/jerf stated, if /r/golang is toxic, this thread is radioactive. Anyway, it's rude to make unfalsifiable implications about other people, or to speak as though your own subjective thresholds are absolute ("...toxicity even when it blatantly exists"); and it's foolishness when your own standard for "toxic" is so much lower than the common use.

> /r/golang is officially outside Go community since even go maintainers disowned it and want nothing to do with it.

You're confusing the Go community with the various subcommunities under the maintainers' moderation. You have to provide a better rationale for why /r/golang is an invalid sampling of Gophers for the purpose of named returns; "because the maintainers don't like it" is not very compelling.


> It doesn't "prove" anything. It only proves that the maintainers are intolerant of the community, for whatever reason.

Wow. What a way to prove the parent's point.

>Maybe I "act in a toxic fashion" or maybe your standards for "toxic" are just extremely low.

Honestly, you appear to make it your own personal point to act like a dick, and for no reason at all. That's very toxic, and in a very needless manner. If your behavior is any indication of what goes on in /r/golang, no wonder relevant people decided to abandon it. There's nothing to be gained by sticking around people who make it their point to act like dicks.


Good to know. Thanks for sharing this, as it's helpful for other folks who now have Go to deal with.


Quite frankly, your post makes me think maybe /r/golang is in fact toxic.

You fight accusations of toxicity with examples of the community being great, not with defensive and vague posts.


You can think what you like. My post wasn't defensive or vague, but then again who can disprove accusations about their motives? The OP was criticizing a community I don't belong to, and which I even criticized in the post above; so you can believe me if you like or not. As for "vague"; it's hard to be too specific when the OP would only speak about unqualified "toxicity".


A couple of months ago I had someone on /r/golang taking credit for one of my own projects. I just ignored him and took it as a compliment but now I'm not sure if that's an insult (jk)

Disclaimer: I don't follow /r/golang. I was looking at Github traffic and noticed quite a few requests with Reddit as their refer(r)er.


Sounds like #scala used to be.


I think changing the behavior of the return keyword is a bit worrisome given the often long nature of Go functions.

The way return works is dictated by the existence of a perhaps bad name in the start of the function declaration, in a place most likely to fall off screen in a split editor scenario.

I called it sneaky because I think it's hard to see. I don't get the value of this technique over, say, naming and returning locals. Certainly from a performance perspective it will be identical.


It doesn't change the behavior of the return keyword, though. It adds a case, but it's an orthogonal case; if you have a function that returns anything, a bare return can only be using the named return values.

My position is that I'm OK with saying "only use this if you need to screw with the return values via defer, and thus, the presence of named return values can be reliably inferred to mean that someone is using defer to change the values", but to say "They're always a bad idea and never use them." is way too strong and dogmatic. They're in the language for a reason, because otherwise errors in defer statements could only be A: ignored or B: panic'ed, which is a far bigger problem in reality than the named return parameters.


Without a named keyword, a void function can use the return keyword to terminate control flow conditionally. So it's only orthogonal in the case of a function with a typed return value.

Which is fine. I have no problem with it. I'm a Haskell/ Clojure/Erlang/TypeScript/CL developer, so I'm used to this. But golang users beat me over the head with the "simplicity" of Go and use examples of Haskell and Clojure having lexical scopes change common keywords as examples of the "excessive complexity of !Golang".

I'm not asking you to defend an argument you didn't make, but I think Go folks need to own that this is actually a code smell for something that could be done differently a scoped variable.


I feel some of the criticisms for this feature are a little exaggerated. I mean comments like "Also my hair gets paler and my body grows weak thinking about using defer with named return values" are borderline silly. Opinions of named returns aside, they're really aren't that hard to follow. Or at least if your function is genuinely that complex that you have a physical reaction when trying to parse a function with a named return then I'd suggest that function needs more refactoring beyond the initial critique.

A few direct counterarguments:

* Named returns doesn't change the nature of the return statement. In fact you can still use return with locally scoped variables even with named returns (example below).

    // This is only an example of overriding the default return.
    // I'm not suggesting people write code like this!
    
    func StrToBool(s string) (b bool) {
        if s == "true" {
            return true
        }
        return
    }
* "often long nature of Go functions" is purely a developer style. I prefer the methodology of breaking functions down to small logical units. Sure sometimes the cleanest code is a long function. But most of the code I write and collaborate with is more around 20 lines or less.

At the end of the day named returns do provide some benefit eg when writing public APIs so other users can see - at a glance - what inputs and what returns a particular function takes. But like any feature in any language, a bad developer will easily find ways to abuse it.


> I mean comments like "Also my hair gets paler and my body grows weak thinking about using defer with named return values" are borderline silly.

I regret my deliberate silliness only reaches "borderline" with you. Do I also need a unicycle. What does it take?!

> StrToBool

Make this function 30 lines long, take 6 arguments and then you have my original argument. It creates context sensitivity, something Golang tries not to do.

> But most of the code I write and collaborate with is more around 20 lines or less.

I'm tempted to write a github crawler to work this out. Golang is C-like in that its lack of reuse capabilities incentivize longer functions or copypaste functions.

I'm simply against this kind of context-sensitivity in a language that prides itself on being reader friendly. It's not. Let me rephrase my argument.

Question:

   return a; // What does this do?
Response: It returns the value at a. I don't know what that value is, but it must be a local or a function parameter. This function definitely returns a value, one value.

Question 2:

   return; // What does THIS do?
Response: Well... all I can say with confidence is that this returns, the functions execution will end. But I can't tell you if it returns nothing, or a value, or how many values.

The existence of this makes bare returns much more confusing than... well... I struggle to come up with a syntactical convention in Golang that can do this. I haven't written Go for reals for 2 years so maybe you have something better.

To me, this is way worse than even the "worst case" Haskell scenarios where your code only makes sense in the context of its caller.


> Make this function 30 lines long, take 6 arguments and then you have my original argument. It creates context sensitivity, something Golang tries not to do.

But like I said before, you're blaming unclear code on the language rather than the developer. It's an optional feature, so don't use it in inappropriate situations.

It's like the whole goto statement argument. Nobody is suggesting we all using goto's just because the feature exists. But very occasionally it does produce cleaner code. Yet you still get an army of evangelists who argue that "goto" should be stripped from every language specification written since the 80s.

I've read a lot of other people's code. Particularly the Go source code itself - there's named returns all over the place there. There are also some quite long and complicated functions too. The use of a named return has had a negligible impact on my ability to parse a function compared to any of the other inherent complexities that function exhibited. ie I followed that code just as easily than if those returns were not named.

Which is why I keep coming back to the "You're points are not wrong per se but they are greatly exaggerated." arguement. But like nearly all arguments about language semantics and syntax, developers love to argue how their personal preferences are conclusive scientific facts. Ironically spending more time trying to prove our points online than we actually spend affected by the problems we're arguing about.

So yes, you are not wrong per se. But you are greatly exaggerating the issue.


> But like I said before, you're blaming unclear code on the language rather than the developer. It's an optional feature, so don't use it in inappropriate situations.

There's a 30 line function claimed to be a refactor of production code in the article we are discussing. It's not my whole cloth example. Heck, my first post was commenting on the structure of that very function hoping it was a defactoring example. People have chosen to focus on the other point in that post.

> So yes, you are not wrong per se. But you are greatly exaggerating the issue.

I understand what you're trying to do but I'm pointing to the overarching article. I didn't make this scenario up.

Also, the personal context I bring here is how many lectures I get from anti-haskell-pro-Go people lecturing me about simplicity, obviousness, etc ad nauseum about why I should adopt their language. So if you're detecting a bit of frustration here at a double standard, I do apologize.


Yeah, the article wasn't great. I think few supporters of named returns would even agree with that article.

Like all language features, you're always going to get some individuals who will misuse them. The author there I definitely think is misusing named returns for something that really needs to be optimised in the compiler instead.


In defense of the article, I can see how anyone writing a lot of Go would start to get a bit desperate for shortcuts. It's a language that's relentlessly hard on prolific writers, putting their needs second in favor of minimizing the context required for any code.

So I could see how things like having a alternative-providing validator chain take 5% fewer characters would feel intoxicating after using code generation to stamp out four variants of a competent data structure for some primitive types.


One minute you're moaning that features need to be removed from Go, the next you're mocking it for not having enough features. Yet you dont even code in the language you're clearly very opinionated and highly critical over.

We all have our personal preferences, I get that. But why can't people just live and let live instead of trying to make out their personal preferences are measurably better than those they dislike? After all, if all programming languages were identical then the IT industry would be worse off for it.

For what it's worth, I've been writing software for nearly 3 decades now and have writing applications in well over a dozen different languages. Go might lack some of the expressiveness I'm used to but it's still one of the most rapid languages I've used to create software. And one of the most painless to deploy too. In fact ironically some of the "worst languages" in terms of developer chin stroking have been some of the easiest to work with; Visual Basic (pre .NET) is another example. But if I had my way we would be back to writing DOS programs in Turbo Pascal. This is why I get so fed up with people moaning about their tools. Frankly put, mocking any particular tool for not behaving like another particular tool just shows ones own limitation as a developer.


> But why can't people just live and let live instead of trying to make out their personal preferences are measurably better than those they dislike? After all, if all programming languages were identical then the IT industry would be worse off for it.

I enjoy programming in Go about as much as I enjoy wearing shoes about 3 sizes too small. As a result, it's my goal to find very good arguments against its use in any of my projects. Should I feel bad about this? If so, can you come and stop people from powerdunking on me every time I talk about how Haskell is good at something?

I'm not mocking Golang here. It is what it is. I think it's an oddly designed tool based more around making Google's turnover easier to deal with rather than helping me as a software engineer. I am expressing frustration at people who want to laud its design as "good" or "progressive" or helpful when really, it's the language equivalent of a querty keyboard layout. Designed to be about as easy to learn as anything but prevent anyone from getting excessively good with it, because large skill gaps in a workforce with turnover are hard to manage around.


The Go code review document gives a brief comment about when they like and don't like to use them[1].

Personally the only time I find them to be acceptable is when you have to reference a returned value in a defer. All other usages are questionable, though this is a taste thing and is likely because of my dislike in it's usage as a way of omitting a `var` in a function body (and also the fact that naked returns are magical in comparison).

[1]: https://github.com/golang/go/wiki/CodeReviewComments#named-r...


I don't think naked returns are any more magical than when you just drop a variable name in there. Either way, when you read a return statement you'll want to re-read the code to find the context of that return. The only difference is with named returned the variable is declared in the function declaration rather than the body of code. So it's definitely just a preference thing there.

My coding style is to try and avoid lengthy functions - my procedural code is definitely inspired by the functional paradigm. Granted it's not always possibly to avoid a long function but for the shorter ones named returns tend to produce cleaner looking code _in my opinion_.

In any case, I will definitely take your points on board and be more mindful about when using named returns - ie think about whether they're adding to the readability or if I'm just being lazy saving myself a var declaration. Thank you for the reference article as well.


> Either way, when you read a return statement you'll want to re-read the code to find the context of that return.

That's the ugly part of this feature. It adds one more place where you need to check.

Also my hair gets paler and my body grows weak thinking about using defer with named return values. Isn't Go supposed to easy to read? Isn't that like it's principle virtue?


Eh, I've heard this a few times; I thought once from Francesc Campoy, but maybe I'm wrong? I don't feel strongly about them, I was just repeating what I heard.

The rationale was that they can make code hard to follow, especially when one of the returns is err and you have other errs in your function.

Lastly, to be clear, I didn't mean to imply they were frowned upon in all cases; only that you should prefer normal returns unless you have a compelling reason.


Definitely agreed. Since getting into using Gogland (the JetBrains Go IDE), I'm using named return values much more because the name is useful in the pop-up help. I don't need to look up function docs anywhere near as often.


This is possibly one of the worst reasons to use named return values. To be honest, I've always disliked them because they make `return` magical since you now have to read through the whole function twice to make sure that you know what is actually being returned. Luckily you can do explicit returns with named returns, but then it becomes confusing if you have non-explicit returns in the same function.

In my mind there are only two reasons to use named returns. The first is when writing interfaces, and you want to document the returned values in a nicer way (in addition to the godoc). This is mostly a taste thing and is not required.

However, the second one is required by the language and that is deferred function calls that interact with the return value of a function. If you want to have a deferred function call that does some cleanup but has to check the error value of the function (for example a cleanup that only runs if an error occurred) then you have to use named return values. If you want to have a deferred function that changes the return value of the main function then you also need named return values. This is a fairly annoying requirement of Go, but I understand why they felt it was necessary to do things that way to make it feel more explicit.

Other than that, in general named returns make functions harder to read (in my opinion). In a similar vein, I really don't like Ruby's or Rust's implicit returns (though at least Rust is less magical about it).


This clouds up your godoc with unnecessary names though. Why not just initialize a default value at the top of your function? You don't get the bare returns but it's a lot cleaner.


Does it? Names are documentation no? Eg, that's the one thing I actually like about named return values.

In my perfect world, we would have named return values by default, so then we could use them to document the return values, but never empty returns. Empty returns are the only problem with named return values Imo, as it's easy to accidentally slip values into the return that you didn't intend.

Though, I had always wondered about returning copies like Foo{} on every return.. guess I know now.

edit: As an aside, I should not dismiss your cluttering comment. I did so, because I feel it's unrelated to this exact topic. If named return values, something which by it's very nature conveys additional information, is cluttering the godocs, then that should be a bug, and filed/fixed accordingly. Imo.


This post specifically used very short uninformative names though, which was more to my point. If you're not going to use informative names, it shouldn't be part of the signature, especially when its barely any extra typing to do the same within the function body.


That I can definitely agree with.


This is one of the few places Go fails to avoid the "multiple ways of doing the same thing" problem that many languages are overflowing with. It adds cognitive overhead and creates needless debate and afaik avoiding this has been one of the Go team's guiding principles.


I don't know all that much about compilers, but I'm slightly surprised that Go doesn't automatically optimize the first into being the same as the second.


Generally, any modern optimization pipeline that includes global value numbering and basic memory dependence analysis will perform that transformation, yes.


Modern being the operative word here.


I don't understand, through these examples, why the introduction of a name is necessary in order that an empty return statement be used.

The name doesn't even appear in the function. A name that is declared but not subsequently mentioned serves no purpose (or some side purpose/hack).

In the "NoNamedReturnValue" variant, the return type is still declared. The compiler could, from that type alone, infer that "return" means "return a representative default instance of that type".

That is to say, why can't Go programmers just have this:

  // oi name removed:
  func NamedReturnParams(i int) (/* oi */ objectInfo) {

	if i == 1 {
		// Do one thing
		return // wee, allow this anyway!
	}

	if i == 2 {
		// Do another thing
		return
	}

	if i == 3 {
		// Do one more thing still
		return
	}

	// Normal return
	return
  }
Also, why can't the compiler just optimize away the "return Objectinfo {}" statements down to "return", if those really are equivalent.


I very, very rarely use named return values. My usual go-to problem is when one of the return values is an `error` that I want to set inside `recover()`.


Surprised to find this not mentioned in the article and buried in comments. It's the first thing that comes to my mind as not merely a benefit, but an essential role played by the named returns.


I was going to post "But JS has this:

    return { named_var_1, named_var_2 }

    const { named_var_1, named_var_2 } = f()
But then I read the article.

I don't know really what is going on in this article on a quick reading, but it builds by existing sense that go is a really cool language that I ought to get into.

I have no idea if anyone wants to help me understand, but in the last part where he changes to use a named return parameter, he says, you may also "enjoy the cleaner look of the source code, too". To me the only difference I see is the return name, to the right of the existing function signature. What was I missing that makes this actually cleaner?


Naming the return value in the function signature will both allocate the named variable, as well as make it the default variable to return.

Returning a new unnamed variable for every return will cause all of the unnamed variables to be allocated.

It's interesting, I don't use this feature of the language... my default way to write this would have been:

  func NoNamedReturnParams(i int) (*objectInfo) {
        obj := &objectInfo{}
	if i == 1 {
		// Do one thing
		return obj
	}

	if i == 2 {
		// Do another thing
		return obj
	}

	if i == 3 {
		// Do one more thing still
		return obj
	}

	// Normal return
	return obj
  }


I had a bit of a play around with it. It's pretty powerful being able to see <func name> <params type> <return type> in one line ( or possibly multi line )

It really hammers home the concept that a function is a transformation, and of what into what. And I think this syntax would probably encourage pure functions. And it's so useful to allocate the return in the top line. I really like go.


I'm fairly sure that almost every statically typed language has function definitions like that. C, C++, Java, Go, Rust and so on all have that style of function declaration.

Named returns are a Go thing mainly because of defer.


How does defer interact with named returns?


As one would expect. The deferred code runs after the normal code in the function has ended, and before the calling code has resumed. Named return values are in scope and can be read and modified.

Try it and see: https://play.golang.org/p/1ozFWDj15a


Cool. Can anything execute between the end of the deferer and the start of the deferred?


Anything as from any other thread/goroutine? Yes of course, all shared data must be protected from races.

If you mean in the same context then, no.


I'm thinking of it in the way that the interpreter works in JavaScript with tasks. If you setTimeout(A, 0), inside B, then after B returns, A is called, but any other tasks previously inserted into the queue are called first.

So I think that means I was asking about execution in the same context (as in memory context), unless you mean stack frame by context, in which case, I think i understand that because the caller returns the value of the deferred, they are in the same stack frame, and nothing else could insert in that frame between them. I'm not sure you know what i mean, but do i have it about right?

I don't really understand this, but i think I'm getting somewhere.


It's much simpler than that. It's just a way of defining cleanup functions without having a language-level concept of destructors. Here's an example: https://gobyexample.com/defer.

This simple example should explain everything:

    package main

    import "fmt"

    func function1() {
        defer fmt.Println("function1: defer a")
        fmt.Println("function1: inside")
        defer fmt.Println("function1: defer b")
    }

    func main() {
        fmt.Println("main: before function1")
        function1()
        fmt.Println("main: after function1")
    }
Here is the output:

    main: before function1
    function1: inside
    function1: defer b
    function1: defer a
    main: after function1
All defers run in LIFO order at the point when a function returns before the function returns execution back to the caller.


Got it thanks a lot, that's very clear.


You can easily access and modify the named return variables from a deferred function body.

If you had a variable declared in a block just before return, then you return it, you have no way to modify it in defer.

I personally don't like this style of code, but I can see some uses.


Ok now I got they are in scope. About defer, could you think of it like defer makes a function into a function with multiple entry and exit points, possible to be stopped and resumed, like a coroutine?


Ehm, not sure I understood, but I'd say no. Defer is just code that is executed in reverse order upon return from a fuction. It has nothing to do with coroutines. It's a cleaner way to write the usual C syntax of "goto cleanup_x" code.

Go coroutines (goroutines) are functions invoked with the "go" keyword. These cannot be stopped or resumed, but might be (possibly) executed on a different thread. In any case, they are not guaranteed to be executed immediately in the normal flow of the code.


>Ehm, not sure I understood, but I'd say no. Defer is just code that is executed in reverse order upon return from a fuction.

I'd say more like the equivalent of a "finally" clause (or more) for your whole function.

Though not sure about the "executed in reverse order part" -- what's "in reverse order" about Defer? Except if you mean that multiple defers get executed "last seen first"...


Thanks alot for explaining, I think i get it a bit better now.


defer can refer to and change the return values. This is useful, for example, when the deferred code can trigger an error that one wants to return, https://play.golang.org/p/MBmy9OocAG


Is that actually returning err as the deferred return value of test? In other words, to the caller of test it appears test returns the result of the deferred?


Deferred functions don't return anything. You can modify the values that are returned by the parent function, and those will be returned as the deferred function modified them.


Yep, and one cannot simulate that with local variables. In Go "return v" copies v into the return location before calling the deferred code. If that location is not named, the deferred function has no way to change it, see https://play.golang.org/p/Opg4XI08P7


I got it. That's a neat example. Do you have to define that deferred function inside the caller, in order to reference the name? Or can you factor out deferred functions to be used by various callers and pass in the return names for them to modify?


You can pass a pointer to the named return values to a function defined outside, like in https://play.golang.org/p/5jBKcUCj8C .


Ah, okay, thanks. I get it better now. Sort of like decorators in purpose, then.


What language that has static types doesn't have the func, params, and return type in one line?


Some HM language can infer types at the toplevel so you may not have an explicit signature at all, although that's generally frowned upon:

    add1 = map (+1)
OTOH they also split the signature and function "header" so that you don't need to remove the "noise" to get the bare signature e.g.

    add1 :: (Num a) => [a] -> [a]
    add1 = map (+1)


C++ ends up with three forms (of course it does):

Normal C style declaration

    int foo() { return 2; }
Trailing return type. Useful when the return type depends on the parameter types, or is inside the namespace of the function

    auto foo() -> int { return 2; }
Automated type deduction. Increasingly the choice when possible

    auto foo() { return 2; }


K&R C had implied type of 'int' for both arguments and return value, and didn't list the types of arguments on the line specifying function name, argument names and return type:

  int foo(x,y)
  {
     return baz(x,y);
  }

  bar(x,y,z)
  short x;
  int z;
  {
     return x+y+z;
  }

  baz(x,y,z)
  {
     return 42;
  }


It allows you to assign to the "return variables" inside the function, then just return without naming all the returned values at every return.

e.g. https://play.golang.org/p/gdac5QR-wW

This can also be used to return the default value for a type by never assigning anything to the return value.


Okay, I think I get it. The format / syntax is pretty easy, simple and clean. I like it. I added a new function that takes two params and returns param, so I think I get this naming the returns now.

https://play.golang.org/p/7bfiSqKY_w

I was also looking for a "spread" / ... operator, but couldn't see a way to do that.


Sounds like an explicit version of https://en.wikipedia.org/wiki/Return_value_optimization .


No, RVO is about the compiler silently moving the on-stack location of the "returned" object into the callers frame.


This is a neat trick. But when I look at their examples it seems more like err conditions rather than normal behavior for the early return, in which case they should add the error into the return tuple?


For me, the biggest reason I use named parameters is when something I'm ’defer’ring can fail. You can set your err return value within your defer only if it's named.

I went into reading this expecting that use case but was saddened it was not mentioned. The use case the author does give is kind of meh, I find it to be harder to understand what is going on than returning as usual, and that's worse than the slight supposed compiler gain is worth.


This is really a Go wart, in my opinion. Go forces you into this pattern, because there's no other way if you want to use defers. So while it's true that it's a legit use of named return values, it's an unfortunate one, too.


I feel like a lot of deferred errors go unhandled because inexperienced devs don't know that they can.


That's one big reason why naked returns are frowned upon.

It's much easier to read code where there is a clean "return nil, fmt.Errorf(...)".


I don't do it, but mostly because all I would be doing is logging the error, and if some day it takes me a little longer to root cause some odd file close error, I'll still come out ahead.


This isn't necessarily an argument for named return values. It's an argument for declaring your return value up front and even then it's really just an argument for Go to do a better job in the compiler here.

Named return values are one way to declare your return value up front but you could just as easily have had a oi := objectInfo{} line at the top of your function too with the same effect.


I feel like it was a mistake to allow mixing the two styles inside the same function. I ran into enough issues with non-initialized returns during my adventures in Go to stop using named return parameters completely. To me, this feature has the same appeal as JavaScript's optional semicolons; or Pascals with-statements; convenience at the price of reliability and correctness.


Leaving aside any quick benefits in code size or run time, named return values might make code more readable.

Fortran functions have always (since Fortran II anyway) used named result values; so did Pascal. Since you can now pick the name of the result value variable in Fortran, I find it a useful convention to just always put RESULT(RESULT) on my functions.


I went this route a little for a side project because it's natural to pass up errors via `err` this way, but I found out you have to have a naked return at the end of your function no matter what. I can't at all think of a reason for this; is there one?



I actually hate named return values. I'm relatively new to Go, but I like some of the ideas about it. Named return values is something about Go that I despise because it is absolutely misused in my opinion. For such an opinionated language, with some opinions that I don't agree with, this is one that I vehemently disagree with.

It makes the code a lot harder to read, and you need to keep more memorized "magic" in your head. For example, you can't just look at the return values to figure out what a function returns, now you need to look to see if the return values are named, and then trace through that.

I very much prefer explicit code as opposed to implicit code, especially on a day-to-day basis. It just makes my life a lot easier in the long run.


I think you are talking about bare returns, which are somewhat orthogonal to named return values.

You can use non-bare returns with named return values, just mention the names in the return statement. This compiles to the same code as the bare return.

I'm a bit ambivalent about bare returns, but named return values are useful, especially in the presence of defer statements.


I feel the opposite way. I always use named return values because I feel the code is less cluttered and less magic. I just look at the first line of a function and I know what variables are used as return.

I think they are pretty explicit and simpler, because there are defined in a single point of our code. Anyway, we probably have different backgrounds :)

Although it wasn't your point, what I don't like is mixing bare and named return. I think it's better to stick to one style or the other.


Its a good point. Especially since one of go's mantra's is simplicity.


I'm not familiar with Go and I'm confused by the fact that even though the returned variable is named, e.g. "ch", that name is never mentioned in the function body.

What am I missing?


Naked returns. Basically, you just write "return" and that returns the values declared as named return parameters.

If they are never mentioned in the body you probably have a return of the default nil value for the named return variables.

But then at least one return is not naked and returns some value, discarding the named one.

I think it's a bad use of named returns.


Nothing.

The named return value is default-initialised (zero-initialised) before the function body (you don't have to assign to it, you can just modify it in place), and if you don't explicitly return a different value it will be returned regardless of you using it or not.


These don't represent a real world application at all. I'm not convinced this is anything more than a compiler optimization for a function that is essentially a noop.


Or you could return a pointer...


>In fact we are also investigating if we can develop a little utility to help or automate this process.

go fix?

Also, it seems like this is begging for a compiler optimization that will make this all obsolete...


this helps with legibility I think, you can see by the function definition that you have return variables: a,b,c and don't have to scan all the returns to see which order things are in.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: