3 ms·
My comment wasn't meant as a criticism of Rust. Rust's trait system does the heavy lifting for enabling this operator (and all kinds of sugar, for that matter),
by weberc2 8y ago
My comment wasn't meant as a criticism of Rust. Rust's trait system does the heavy lifting for enabling this operator (and all kinds of sugar, for that matter), so this makes sense for Rust. Go doesn't have a trait system (though it could well be a useful addition!) or a macro system, so the alternative is to treat errors as a special type instead of plain-old-data as they are today.
- masklinn 8y ago> the alternative is to treat errors as a special type instead of plain-old-data as they are today. They're already a special type to an extent, since Go's MRV are not first-class it could add builtin facilities applicable to any 1..n-ary return value whose last value is either an Error or an Error-implementing struct or somesuch.
- weberc2 8y ago> They're already a special type to an extent This is news to me. How so? > since Go's MRV are not first-class it could add builtin facilities applicable to any 1..n-ary return value whose last value is either an Error or an Error-implementing struct or somesuch. MRV is "first-class" for the purposes of this discussion. Your proposal still depends on treating `error` as a special type, so my criticisms still apply--the juice just isn't worth the squeeze. That said, if another mechanism is introduced (for example, a trait-like system for hanging sugar off of a la Rust's), then the calculus changes; however, as previously mentioned, error handling sugar is not a good reason to add such a system.
- masklinn 8y ago> MRV is "first-class" for the purposes of this discussion. MRV is not first-class for any purpose. > Your proposal still depends on treating `error` as a special type, so my criticisms still apply--the juice just isn't worth the squeeze. That said, if another mechanism is introduced There is no need for "an other mechanism" since as you yourself note the only need is for the compiler to understand the existence of error, which would require no change to userland code or break any existing. Hell, technically and if you want a lowest denominator that isn't even necessary, you can hang it off of the MRV itself (which is very much a built-in special-purpose not-first-class construct) and have syntactic sugar for special-handling of the last return value, irrespective of its actual type. > for example, a trait-like system for hanging sugar off of a la Rust's try! and ? don't "hang off of" a trait, they work specifically on `Result`[0]. They only use traits for the sub-feature of automatic conversion of the error value, which IME is only a small and non-necessary part of the feature: usually you either return the error as-is or need to add context beyond what can be extracted from the original. [0] the Try trait aims to eventually allow its use beyond these, but is currently nightly-only
- weberc2 8y ago> MRV is not first-class for any purpose. You're going to have to elaborate because MRV as it exists today is sufficient for your proposal. If you mean "you're returning something that looks like a tuple, but you can't use it like a tuple" then that's all true but I don't see how that's necessary to implement your propsal. > There is no need for "an other mechanism" since as you yourself note the only need is for the compiler to understand the existence of error, which would require no change to userland code or break any existing. I agree that this is true, but I disagree that it's advisable to make `error` a special type to the compiler. > Hell, technically and if you want a lowest denominator that isn't even necessary, you can hang it off of the MRV itself (which is very much a built-in special-purpose not-first-class construct) and have syntactic sugar for special-handling of the last return value, irrespective of its actual type. Sure, but still a bad idea. > try! and ? don't "hang off of" a trait, they work specifically on `Result`[0]. They only use traits for the sub-feature of automatic conversion of the error value, which IME is only a small and non-necessary part of the feature: usually you either return the error as-is or need to add context beyond what can be extracted from the original. I was referring to the Try trait, but as you mention, it's only available on nightly. Nevertheless, I object to making the error type special to the compiler for the negligible advantage of eliding some boilerplate. If this is facilitated by some more general mechanism (e.g., monads or the Try trait), then so be it. Do note that this is my opinion, and you're free to disagree with it.
- masklinn 8y ago> You're going to have to elaborate because MRV as it exists today is sufficient for your proposal. If you mean "you're returning something that looks like a tuple, but you can't use it like a tuple" then that's all true but I don't see how that's necessary to implement your propsal. First-class features can be manipulated as regular values of the language. That Go's MRV are not first-class are an advantage with respect to my proposal: they're already a "magical" built-in which can't be manipulated from within go. As a result, new features built on MRV can't conflict with existing manipulations of MRV. > I was referring to the Try trait, but as you mention, it's only available on nightly. And a very recent addition: `try!` does not use it and I've not seen any plan to retrofit it. Try is a way to extend ? to non-Result structures. ? is not built upon Try, Try is being extracted from ?.
- jashmatthews 8y ago> Rust's trait system does the heavy lifting for enabling this operator Are you sure? I thought that try! was a simple macro: https://doc.rust-lang.org/src/core/macros.rs.html#299 https://doc.rust-lang.org/src/core/macros.rs.html#299
- setr 8y ago?[1] is indeed based on traits and try! is a macro that depends on the trait system (line 303: $crate::convert::From::from(err)) to do the very important job of propagation and coercion, by means of error-wrapping. If all it could do was pass the error forward, it'd be quite annoying to deal with. Specifically that since rust represents errors with enums, and you have to handle every error, the eventual caller function would end up dealing with 10 different possible errors, defined in 10 different libraries, and has to be made aware of all downstream dependencies. Or you'd end up avoiding the operator altogether, and force the coercion in every instance, defeating the point of the operator. The ?/try! utilizes traits to instead allows you to auto-coerce them to a new error with a relevant message/trace (given the appropriate Into definition), allowing it to be a simple one-character addition with most of the expressive power of exceptions. The heavy lifting is specifically that coercion; the "continue or return" part isn't that significant (that would just be avoiding the any language one-liner: if err return err) [1] https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md https://github.com/rust-lang/rfcs/blob/master/text/0243-trai...
- masklinn 8y ago> The ?/try! utilizes traits to instead allows you to auto-coerce them to a new error with a relevant message/trace (given the appropriate Into definition), allowing it to be a simple one-character addition with most of the expressive power of exceptions. That is not a very important job, because it's a context-less conversion and thus only suitable for pretty basic conversions. > the "continue or return" part isn't that significant (that would just be avoiding the any language one-liner: if err return err) "continue or return" is by far the most important part of ?/try!, it reduces an extensive match into a single character or 6, making error handling both terse and simple for the common case where you just want to bubble up the error. Even if it did not do any conversion, such conversion is just a map_err away (and commonly necessary either way as the developer wants to add context or needs to convert from trait object or some such). The automatic conversion is a minor convenience.