6 ms·
Overall really great that Go is addressing it's current biggest issues. I do think the argument for the check keyword instead of a ? operator like in Rust is qu
by JelteF 8y ago
Overall really great that Go is addressing it's current biggest issues. I do think the argument for the check keyword instead of a ? operator like in Rust is quite weak. Mainly because a big advantage of the ? operator (apart from the brevity) is that it can be used in method calling chains. With the check keyword this is not possible. AFAICT the check keyword could be replaced for the ? operator in the current proposal to get this advantage, even while keeping the handle keyword.
Furthermore the following statement about rust error handling is simply not true:
> But Rust has no equivalent of handle: the convenience of the ? operator comes with the likely omission of proper handling.
That's because the ? operator does a min more than the code snippet in the draft design shows:
if result.err != nil {
return result.err
}
use(result.value)
Instead it does the following:
if result.err != nil {
return ResultErrorType.from(result.err)
}
use(result.value)
This means that a very common way to handle errors in Rust is to define your own Error type consumes other errors and adds context to them.
- epage 8y ago> But Rust has no equivalent of handle: the convenience of the ? operator comes with the likely omission of proper handling. Additionally, Rust programs heavily use "RAII", avoiding the need for `handle`. But there is talk of adding support for `catch`. See https://internals.rust-lang.org/t/pre-rfc-catching-functions/6505 https://internals.rust-lang.org/t/pre-rfc-catching-functions...
- kyrra 8y agoI get the feeling that the core Go team is rather against 1-character operators. Also, the Go community doesn't tend to do a lot of call chaining from a general stylistic standpoint.
- zlynx 8y agoI've written a lot of Go in the last few years. I stopped chaining calls after I realized how much of a pain it is to debug. When you need to print / log a value from the middle of your call chain you have to take the chain apart. So just write it out to start with. It's not any less efficient.
- mike_hearn 8y agoI'd note that's only an issue if you don't have a good IDE. In IntelliJ you can breakpoint on an expression that uses chaining, hold down the alt key and then click on the expression you want to evaluate. It turns into a hyperlink and can be viewed easily.
- kcmp 8y agoI think needing a heavy IDE to comfortably work in a language/paradigm is a negative
- mike_hearn 8y agoYou don't need an IDE but it solves problems like "I don't want to chain my methods because my weak debugger can't handle it well".
- sagichmal 8y agoChained method calls being difficult to debug does not necessarily mean using a debugger. It can mean difficult to insert clarifying code in the middle of the chain: print statements, or additional value checks, and so on.
- bmurphy1976 8y agoChained methods also don't diff as clearly (the one liners anyway).
- pjmlp 8y agoThey do on GUI diff tools by using different colors.
- Cthulhu_ 8y agoIt's still not as clear as just splitting across lines, plus in most tools you need to explicitly indicate you want to highlight word diffs. But more abstractly speaking: are chained calls, from the POV of a human that didn't write it, or wrote it two months ago, more readable than a number of statements on multiple lines?
- jacques_chester 8y ago> Also, the Go community doesn't tend to do a lot of call chaining from a general stylistic standpoint. There's also the small matter of multiple returns breaking the chain.
- rsc 8y agoYou're right, I oversimplified that. But having a "ResultErrorType" is not really context, not by itself. The interesting context would be additional fields recorded in that type.
- bcantrill 8y agoThat being the case, you should change the doc, because it's an important distinction. Speaking personally, I have found in my own Rust that the presence of the propagation operator does not, in fact, come with the "likely omission of proper handling"; to the contrary, it has allowed me to (properly) propagate errors that I can't meaningfully add context to, and to (properly) handle those errors that I can handle -- all with very readable code that doesn't involve error-prone boilerplate.
- stouset 8y agoYeah, the underhanded comment "the convenience of the ? operator comes with the likely omission of proper handling" is not only unnecessary but completely wrong in my experience. It strikes me as if the author hasn't actually ever used or investigated the language in any kind of depth, and took a wild guess that "making propagating errors easy means people don't actually deal with errors" or something. In practice, writing `?` is no more or less automatic than `if foo, err := someFunc(bar); err != nil { return nil, err }`, but the difference is that it's immediately obvious when special error-handling logic has been added, by the simple fact of its existence.
- lclarkmichalek 8y agoThis is something I don't enjoy about Rust's error handling. The context is added on a package level (or at least, the level at which the Error type is defined, which seems to be usually package level), and often quite far away from where the error was emitted. For all its ills, `errors.Wrap(err, "...")` puts the adding of context incredibly close to the place where the error is relevant.
- cpuguy83 8y ago
- orblivion 8y ago> chaining Could you do this? check (check func1()).func2() Looks ugly but maybe they can clean it up further. In fact maybe they could just introduce something that looks like ? but works like check.
- munificent 8y ago> I do think the argument for the check keyword instead of a ? operator like in Rust is quite weak. Mainly because a big advantage of the ? operator (apart from the brevity) is that it can be used in method calling chains. In Dart (as in a couple of other languages) we have an `await` keyword for asynchrony. It has the same problem you describe and it really is very painful. Code like this is not that uncommon: await (await (await foo).bar).baz); It sucks. I really wish the language team had gone with something postfix. For error-handling, I wouldn't be surprised if chaining like this was even more common.
- skybrian 8y agoWould it be better to use statements more? After all, this is a sequence of steps and it's doing a context switch between each step. That seems better written vertically, one per line.
- dom96 8y agoI'm not sure about Dart, but in my experience it's rare that you await a function which returns an object with another awaitable in it. Never mind something that nests these 2 levels deep.
- RodericDay 8y agofetch() + response.text() is usually two. add a user-defined third and you're there.
- reggieband 8y agoMaybe I'm crazy but I'd much prefer that as: const temp = await foo; const bar = await temp.bar; const baz = await bar.baz; But then again it looks like in this example we are returning promises from getters on objects which is something I would avoid.
- munificent 8y agoYeah, the guidance is usually to hoist the subexpressions out to variables like you do here. But I encounter it often enough that it feels like we are trying to paper over a poor language syntax. > we are returning promises from getters on objects which is something I would avoid. Getters are very common in Dart and it's idiomatic to use them even for properties that are asynchronous. (It's not like making it a method with an extra `()` really solves anything.) It's not as common to have nested chains of asynchronous properties, but they do happen, especially in tests.
- marcus_holmes 8y agoGo doesn't chain things much. I like that it doesn't. Verbosity can be a pain. But needlessly dense code is (imho) less readable. Having a single character that changes the entire context of a statement is not fun (I have the same problem with !). Reading a set of a dozen chained functions, some with ? and some without... that's not fun for anyone.
- JulianMorrison 8y agoIt looks like check can be used in calling chains. That is, if F is string to string,error, and G is empty to string,error then s := check F(check G()) either assigns a string to s, or calls the handler.
- barsonme 8y agoI hope this doesn't make it in. I'd hate to see the inevitable check (check A(check B(check C( ... ))))
- JulianMorrison 8y agoI wouldn't. It's very explicitly saying, this is a series of calls that can fail.