Announcing Rust 1.13
blog.rust-lang.org
blog.rust-lang.org
try!(try!(try!(foo()).bar()).baz())
Becomes foo()?.bar()?.baz()?
Versus the Go equivalent: a, err := foo()
if err != nil {
return nil, err
}
b, err := a.bar()
if err != nil {
return nil, err
}
c, err := b.baz()
if err != nil {
return nil, err
}
return c try:
foo().bar().baz()
except Exception as message:
return messageRust 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.
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.
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.
Yes, even the JDK is to blame for misusing checked exceptions.
some_vec.iter().filter_map(|i|
i.to_something()
.map_err(|e|None)
.map(|i|Some(i)))
.collect()
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). try_block
some_vec.iter().filter_map(|i|
i.to_something()
.map_err(|e|None)
.map(|i|Some(i)))
.collect()
catch_exceptionAgain, 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#)
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.
Either the full expression is successful or not.
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.
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.
But as you say, it is a matter of style.
Raising an exception does not. It's akin to a `return` and `goto` all in one.
---
I don't mean to criticize Python's choice.
I think exceptions are a good decision for a high-level, dynamically typed, scripting language.
I do not think exceptions are a good decision for statically typed systems language.
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 `try!()` in this case is analogous to checked exceptions. It makes the bubbling explicit.
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.
The trick is "Exceptions" are more than just "Checked Exceptions"
The Rust equivalent would be something like:
`Result<_, Box<Error>>`
The thing is that exceptions do not require the manual CPS transform required to handle Result objects, which results in nicer code.
Even with syntactic sugar like do notation, the syntax is still different from 'normal' code.
Also the escape hatch to unchecked exceptions is convenient too (then again Rust has some form of it already).
Foo.Bar.Baz;
exception when Program_Error => Put ("WHoops");
when Constraint_Error => Put ("Dang");
when Gaim_OVer_Main => Put ("??? D: ");
when others => Put ("IDC...");foo().bar().baz()
ie, let someone higher up the stack handle the error.
try!(baz(try!(bar(try!(foo())))
into baz(bar(foo()?)?)?
Which isn't as bad as the alternating order for chained evaluation, but cleans up a lot of the noise. 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. baz(foo)?("Cannot do baz, foo={}", foo).bar()?"Cannot do bar";
Is there a syntax sugar for such thing? If so, I will be converted to Rust. baz(foo).expect(format!("Cannot do baz, foo={}", foo)).bar().expect("Cannot do bar");The best solution I know is error-chain library:
use errors::ChainErr;
try!(do_something().chain_err(|| "Something went wrong")); [reports.rs:25] ERROR: Cannot write report "Foo" to file "foo.xml".
[templates.rs:125] ERROR: Cannot process template "header". Check is template file exists and readable.
[fileio.rs:45] ERROR: Cannot open file "/usr/share/foo/templates/header.tmpl": unable to enter directory "/usr/share/foo". Check directory permissions.
Instead of writing report cannot open file done
It saves lot of time(money) in production.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.
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.
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.
package main
import (
"fmt"
"io"
"os"
"github.com/pkg/errors"
)
func main() {
v, err := run()
if err != nil {
fmt.Printf("err=%+v\n", err)
os.Exit(1)
}
fmt.Printf("v=%d\n", v)
}
func run() (*int, error) {
a, err := foo()
if err != nil {
return nil, errors.WithStack(err)
}
b, err := a.bar()
if err != nil {
return nil, errors.WithStack(err)
}
c, err := b.baz()
if err != nil {
return nil, errors.WithStack(err)
}
return c, nil
}
func foo() (*a, error) {
return new(a), nil
}
type a struct{}
func (a *a) bar() (*b, error) {
return new(b), nil
}
type b struct{}
func (b *b) baz() (*int, error) {
return nil, io.EOF // some arbitrary error for demo
}
This prints a useful stack trace like below and you can know where the error occurred. $ go run main.go
err=EOF
main.run
/home/hnakamur/go/src/bitbucket.org/hnakamur/stacktrace-demo/main.go:31
main.main
/home/hnakamur/go/src/bitbucket.org/hnakamur/stacktrace-demo/main.go:12
runtime.main
/usr/local/go/src/runtime/proc.go:183
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:2086
exit status 1 a, err := foo()
if err != nil {
return nil, fmt.Errorf("failed to foo: %v", err)
} foo().map_err(MyError::new)?`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.
let a = foo().chain_err(|| "failed to foo")?;Practical concerns aside, in Haskell you get these things from the Maybe monad without extra syntax or macros:
foo >>= bar >>= baz 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<_>>();
[0]: https://crates.io/crates/mdoThat 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 am sure it will come soon (tm) though
Or to put it another way, it's taking an operation that does this:
a -> t of b
And turns it into one that does this:
t of a -> t of b
There's a formal name for "t"s that support this operation. I'll let you guess what it is. :)
(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).
In this sense, ? is following in a long tradition.
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.
>
> 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.
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').
Rust will certainly have warts. Maybe someday in the future we'll feel like this is one of them, maybe not. :)
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 :).
https://en.wikipedia.org/wiki/Safe_navigation_operator
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?
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)[0] https://doc.rust-lang.org/std/result/enum.Result.html#method...
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()https://doc.rust-lang.org/book/error-handling.html goes into it in great depth. It's a fair bit to grok but really shows how thorough the options Rust has when dealing with errors.
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 :)
foo().unwrap_or_else({ |e| return Err(e) })
Or in Go terms: a := try!(foo)
is equivalent to: a, err := foo()
if err != nil {
return nil, err
}
Basically it will take the result of foo, and if foo returns an error it will return the error itself.https://doc.rust-lang.org/beta/std/result/ has lots of examples of alternative ways you can deal with the error case, or the Ok() case of a result.
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.
EDIT: Answering my own question: https://github.com/rust-lang/rfcs/blob/master/text/0243-trai... makes it pretty clear; the error types must be the same. Which completely makes sense.
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) use std::fs::File;
enum MyError {
OhNo(String),
}
fn foo() -> Result<File, MyError> {
Ok(File::open("foo.txt")?)
}
With only the above code, we get an error: 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"))
}
}
Now `?` does the coercion automatically.The error "handling" code mostly part of the error types themselves, mostly in the form of to/from conversions.
The actual logic code can then rely on these conversions and is freed from bloat.
foo()?.bar()?.baz()?
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.
Ask me how many times I've been bitten by this.
It doesn't even compile so I doubt you have run into it in real code.
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.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.
It takes more effort to wrap a handler for a specific exception because there was no need originally to write a handler in that place.
You can just map your logging in between:
foo().map_err(|e| {log(e); e}).bar()
You can just macroexpand ? and do that (although I don't know that there are tools doing macroexpansion currently).
> I don't know what ? really does
It's really nothing special, `a?` simply expands to:
match a {
Ok(val) => val,
Err(err) => {
return Err(From::from(err))
}
}
> also if the funcitons throw exceptions instead of returning errors in other languages?
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.
Great tip. Thank you for the hint.
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.
edit: nope, fails with "err evaluated but not used".
The go way is a little more verbose/uglier but we are both going to do it in the same way as will the 30 other developers working on the project.
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?
Ultimately once you are actually writing code it doesn't matter.
(I personally am opposed, but I suspect I'm in the minority.)
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; }; if a < b { b } else { a }
why are you adding sprurious returns? And if you need to get the minimum of two values so much that actually impacts you, write a `min!` macro? use std::cmp::min;
min(a, b);
https://doc.rust-lang.org/std/cmp/fn.min.htmlSince if is an expression, ternay operators would be wholly redundant.
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.
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.Edit: Specifically with error-chain, you get:
- 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
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.
"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.
"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 sure seems to draw a lot of enthusiasm.
Not to detract from your point though. It's an awesome language, I'm just learning it but enthusiastically so for sure!
However, the timeframe for that is the same -- it is a different period of 6 weeks, but still a 6 week period.
Someone could open a PR today and not see it merged for a year, surely?
[1]: https://blog.rust-lang.org/2014/12/12/1.0-Timeline.html
They could, but this counts people who have landed a patch, not opened a PR. That many people got a patch into this release.
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.
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...
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 :)
Regardless, as I said below, it's great that so many people are contributing whatever the time frame :)
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.
[1] http://stackoverflow.com/questions/26685666/a-local-function...
> Does it bail out of just the lambda
Yes. > What about nested functions?
It returns from the nested function, not the outer function. > a named nested function which can access its outer scope?
No, functions don't close over any scopes.Of course for now you can create this control flow with `match (|| { block }) { }` as you describe.
Lamdas can capture their scope, and you can give them a name, so that might be useful? You can't use named functions (as per fn foo).
def lambda_test
lam = lambda { return }
lam.call
puts "Hello world"
end
This will print "hello world". But this: def proc_test
proc = Proc.new { return }
proc.call
puts "Hello world"
end
Will print nothing, as the 'return' returns from proc_test. Examples taken from here: http://awaxman11.github.io/blog/2013/08/05/what-is-the-diffe...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> { ... }
Check out this article, starting from "The From trait": http://blog.burntsushi.net/rust-error-handling/
I.e. this:
* https://github.com/rust-lang/rfcs/issues/349
* https://github.com/rust-lang/rust/issues/31844
Is it something coming soon, or it's pretty far still?
All of the bits are still too far out to give a good estimate of.
GPG signatures do exist for the tarballs, though, see
https://static.rust-lang.org/dist/rustc-1.13.0-src.tar.gz.as... for the source, for example, or https://static.rust-lang.org/dist/rustc-1.13.0-x86_64-apple-... for rustc.
http://rustup.rs/ will automatically use all of this if you have it installed and our key imported.
Suchas:
let x = Bus();
match x{
Car => ...
Bus => ...
Person => ...
}You would need to have runtime type information of course, this could be a compiler flag.
Rust isn't go. You don't have to break out of the type system because it doesn't have generics.
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.
I am curious how long it took you to learn and become proficient?
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.
Basically this feature is here to provide an easy propagation of errors, something an exception system does for you for free.
"?" exist because the current error handling make it inconvenient to propagate errors, and is a workaround to make it less of a pain.
It's alright. There is no shame in it. Just say so, don't try to talk your way out of it.
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.
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.
* makes you be explicit about call sites which throw
* makes it easy to reify fallible computation (so it can be stored in e.g. a Future)
For instance:
fn foo() -> Result<Value, IoError> {
let x = a()?;
let _ = b();
if someCondition {
return Err(IoError::Whatever)
}
return c(x).unwrap();
}
is just Value foo() throws IOException {
x = a();
try {
b();
} catch(Exception e) { /* TODO: ERROR HANDLING? */ }
if (someCondition) {
throw new IOException();
}
try {
return c(x);
} catch(Exception e) { abort(); }
}
This is made especially clear by Swift's model which is pretty
much exactly the same as Rust's, but makes it look like exceptions: // RIP typed errors
func foo() -> Value throws {
let x = try a() // "rethrow"
try? b()
if someCondition {
throw "Oh No IO!"
}
return try! c(x)
}(My code does have an error because I forgot to wrap the result in Ok)