-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: Go 2: return if (with context) #53017
Comments
I'm almost afraid to ask, but I assume var ErrBadInput = errors.New("bad input")
func Foo() error {
err := CanReturnMultipleErrorTypes()
return if errors.Is(err, ErrBadInput) { fmt.Errorf("invalid input: %w", err) } else if err != nil { err } else { nil }
} Pretty nasty looking. |
@deltamualpha Correct, Edit: Note that this maintains explicitness. func Foo() error {
err := CanReturnMultipleErrorTypes()
return if errors.Is(err, ErrBadInput) { fmt.Errorf("invalid input: %w", err) }
return if err != nil { err }
return nil
} Originally var ErrBadInput = errors.New("bad input")
func Foo() error {
err := CanReturnMultipleErrorTypes()
if errors.Is(err, ErrBadInput) {
return fmt.Errorf("invalid input: %w", err)
} else if err != nil {
return err
}
return nil
} Originally Alternatively if err := CanReturnMultipleErrorTypes(); errors.Is(err, ErrBadInput) {
return fmt.Errorf("invalid input: %w", err)
} else if
... |
The following people have downvoted with no further explanation: carlmjohnson (from #21498 conversation), chrispickard, florianl, BroccoliHijinx, and gmeligio, MojixCoder, seankhliao, ericlagergren, krhubert, and jimen0. Included to address "community voting". |
(moved to proposal) |
Potential Supporters (from similar proposals): @schwrzstrbn @urban-wombat @eldandev @FlorianUekermann @l0k18 @perholmes @andreynering @alanfo @mccolljr |
I also like this idea. It is similar to Ruby's end of expression if but by placing return if at the beginning of the statement, it becomes much more readable. |
Here's my opinion: Do I find the current state of error handling somewhat unwieldly? Yes. Does this proposal improve upon the things I find unwieldly? No. I think that a proposal like this is too singularly focused. This simplifies single error returns, but I'm not sure it successfully addresses things like error aggregation or propagation. I'm glad you're thinking about alternative solutions for error handling. Please continue to do so. I just don't personally feel like conditional return is the solution. |
I ask that you correct me where I'm misinformed in the following statement. I agree that this proposal doesn't consider error propagation or aggregation. However, it's impossible to solve every mechanic with one proposal, without compromising on fundamentals. I also don't believe it's necessary. In the original error-handling draft design, creators of the language state:
Error propagationGo treats errors as values. The program does not crash when you ignore a string, so you cannot crash when you ignore an error. Go already has technical stacktraces upon If error propagation refers to the request to handle errors, that request is wrong. You are NOT required to handle ignored strings, so why would you be required to handle ignored errors? You are NOT. In comment #52416 (comment) of #52416, @sirkon uses a code statement similar to Russ Cox Error Handling Problem Overview to outline the "issue" with errors. What does Russ say about that code?
Go will never support mandatory error handling and thus never support error propagation. You can propagate error values with Error AggregationHandling MultipleThe Russ Cox Error Handling Problem Overview states no issue with aggregation.
Keep in mind that @rsc starts this same overview with the following statement:
Then proceeds to make a proposal for ComparisonIf aggregation refers to the comparison of errors, we are usually referring to a problem creating by a developer. If I want you to compare 2 strings, I expect you to use the An Why do we need a language feature for this? |
I think this proposal is interesting because it would also be useful when returning various values from a function even when not used for error handling. I used the similar conditional if expression a lot in Ruby before, it is very convenient. Do this is more than just a convenient shorthand for error handling. |
it's similar to if-expression in Rust: if num > 3 {
"Tom"
} else {
"Jerry"
}; Personally, i like this proposal, it's more expressive, but it may also be againsted with reasons like "one way", "keep Go simple", etc.. |
I think if gofmt was changed to accept one liner if statements none of this would be interesting. I don't like the implicit return syntax of Rust, which I see many clearly referring to, because it's even worse than a naked return on a long function or unnamed multiple return values and 5 different variable declarations scattered over 60+ lines of code. The gofmt solution changes nothing in the compile it just makes a small readability improvement. We can make one liner function declarations, why not one liner for all parenthesised blocks? |
A thousand times THIS! This is purely a formatting problem. In other (more relaxed) languages, you just get all of this on one line, and the problem goes away:
Or whatever. It's a hack, but GoFmt could simply make it a rule that if a "return" statement is on a line, it isn't formatted. Instant 70% reduction in code spam caused by error handling. |
Yes, but that's what #33113 proposed. And this proposal boils down to adding a new keyword to avoid trying to have that discussion again. |
This isn't really what I was referring to. A lot of code can generate errors. Syscalls, parsing, validation, network requests, etc - most code we write can in some way produce an error. It is not always clear what that error should be or where our code should handle those errors, especially during initial prototyping. To take a limited example, think about making an API request - some errors are immediately retryable, some are retryable after some action (i.e. we send a request to refresh an auth token), and some are unrecoverable. If you want to handle those 3 cases differently, you have to pipe around the raw errors and check them. This necessarily makes From that perspective, saving a few characters of typing for each intermediate error return doesn't really address my pain point, which is that I have to either: assume I will eventually want to handle an error and add Another common thing that comes up is the need to do a series of N things, where each thing can generate a single value and a single error, and where each thing N depends on the value generated by thing N-1. In this case, I end up writing N lines of function calls and Nx3 lines of error handling. Even if that were simplified to N lines of function calls and N lines of error handling with simplified return statements, the error handling code takes up 50% of the space even though I'm not handling any of the errors locally to that code - I'm just sending them somewhere else to be handled, ignored, reported, or aggregated. If I want to use "default" values at each step, rather than just return the errors? It goes back up to Nx3 lines of code because I can't use the early return anymore. If I need to clean up after the previous steps? I either have to pull this N-step process out into a helper function and use I do not want
It does not. I was referring to these situations:
And I'm not sure that a language feature is needed to address this. There are libraries that help, like In general, I think It is true, because errors are literally values, in that they can be ignored, collected, moved around, and in general treated as any other value. I think this is a good thing. It is false, because they are also special in that they have a unique role in a program. They are values, but they are values that are treated specially by most code that uses them. Can an integer be an error? Absolutely. Can a string? Absolutely. Can the empty struct be an error? Again, absolutely. But when that object gets wrapped in Perhaps a part of this pain point is how general the |
If expressions in rust can be used anywhere an expression is expected. This includes assignments, in macro and function calls, etc. This proposal is specific to return statements. If this were a proposal for some form of inline If expression I would be more supportive - that would be much more general-purpose. I think such a proposal would be unlikely to pass debate though - the Go team seems like they've firmly made up their minds that such a thing will never exist in the language |
See Iteration One. One-liners can decrease readability by combining extraneous statements with return statements. In a similar manner to what deltamualpha stated in #53017 (comment). |
The main thing is that in Go, errors are values. It's a fundamental of the language.
Again, this code needs to provide a way to compare errors if it is necessary. As an example, network requests usually involve status codes which provide context to an error.
The pain point in the other proposals point at the verbosity of error handling being the issue. I understand what you're saying with regards to error handling, but you cannot avoid handling errors because errors are values. I am not sure what you mean by the following statement.
You can return the zero value with an error. The series of N issue — as you have explained it — can only be handled with exceptions (i.e The following code is from the func CopyFile(src, dst string) error {
handle err {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
r := check os.Open(src)
defer r.Close()
w := check os.Create(dst)
handle err {
w.Close()
os.Remove(dst) // (only if a check fails)
}
check io.Copy(w, r)
check w.Close()
return nil
} In any case, func CopyFile(src, dst string) error {
handle err {
// requires error comparison
if error.Is(ErrOsOpen) {
...
else if error.Is(ErrOsCreate) {
w.Close()
os.Remove(dst) // (only if a check fails)
} else if
...
}
r := check os.Open(src)
defer r.Close()
w := check os.Create(dst)
io.Copy(w, r)
w.Close()
return nil
} When errors are values, we are forced to handle (or ignore) the error at the source. As a result, the only way to improve error handling is by making it easier to write (and read). |
@switchupcb Indeed, and error handling at the source is good, because the context is kept. With |
@mccolljr There is also https://pkg.go.dev/errors which is a standard library approach to managing aggregated errors. |
@switchupcb sorry for delayed response here. I'm aware of the errors package, of course, but it isn't actually standard library as far as I am aware, even if it is commonly used. As for your other points regarding what is/isn't possible with errors in go due to the fact that errors are values: I'd be likely to support a proposal for novel control flow that could benefit error handling as a side effect. As it stands right now, I see conditional return as a minor space-saving syntax optimization, which I can live without for the time being if it means a more aggressive control flow optimization may be considered in the future. If this feature were added to the language I might make use of it, but I don't get the sense that it would dramatically improve my experience with error handling as it stands. |
The suggestion here is that we replace if err != nil {
return err
} with return if err != nil { err } The new code has fewer lines but the same number of characters. The syntax, with a value or list of values in curly braces, is unlike any other current Go syntax. The emoji voting is opposed to this change. Therefore, this is a likely decline. Leaving open for four weeks for final comments. |
I know this is over but after reading the replies I'd have revised that to "one liner if without else statements in gofmt" It's the smallest change. |
This proposal is not a duplicate, but similar to #33113, #27135, #27794, #32860, and #52977. For more information, read Justification.
Code
Iteration 3
Fixes iteration two and scopes the returned variables or functions.
Single
Multi
Iteration 2
Fixes iteration 1 by requiring the user to return a variable. However, this version offsets logic by adding a trailing
if
after the returned variables, in contrast to placing the returned variables AFTER theif
statement (i.eIF err != nil { return ERR }
).Iteration 1
The issue with this version is that it implicitly allows 1-line if statements to be used on a variety of code. The reader will NOT be able to guarantee that the 1-line if statement is returning from the function.
This proposal only supports Iteration 3.
Justification
Is it cryptic? No information is lost while the return statement is made more explicit.
Is it complex? It's understandable by new people.
Is it worth? error handling + one-line returns codebase %.
Is it backwards compatible? Yes.
Does it allow wrapping? Yes.
Does it pass code coverage tools? if
if
does.#33113
This statement is made in the context of a now-closed error proposal (
catch
) and implies that the 1-line if statements would apply to all Go code.#27794 (comment)
This statement involves the syntax for the first iteration, which is not what this proposal sets forth. More important is the argument is that there is already one method to conditionally return (preceding
if
), such that a trailingif
is unnecessary; if it only removes a few lines. As a result, this proposal would be further justified if #21498 passes.Are there any special rules? No special rules are required for error values or named return values (#37141).
Are errors treated as special values? No, since
return if
can be used on any value (matching fundamentals).Think of the vim users!
return if
requires 9 characters, while a statement such aserr != nil
requires 10, giving Vim users 59 (80 - (19 + 2)) characters of free-width. The cost is 1 vertical line as opposed to 3.Readability
People reading code can distinguish that
if
statements involve further evaluation; as opposed to mixing up error-handle or variable-assignment or any other one-lineif
functionality that is prevalent in Go codebases.Here is an example of error code handling (provided by a comment below) in
go 1.18
:Here is an example with
return if
:PLEASE READ THE FOLLOWING COMMENTS BEFORE COMMENTING
#53017 (comment)
As of 5/24, twenty-one people have downvoted without further explanation.
Please upvote the proposal by reacting with a thumbs up.
Downvote by stating your objections in a comment or upvoting a similar comment respectively.
The text was updated successfully, but these errors were encountered: