Rust doesn't have exceptions, for many reasons. One is non-locality of exceptions. Even gotos but bounded by a function. Exceptions, however...those can take you anywhere.
But the other side, as you point out, is that exception many common patterns are much made more succient.
>One is non-locality of exceptions. Even gotos but bounded by a function. Exceptions, however...those can take you anywhere.
I've never understood this argument.
Where does `try!()` take you if there's an error? To the caller. If the caller also has a `try!()`, where does that take you? To the caller's caller.
Where does `throw new FooException()` take you? To the caller. If the caller doesn't have a `catch(FooException)`, where does that take you? To the caller's caller.
Edit: Note to commenters: I'm not arguing about explicit vs implicit returns. I do agree that explicit returns are nicer to read and reduce the surface area of a language. My comment is solely for the "Exceptions can take you anywhere" sentiment.
Explicit vs implicit basically. You don't get to ignore the error. If the caller does have a try, it propagates. If the caller doesn't have a catch, it propagates.
This becomes important because now your code no longer has invisible escape hatches 20 levels deep. You design your function with specific points where it may stop executing. You control when your function returns.
You can also store the error value and handle it later (Servo makes use of this pattern often -- e.g. a network request with a body of Err, which needs to be handled when we try to read the body).
> Where does `try!()` take you if there's an error? To the caller. If the caller also has a `try!()`, where does that take you? To the caller's caller.
You can say this about return, too. returns and try both take you to the caller. The caller then bubbles you up, because it explicitly returns or tries.
I LOVE this about Rust. Whenever I'm using another library, if a function I'm calling returns a Result, I know that I need to handle an error condition. With exceptions, you never really know if the code you're calling throws exceptions or not. The explicitness of "yes, this function may return an error condition" is really nice. And if you really don't give a damn about errors, you can just `.unwrap()` everything.
I think this is the most meaningful and valuable thing about it: `Result` gives you a type-level indication of the need for error handling, but in a manner that is sufficiently polymorphic and friendly to handle that it doesn't end up like Java's checked exceptions (which could be seen as a special sub-system of the type system).
The problem with checked exceptions in Java is that many don't design them properly, not everything needs to extend Exception, there is a reason why RuntimeException is there since the early days.
Yes, even the JDK is to blame for misusing checked exceptions.
You can smash ahead with unwrap()s and get something that works. And then when you want to make it reliable, you know where to look. It's like the language forces me to put in those "// TODO"s my code would otherwise be full of..
There are other good comments here, but the use case where I find it most important is in lambdas. What do you do when a lambda throws an exception in the processing loop of an iterator?
I think that will compile. The `to_something()` might return an error, the important thing is that there is no exception and allows for clean error handling. In this case we're just ignoring errors. With exception handling, while it's not impossible to do the exact same thing, I believe this is cleaner (and yes, there are other ways to write this).
This is not correct. The to_something() is what throws the exception. In those languages, you need to put the try block inside the iterator's map lambda.
Again, I didn't say it was not possible in the other languages, I only stated that I didn't think it ends up being as clean (specifically in regards to C++, Java and C#)
In most languages, it is easy to write a 'try(expr, fail)' HOF that gives you the same functionality (but possibly with different performance profiles, depending on the underlying exception handling implementation).
On the other hand without non-local error handling it is harder to bail out of the outer map on a to_something error, if that's what's desired.
In Lisp the "catch" part could be "handler-bind", which could invoke a restart (skip element, or use-value). This requires the called function to establish a restart, but the good news is that it removes the burden of the decision from the innermost part.
What we are debating at this point is style. I offered something which I think is cleaner than what I see in other languages.
You don't see the point, and that's fine. Initially when Java came out with Streams I hadn't worked a lot with ETL like functions. Then while with Rust I really got it, so now I want to apply that practice to code in Java, because it simplifies many areas of code. One thing that became very clear, is that Exception handling throws a minor monkey wrench into keeping the code simple. This is why I picked the example I showed.
You don't think it's cleaner, or makes a big difference, and that's fine. I personally find it to be easier to read and create more understandable code. You can have your exceptions, but after Rust I am totally sold on the Result for errors pardigm.
I don't think it is cleaner, because usually we architect our applications in logical blocks.
For example when sending a data packet over the network and it fails, I don't care if the error was in the communication, buffer, socket level or network layer, just that the packet could not be sent.
That is what we need to retry, sending a new packet, not the lower level operations.
Languages with exceptions, which I use since 1993, allow me to choose using an exception, transform it into a plain error code or monadic error. I can have it all and choose which flavour I want to use, depending on the use case.
Yeah there's a good number of people who just don't want exception handling and stack unwinding code in their binaries. If Rust is going to be a usable systems language, it at least needs to placate the needs of systems developers.
Except it's treated as a normal return and could be written that way. There is no stack magic happening.
The return value is a Result, which is an enum and must be handled in a match or other construct, or explicitly ignored with an .ok or .expect (that may be for Option only) which result in a panic if not successful.
The argument only applies to unchecked exceptions because those can happen from anywhere without being mentioned by the calling code. It's easy to sneak through a layer of abstraction since the bubbling is implicit.
The `try!()` in this case is analogous to checked exceptions. It makes the bubbling explicit.
In my view non-locality manifests with wrappers when you call wrapper function, which in turn calls wrappee (how do you call it?), which throws/excepts/whatevers but the wrapper does not catch it or merely logs and rethrows it: now you get return from wrappee directly to the caller. Or in other words: "Exceptions can take you anywhere".
This is a huge problem with wrappers in general that signature of wrappee becomes part of wrapper signature. Since exceptions are part of function signature they make the problem worse. And since most if not all languages do not enforce exceptions in signature there is literally no way to know what will be thrown if runtime polymorphism is used. Which results in situations where querying REST endpoint written in a language we all love to hate returns trace in the body, ooops :)
There will be exceptions I don't know about, and that's fine.
I let go the desire to fully know the set of all exceptions that can be thrown. Instead, I weaken my assumptions and code as if everything could break, and that makes code quite robust. For example, I try to arrange the code so that it is transactional, which can be easy if the approach is functional. Otherwise, places that must be protected get a catch-all handler, etc.
Checked exceptions probably act more like ad-hoc enums since you've got to handle each kind of exception that can be thrown, but I'm sure that could be encoded in nested results too.
try!() is a macro. Hence all it does is take a given expression and expand the whole thing into a if-error-then-return block. I guess you say that it's an explicit return under the hood.
Foo.Bar.Baz;
exception when Program_Error => Put ("WHoops");
when Constraint_Error => Put ("Dang");
when Gaim_OVer_Main => Put ("??? D: ");
when others => Put ("IDC...");
True, though in some cases you may want to write that one as:
foo().and_then(bar).and_then(baz)
That becomes a bit more verbose, but it reads left-to-right, and works better if you want to use a closure as one of the functions. Which form looks more readable in a given circumstance may depend on the nature of foo, bar, and baz.
Where you found this expect() function and how it will receive error, generated by baz? I read Rust documentation about error handling multiple times, but newer saw adequate error handling.
The best solution I know is error-chain library:
use errors::ChainErr;
try!(do_something().chain_err(|| "Something went wrong"));
If you found incorrect question in exam, then it is a bug.
If you answered all questions but wrongly, then it is a failure.
If you answered correctly to all answers but examiner said that you failed, then it is an error.
In case of bug, code must panic.
In case of failure, code must return a result, which clearly says that this is the failure. Try next time.
In case of error, code must print informative description of error, bunch of data, and maybe some hints to operator, so he will be able to fix it.
We are talking about errors, right? Why I need conversions to display bunch of strings with descriptions and variables to an operator? Just let me collect strings and print them to a log.
> Just let me collect strings and print them to a log.
If that's something you want to do, that's quite easy. Vec<String> is your error type. Done.
But if you want to build something a bit richer, for example, to differentiate between the case in which you answered a question wrong, or when the examiner fails you, you could do that as well. It all depends.
But errors as values is what allows you to have that flexibility; it's easily extensible.
Has anyone suggested some sort of postfix macro syntax? I don't have one in mind, but perhaps there is some nice looking, viable one. That might have been a more general solution than a new operator.
> Has anyone suggested some sort of postfix macro syntax?
Yes, numerous people. A few different discussions have gone by; it has many potential applications, but it introduces several aspects of syntactic weirdness and parsing weirdness, so it'd require a lot of care to introduce if at all.
You often won't need to change the error object from what foo() returns; Rust errors are generally structs or enums, rather than non-machine-readable strings. But if you do need to change it, it looks like this:
go errors are generally backed by structs as well (though not necessarily), even `fmt.Errorf("some error")` is a struct (called `errorString`).
`Error() string` is just the interface that things that want to be errors must implement, which ideally gives you the human readable version of the error.
In Rust, I think it's more idiomatic to just implement the `Display` trait on you error type so that you can generate the string when you need it instead of at the time the error occurs
If you want something like this, you'd usually use your own error type to wrap the errors returned from the functions you call. You can implicitly do the conversions and keep the code the same if you implement `From` or `Into` for the wrapped error type.
`?` will convert the original error to the one the caller returns so you can implement the decoration in the conversion if that's suitable, otherwise `Result#map_err` lets you convert an error "at the callsite" (and is a noop if there was no error). Alternatively, you can use error_chain: https://brson.github.io/error-chain/error_chain/index.html.
This is superficially nice, but let's not forget that we would hardly do this in practice. It's much more likely that we would write your Go version with some error handling or logging messages added in.
Practical concerns aside, in Haskell you get these things from the Maybe monad without extra syntax or macros:
I recently started checking out Rust and was quickly disappointed that it's near impossible to chain anything unless their signatures match perfectly. Subsequently, I found the `mdo` crate[0] to be rather useful, although I'm not sure how idiomatic it is. It's essentially a macro that implements Haskell's do-notation. Here's a snippet from the README:
let l = mdo! {
z =<< 1i32..11;
x =<< 1..z;
y =<< x..z;
when x * x + y * y == z * z;
ret ret((x, y, z))
}.collect::<Vec<_>>();
I think this would actually be more comparable to the Either monad. Also, "without extra syntax or macros" is kind of a trivial distinction, as `>>=` is just a function, not some sort of magic.
That being said, I really wish there were some sort of equivalent of `try` or `?` for options in Rust. I don't feel like I want monads in Rust in general, but when dealing with options, I definitely miss the Maybe monad.
I guess I'm not seeing why having a function rather than a macro is really that much of a distinction in this case. Either way, it's essentially just a namespaced block of code that compiles down to an AST fragment; I don't think the facts that one of them uses the stack and that they're compiled at slightly different times really make much of a difference here.
I usually just implement a `try_option` macro, which works well enough. I just wish it were a built-in thing in the language, as I don't like having to implement it for every new project, but I really don't want to include an external crate just for one macro.
While it may be convenient, it's truly worrying such (EDIT: was "many") syntactic special cases are going into Rust at this point.
(It may be the case that these things absolutely cannot be handled in a systematic way in a no-GC/no-runtime language, but I'd really like to see some evidence that this is the case.)
I can only think of one redundant feature, and that is the ? operator. This is because `try!` gets used very often in in a lot of code.
I can only think of one syntactic feature (redundant or otherwise) that doesn't get used often and that is HRTB(`for<'a>`), which needs to be there to be able to specify some types correctly (but is very rare).
Almost every control flow mechanism in Rust is redundant: `if (let)`, `while (let)`, and `for` are all encodable with loop/match/break. Even `return` and `continue` are arguably redundant. Control flow is also probably the biggest source of redundant functions (map, and_then, or_else, unwrap, etc...).
In this sense, ? is following in a long tradition.
If you have enough abstraction capability (HKTs, for example), try! is also redundant... and you can instantly generalize to anything that isn't necessarily a `Result` but has the same general structure.
By special-casing now, one almost-certainly[1] closes the opportunity for future consolidation.
[1] I say almost because it's not necessarily a done deal, but migration gets exponentially harder the longer a language has been "out in the wild". GHC/Haskell has gone through a similar upheaval in the last 1-2 years and it wasn't pretty... but ultimately it seems to at least have survived, so there's that.
> and you can instantly generalize to anything that isn't necessarily a `Result` but has the same general structure.
>
> closes the opportunity
The door isn't closed for this, `?` works with a "carrier" trait, which lets you extend the operator. Now, it's currently unstable, so right now you can't use it, but the door is wide open for the design of `Carrier` to be finalized.
This was explicitly discussed and thought out before ? was stabilized. It's just that ? was stabilized before Carrier.
The compiler knows nothing about `Result`. There are very few structs that it knows anything about, and these all have to do with atomics or `UnsafeCell` or other intrinsic-based primitives that you don't need to reimplement out-of-tree anyway. It knows a few traits, like Add and Copy and Carrier, which can be used to extend operators and stuff.
> The door isn't closed for this, `?` works with a "carrier" trait, which lets you extend the operator. Now, it's currently unstable, so right now you can't use it, but the door is wide open for the design of `Carrier` to be finalized.
Oh, OK that at least sound like sound "defensive design" -- good to hear :). However, what happens if `Carrier` needs to be radically redesigned, e.g. to account for HKTs or Rank-N types (or something similarly 'crazy').
Carrier itself is unstable, so it can be redesigned into whatever it wants to be. The only restriction is that it should continue to work with Results the way it does now, and that's an easy thing to maintain. It could be made into an HKT trait thingymajig. It will probably just be a trait with two associated types, this is very close to HKT anyway.
While this is true, it's not clear if Rust will ever get the stuff needed for do notation, some believe that it's impossible. And people need to reduce their error-handling boilerplate today.
Rust will certainly have warts. Maybe someday in the future we'll feel like this is one of them, maybe not. :)
> it's not clear if Rust will ever get the stuff needed for do notation, some believe that it's impossible
True, and I think I acknowledged as much in my original comment.
> And people need to reduce their error-handling boilerplate today.
Indeed, I'm just (vaguely) worried about Rust being painted into a corner. As I said in my OP, it may actually be an unavoidable corner -- I hope not, but as you say... I guess we'll see :).
To the contrary of what you're suggesting, I think Rust has done a great job eliminating a lot of the previous special-cased syntax and rebuilding things upon a simplified set of core primitives.
What worries you about it? What things need to be "handled in a systematic way"? Are you just generally opposed to any new syntax in languages or something?
As a Go developer, I'm curious how is the error handled when foo() or bar() fail?
Could you show an example where `bar()` returns a re-triable error or a fatal error etc? something where I have to make a decision other than returning it up the call stack?.
> As a Go developer, I'm curious how is the error handled when foo() or bar() fail?
By converting their error to whatever the enclosing function returns, and returning.
> Could you show an example where `bar()` returns a re-triable error or a fatal error etc?
You'd use either one of the various combinators[0] or a full explicit `match` (which is what try!, ? and the combinators end up desugaring to). So let's say you wanted special handling for bar()'s result, you'd specially handle that one:
let a = foo()?;
let b = match a.bar() {
Ok(value) => {
// the call succeeded
}
Err(error) => {
// the call failed
}
}
etc…
If you want bar() to be a fatal error, you can unwrap(), that desugars to:
match v {
Ok(v) => v,
Err(err) => panic!("called `Result::unwrap()` on an `Err` value: {:?}", err)
}
that is it panics on failure and unwraps the value on success (there's also an `unwrap_err` which does the opposite)
The ? operator is a blanket "return from the current function if error, otherwise produce successful value." If you wanted to have conditional logic to handle the error within the current function, you'd use a match:
let foo_ok = foo()?;
let bar_ok = match foo_ok.bar() {
Ok(bar_ok) => {} // conditional logic here
Err(bar_err) => return Err(bar_err),
};
bar_ok.baz()
If you need more complex logic, you simply wouldn't use `try!()`. Try! is intended for those `if error then return` checks, just like with Go's `if err != nil { return err }`.
If the error needs to be checked for a specific type (like a timeout) and then retried X times, you don't use the simple `if err != nil {...}`, you hand write something:
let barRes = try!(foo()).bar();
match barRes {
Err(MyError::Timeout(_)) => // .. some retry code
}
Note that the above code is woefully incomplete (i'm not checking all possible match values, for example), but it should give you the gist of the idea.
edit: Man, the guy (or wo-guy) has a problem and we all pounce :)
When they fail, it's like "return err"; that is, they just return the error value.
You can only make it return up the call stack, that's it. It's for recoverable errors, not fatal ones. And the calling function would check for the error case and re-try if that's the behavior it wants.
So do all the functions have to have the same error type as the function that is calling them? I don't know rust, but in haskell notation, if a function returns "Either x y" would it then only be able to use the "?" notation on functions that themselves return "Either x z", and not on functions that return "Either a b" (so, failure sides have to match, but success sides are flexible)?
> So do all the functions have to have the same error type as the function that is calling them?
Not quite, they must have an error type convertible to the caller's error type, try! (and ?) really desugar to
match val {
Ok(v) => v,
Err(e) => return Err(From::from(e))
}
From is a generic conversion trait, a type A can implement From<B> in which case `From::from(b: B)` will yield an A (assuming A is either inferred or explicitly requested)
the trait bound `MyError: std::convert::From<std::io::Error>` is not satisfied
So by implementing it...
use std::convert::From;
use std::io;
impl From<io::Error> for MyError {
fn from(e: io::Error) -> MyError {
// do some sort of conversion
MyError::OhNo(String::from("some error"))
}
}
This is somehow less clear, and somehow "more debuggable" than
a, err := foo()
if err != nil {
nil, err
}
b, err := a.bar()
if err != nil {
nil, err
}
c, err := b.baz()
if err != nil {
nil, err
}
return c, nil
One of these obviously has no bugs. There is literally nothing to debug. The other has no obvious bugs.
Oh, except look again, I accidentally forgot to return from those error-handling-statements and now I have what should have been a completely avoidable bug.
let a = foo()?;
let b = a.bar()?;
let c = b.baz()?;
if that's what you want, not sure how the Go version would be easier to debug beyond that, and you can easily perform that transformation when you actually need to debug that code if the "pointfree" version hinders that.
If you want to set a breakpoint on the error then I guess that's not possible in your form. I don't know what ? really does but I guess it somehow short-circuits the current block in the error form and returns the error.
I also sometimes include log statements in specific error branches for debugging and delete them afterwards. That's trivially possible in the Go version but requires more effort in the shorter variants (e.g. also if the funcitons throw exceptions instead of returning errors in other languages).
Because you would rather pay the ongoing cost of writing (and worse, having to read) thousands of pointless `if err != nil` blocks than take three minutes to see exactly what try! does, which is (almost) exactly the Rust equivalent of just that.
> That's trivially possible in the Go version
It's also trivially possible in the Rust version, except you don't pay a ludicrous cost in readability of the logic flow in the thousands of other places you have to return an error.
>I also sometimes include log statements in specific error branches for debugging and delete them afterwards. That's trivially possible in the Go version but requires more effort in the shorter variants
> It's really nothing special, `a?` simply expands to:
Ok, that's what I expected
> ?
I meant if I have foo().bar() in C# or Java and foo() throws it's equally hard to change the code to add error handling in between since I would need to add a try/catch clause and rethrow the error.
It's not really a mistake. Just coding preference. For example: do_A()?.do_B()?.do_C()?. With the one line statement like that, when weird things show up, I don't really know if it's caused by bugs in do_A, do_B, or do_C. The linear layout of Go code lets me go through each do_X call line by line and setting break points is also easier. The problem here is when I need to investigate the chain do_A()?.do_B()?.do_C()?., I may have to rewrite that statement.
I agree that we should have good debugging tools for this (in particular, break on error return). But note that you can break on function calls and use "finish" to effectively go call by call to see where the error is today.
But consider that the Rust does not have null, so every case where you need to step over the code and see if it does the right thing when value is null does not need debugging!
The only thing bad about Go's version (other than the usual repetitive code complaints) is it's easy to ignore (ie forget) to handle an error.
And really much of the error handling in Go is by convention (ie, `if err != nil { return nil, err }`) rather than actual language semantics.
Honestly it would be nice to be able to always return the error (without special macros or symbols) if there is one unless I've explicitly setup a handler block.
I've also found (the hard way) that it's possible to mistype a conditional as `err == nil` when it should be `err != nil`. It's a really dumb mistake, but will waste time as you track it down (without the compiler's help).
Nothing keeping you from using the same approach(or even some of the really nice mapping functions), it just lets you get rid of the boilerplate that you see there when you want to.
I don't think it's fair to say that the option Rust gives you is more complex. It's a syntactic convenience which reduces the chance of a someone typing a mistake in the longer form.
Regardless of how simple the language, there is always more than one way to do things. Rust is just giving you the choice to eliminate a lot of boilerplate if you so desire. In reality most Go users will opt to use the style mentioned above instead of something more verbose/complex and most Rust users will likely opt for the new ? operator.
In the Rust one, there's no code to read. How is this somehow less readable than the Go version, where there's dozens of lines of code to read that have nothing to do with the task you're trying to accomplish, that are all completely identical?
I don't get why they'll let the language be abstracted in that way, but are against having ternary operators. It's the one thing that's somewhat bugged me about Rust. Aside from this one minor thing, I"ve been really loving the language itself.
let greater = a < b ? b : a;
Is a lot better than having to write this all the time:
let greater = if a < b { return a; } else { return b; };
The whole point of it would be to have a higher level of abstraction which makes codebases more concise. Avoiding duplication for the sake of avoiding it is as nonsensical as avoiding 100 dollar bills since we already have 100 single dollar bills that could make it up.
> The whole point of it would be to have a higher level of abstraction
What are you talking about? A ternary isn't a higher level of abstraction, it's at best the exact same thing with a different syntax, at worst it's a more limited version of the same.
Ditch the returns and semicolons in the latter, then if is your ternary op, just more readable in cases where a and b are expressions more complex than in this example.