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.
OK, here's some error handling I wrote about a year ago, for an RSS feed reader. This code
calls lots of packages with their own error types. If anything fails, an error bubbles up.
Those had to be packed up into a single type for Rust's error handling. What's changed that would make this more concise?
pub type FeedResult<T> = Result<T, FeedError>; // return type, result or error
// A set of errors that can occur handling RSS or Atom feeds.
pub enum FeedError {
FeedIoError(io::Error), // error at the I/O level
FeedHTTPError(hyper::Error), // error at the HTTP level
FeedXMLParseError(xml::BuilderError), // error at the XML level
FeedDateParseError(chrono::format::ParseError), // error at the date parsing level
FeedUnknownFeedTypeError, // wrong feed type
FeedFieldError(String), // required RSS field missing
FeedWasHTMLError(String) // expected XML, got HTML
}
//
// Encapsulate errors from each of the lower level error types
//
impl convert::From<hyper::Error> for FeedError {
fn from(err: hyper::Error) -> FeedError {
FeedError::FeedHTTPError(err)
}
}
impl convert::From<io::Error> for FeedError {
fn from(err: io::Error) -> FeedError {
FeedError::FeedIoError(err)
}
}
(More of those are required, one for each enum value...)
(Then we need this.)
// Convert FeedError to text string.
impl fmt::Display for FeedError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&FeedError::FeedIoError(ref xerr) => xerr.fmt(f), // I/O error
&FeedError::FeedHTTPError(ref xerr) => xerr.fmt(f), // HTTP error
&FeedError::FeedXMLParseError(ref xerr) => xerr.fmt(f), // XML parse error
&FeedError::FeedDateParseError(ref xerr) => xerr.fmt(f), // Date parse error
&FeedError::FeedUnknownFeedTypeError => write!(f, "Unknown feed type."),
&FeedError::FeedFieldError(ref s) => write!(f, "Required field \"{}\" missing from RSS/Atom feed.", s),
&FeedError::FeedWasHTMLError(ref s) => write!(f, "Expected an RSS/ATOM feed but received a web page \"{}\".", s)
//_(ref xerr) => xerr.fmt(f) // would be convenient, but not allowed in Rust.
}
}
This sort of thing is why I like Python's exception hierarchy. If you catch the higher level errors,
you get all the lower level ones, too, without losing the info about what went wrong.
- Concise declaration of the enum for each wrapped error type (your first code block).
- Automatic impl of From for each wrapped error type (your first code block).
- A custom Result type (your first code block).
- Concise way to define description() functions for each enum variant, which get used for the Display impl (your second block). For "foreign_links" it would forward to the wrapped error's fmt automatically, so you only need to do this for the others.
- Bonuses like backtraces conditional on RUST_BACKTRACE=1
It is a macro that just helps you define a type that implements the appropriate traits so of course it works with the rest of the world. No-one outside the crate that uses it even needs to notice, as one could just write out the definition by hand.
This is correct. Technically the backtrace functionality and ErrorChainIter implementation come from the error-chain crate (the former as a re-export of the backtrace crate) so your linker will notice.
I think Python's exceptions work pretty well, but how do you make use of the hierarchy? It's not clear to me from this example.
I find myself using Python exceptions in two pretty disjoint cases:
1) try/catch around a single line, where the exception is as specific as possible (e.g. KeyError, etc.). To do something specific in the error case, perhaps decrement a retry count. (Although this is one of those places where "exceptions should be exceptional" comes to mind; I don't like "except" for normal control flow...)
2) try/catch around main(), where the exception is as general as possible. To log the exceptions, or perhaps print a usage message.
There doesn't seem to be much usage between those two extremes.
I recently read "The Design and Evolution of C++" by Stroustrop, and it was not obvious to them to use the class hierarchy for exceptions. I'm not sure exactly where the idea originated from. (He also talks about resume after exceptions, which sounds insane today, but was pretty heavily debated at the time.)
Go has no class hierarchy, and I believe the error handling is with an Error interface. Although I guess is does have some kind of subtyping by creating a more specific interface than Error by adding methods.
The class hierarchy for exceptions is a library standardization concept in Python. Exceptions were rather haphazard in early Python, but got organized around Python 2.6-2.7. All exceptions descend from "Exception". Usually, though, you don't want to catch that. If you do, it's usually to log the error or report it someplace before aborting the program.
"EnvironmentError" includes most of the things that can go wrong external to the program; it subsumes OSError and IOerror and all the networking/HTTP errors. Modules which do I/O or network operations should catch EnvironmentError and do something about it. Your GUI program or web service shouldn't abort for an EnvironmentError. Code can and should catch more specific errors at lower levels, but a backup exception handler for EnvironmentError is useful for when something that "never" fails does fail.
I have a moderately large Python system which reads and processes arbitrary web pages, trying to rate web sites. Such a program sees all the things that can go wrong with networking, HTTP, and HTML. Functions in standard packages functions sometimes raise unexpected exceptions. None of those events are abort conditions; they have to be dealt with as ordinary problems. Some errors require "try again", some require "try again later", and some require "never try again". Errors log to the database, not a log file, and some are reported on the rating page for a site. The point of all this is to have a system which requires very little human attention. It's been several years since I had to deal with a system failure manually.
In addition to libraries like quick error and error chain, you can just use coalesce your errors into `Box<Error>` instead of defining an enum like this.
"1.13 contains a serious bug in code generation for ARM targets using hardware floats (which is most ARM targets). ARM targets in Rust are presently in our 2nd support tier, so this bug was not determined to block the release. Because 1.13 contains a security update, users that must target ARM are encouraged to use the 1.14 betas, which will soon get a fix for ARM."
Rust looks great, I'm not using it but learning and it look fantastic.
I have one question, I guess off-topic: Rust uses LLVM, like Swift, Julia etc. Could these LLVM languages interoperate better because of this common foundation? Maybe link using LLVM IR instead of C ABI FFI, or something like that?
LLVM has so many little ways to configure its output, and unless two LLVM users configure the outputs to be compatible, they are unlikely to be. LLVM is more of a toolset with some language than a shared infrastructure.
They didn't necessarily actually contribute in that time though, presumably the list is of people who's contributions were merged into the release branch (or master during that timeframe, or... according to whatever workflow they're using).
Not to detract from your point though. It's an awesome language, I'm just learning it but enthusiastically so for sure!
Rust uses the train release model which releases every 6 weeks [1]. It's definitely possible for someone to have an open PR for a year that's not in this list. I'm pretty sure this list is generated by running `git log --format='%aN' 1.12.0..1.13.0 | sort -u`.
I think what's going on here is a rather specific misinterpretation on one or both your parts of what "contributed" means in this context.
If contributed is interpreted to mean "code was merged" not "work was done" then the period is a fairly static 6 weeks, but there's really no reason to be astonished about how many people were involved, as it could contain work from multiple months ago.
If contributed is interpreted to mean "work was done" then the number of people that contributed in this period compared to some other will have more meaning.
I think OJFord is working from the second interpretation.
Yes, it means "code was merged." (Though it's not a particularly large number of contributors, the last release had 176, and the release before that 126.)
This is actually something I'm kinda sad about; I want to get better at representing work in a broader way. For example, get a patch into Cargo, but not rustc? You're not on this list. I don't like it. Working on it though...
No, I'm assuming that the number of contributors is the number of people who's contributions - code - was merged into this release.
I'm disputing the six weeks figure by assuming exactly what you outline above - that 'work done' is different from 'code merged' - so contributors may well have produced the work over a period of a year or more, and it just happens that those contributions came together for this release
Anyway, it's not really important, I didn't mean for this to descend so far - regardless of the time frame it's fantastic that so many are contributing to the project :)
My point is just that they may have produced the patch outside of the six week period. When it makes the release is somewhat arbitrary, and out of the contributor's control.
Regardless, as I said below, it's great that so many people are contributing whatever the time frame :)
Well, it's concise. The main problem is that the error action is "return" - you have to bail out of the entire function.
What happens if you use "?" within a lambda? Does it bail out of just the lambda, or the entire function? What about nested functions? (Can you have a named nested function which can access its outer scope? This StackOverflow post [1] says no, but may be wrong or obsolete.)
If this works with nested block structure, it's more useful. Having
a "match" for Err/Some around a lambda with with lots of "?" clauses would be useful. That provides a way to get the error from some complicated chain without leaving the function.
The semantics are very straightforward: `?` will return as per the `return` keyword. So if you use it in a lambda, it will return from just the lambda, like returns in lambdas always do (Rust hasn't hewed to Tennent's Correspondence Principle for a long time now :P ).
What you describe as "a match around a lambda" is the `catch` expression, which will catch `?`s thrown within it and allow you to match over them. This syntax has gone through RFC but its not implemented yet.
Of course for now you can create this control flow with `match (|| { block }) { }` as you describe.
It gets used most to enable returning from the outer function while within loop-body-like procs. Rust iterators used to be like that too (pre-1.0), but they switched to the current style to get rid of that complexity.
It seems like a fairly common case for a Rust function to encounter multiple types of error when it runs. My only solution to this is to return a custom error type, but this means manually matching rather than using something like try! or ?.
Can someone with more experience than I tell me if there is a simpler approach?
If your function returns Result<T, Box<Error>>, then you can use try! or ? operator to return any kind of error. It will be automatically boxed and converted into the generic Box<Error>.
This works for two reasons:
1) try! and ? don't simply return Err(err) on error case as one might think. They actually return Err(From::from(err)).
2) The standard library has: impl<'a, E: Error + 'a> From<E> for Box<Error + 'a> { ... }
Another solution I hadn't considered is to simply impl std::convert::From<SomeError> for your custom error type for each SomeError that might be encountered.
Excuse me if this is a stupid question, but when I downloaded the official Mac installer, it's not signed by anyone. I thought in this age we've all recognized that downloaded software should be signed, whether it's using GPG or Apple's signature mechanism? Is there a reason the Rust team decides not to sign their binaries?
Because rust is statically typed, and "x" has to have a concrete type known at compile time?
Maybe there's a use case for matching trait objects against types to get back the original type. But, I think you could do the same thing by adding a trait method that returns Option<T> for some desired type T. If the object wasn't that type, it could just return None.
Rust has static types with generics and algebraic data types. 99% of the use cases for that get covered. It has trait objects for when you need runtime type information, but they're used very rarely and you can make that pattern work with a chain of `downcast_ref` if lets or obviate its need by implementing methods.
I'm in no way proficient but I am a TA for an entry level OOP course at an UC. It took me 2 weeks to read through the major topics in Rust. And another month to get a good hold by gradually writing bunch of code.
Rust doesn't want to have exceptions; it prefers return value based error handling ("monadic error handling", specifically). A lot of people seem to be under the impression that Rust's error handling model is a way of emulating exceptions -- it's not; it's a different model, and we don't want to emulate exceptions. The fact that error propagation is a common task doesn't mean that Rust's way of providing it is done to emulate exceptions.
------
In a sense, every part of Rust's error handling model is "necessary because Rust doesn't have exceptions". Similarly, every part of Java's error handling model is "necessary because Java makes it hard to do monadic error handling". Saying "X is only necessary because you don't have Y" when X is an alternative to Y isn't a very useful statement.
> something an exception system does for you for free.
That's the point, it doesn't, you pay for the exception system and the implicitness that comes with it. Your comment portrays exceptions as the default that monadic errors are playing catch-up with. I could easily do the reverse.
> is a workaround to make it less of a pain.
You say workaround, I say feature.
Ultimately, monadic errors and exceptions have tradeoffs.
Like I said, given any tradeoff between feature A and feature B, you can always say "subfeature X of A exists because we don't have B". It's not a meaningful thing to say.
I didn't avoid an answer, I genuinely think that your comment has no substance to it because it pretends that exceptions are a "default" system.
I consider "not having exceptions" to be a feature that the ? operator helps to enable. Go also doesn't have exceptions, but they don't have the ? operator, which I find to be a little repetitive while coding. My fingers are outrageously good at:
if err != nil {
return err
}
Comparing Rust to a language with exceptions, like Java, the only difference is that they places that the code can return is explicit rather than implicit, because of that one extra character. I find this extremely pragmatic since most of the time errors just need to be propagated up to a place where they can be handled, but you still need to know when that may happen.
But technically you're doing this wrong, since you should be gift-wrapping your errors in Go. So either you have to be a human exception handler, or god help you should you need to figure out where an error actually occurred.