Hacker News new | past | comments | ask | show | jobs | submit login
Announcing Rust 1.13 (rust-lang.org)
321 points by Manishearth on Nov 10, 2016 | hide | past | favorite | 227 comments



The ? operator looks lovely. It's like the C# null conditional operator, but for the Result pattern:

    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


Versus the Python equivalent:

    try:
        foo().bar().baz()
    except Exception as message: 
        return message


It's true.

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?

  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).


C#, C++, Java, F#, OCaml, Haskell, Lisp

    try_block
        some_vec.iter().filter_map(|i| 
                           i.to_something()
                            .map_err(|e|None)
                            .map(|i|Some(i)))
                         .collect()
    catch_exception


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.


So what, I don't care where the error occurs.

Either the full expression is successful or not.


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.

But as you say, it is a matter of style.


Except that try!() returns to the call site.

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.


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 :)


In those cases I take a defensive approach.

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" and the Result pattern can be translated back and forth by a machine (i.e. syntax sugar for each other).

The trick is "Exceptions" are more than just "Checked Exceptions"


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.


Fair enough, CheckedExceptions are almost always "fat pointers". Meaning they are all the same size, and can be passed around equivalently.

The Rust equivalent would be something like:

`Result<_, Box<Error>>`


Amen, they have the same expressive power.

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).


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.


Versus the Ada equivalent:

    Foo.Bar.Baz;
  exception when Program_Error => Put ("WHoops");
            when Constraint_Error => Put ("Dang");
            when Gaim_OVer_Main => Put ("??? D: ");
            when others => Put ("IDC...");


I kind of like how I can't forget to handle some error because the compiler will tell me


That swallows all exceptions. You should instead use a more specific type.


I think the Python equivalent is just:

foo().bar().baz()

ie, let someone higher up the stack handle the error.


that doesn't do what you think it does


Exactly. It isn't just that it's shorter; it fixes a right-left-right-left alternating reading order, turning it into a left-to-right reading order.


I think it also lets you turn this:

    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.


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.


It has somewhat different semantics though, for instance with `?` all three functions can use different error types.


They have to be convertable though, because the function must have a single result code.


What if I want to do something like in following snipet:

    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");


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"));



Panic will stop program at place of error, while I want nicely stacked error message list:

    [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.


It would work with just the ?s if you had the right implementations of the error conversion functions. I have an example below.


Compiler cannot determine intent, so it will not be able to produce correct, user readable error message, in user language.


Yes, that's why you end up writing an error type and the conversions between various errors.


Imagine, you are taking exam.

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.


Thanks you, I got the idea.


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.


In Go, I use https://github.com/pkg/errors these days and wrap errors with stack trace.

  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


How do you decorate the errors? In Go I might write

    a, err := foo()
    if err != nil {
        return nil, fmt.Errorf("failed to foo: %v", err) 
    }


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:

  foo().map_err(MyError::new)?


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.


I use error_chain

    let a = foo().chain_err(|| "failed to foo")?;


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:

  foo >>= bar >>= baz


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<_>>();
[0]: https://crates.io/crates/mdo


I'm pretty sure that ">>=" is "extra syntax". The only difference is in Haskell any function with two arguments can be interpreted as infix.


It's not built-in syntax, not even a macro. Just a binary function.


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.


"extra syntax or macros" refers to OP's example of "?". That ">>=" is not magic is exactly what I said (meant to say).


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.


If you don't mind unstable you can implement it yourself for Option. https://doc.rust-lang.org/src/core/up/src/libcore/ops.rs.htm...

I am sure it will come soon (tm) though


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.


That's because they're both >>= in Haskell. So is SelectMany in C#.

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. :)


This is inaccurate. While Result is a monad, `?` in Rust is not the monadic bind. That's the `and_then` method.


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.)


> how many

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.


Ha! Fair point.


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 :).


This is a standard operator found in many programming languages, including ones with GC:

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.


It has very different semantics in Rust than it does elsewhere though.


Not sure what you are talking about - it is the first bigger syntax addition since 1.0, and I actually want more.


Which ones specifically worry you? I can't think of many, but I am also incredibly biased.


Sorry, phrasing was bad. I've edited.


No worries, it happens.

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)

[0] https://doc.rust-lang.org/std/result/enum.Result.html#method...


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()


To extend on this there's also a ton of great error mapping/handling functions as a part of Result.

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 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 :)


`try!(foo)` is roughly equivalent to this (invalid) code:

    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.


It's worth mentioning that try! is a macro that basically takes that expr and turns it to something similar to your go example.


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)?

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.


> 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)


They have to be convertible to the type that's being returned. You'd need an implementation of y -> z. It will call the conversion function for you.

  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.


This is one of the things that makes Rust's error handling so elegant.

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.


I find the Go version to be easier to debug though.


Really? Really?

    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.


OK, how many times have you been bitten by it?

It doesn't even compile so I doubt you have run into it in real code.


that problem does compile in Go, and i have done it before (maybe with single err and not a comma). iirc there are go vet checks to catch that.


See my other comment for runnable example. https://news.ycombinator.com/item?id=12925765


Nothing prevents you from writing

    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).


> I don't know what ? really does

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.


That's trivially possible in the Go version because you paid the cost of adding explicit error handling upfront.

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.


>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

You can just map your logging in between:

foo().map_err(|e| {log(e); e}).bar()


> If you want to set a breakpoint on the error then I guess that's not possible in your form.

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

?


> 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.


Why? Can you explicitly name a type of mistake that might happen and explain how Go solves it in a way that Rust does not?


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.


> use "finish" to effectively go call by call

Great tip. Thank you for the hint.


I hope so, because it is more subject to accidental copy-paste bugs.


And follow-up fixing n copies when that bugs gets copied, but only gets discovered later.


My favorite: those two snippets are 98% similar, is there a bug, two bugs or two distinct cases?


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!


i actually don't think the Go version is bad. I can very clearly see what is happening and I can also do something with the error


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).


Not to mention forgetting to `return` nil, err. If you just type `nil, err`, you don't handle the error and nothing complains. Oops.



Is this a recent change? I've with 100% been bitten by this before, repeatedly, and the go compiler did nothing to warn about it.


Maybe for a side-effecting function which would only return an error?

edit: nope, fails with "err evaluated but not used".


I'm genuinely stumped. If they've fixed this, that's fantastic because I have absolutely shipped code with this bug to a production environment.


I've been working with Go for 4 years and I've not encountered it. Perhaps there was an edge case where it was allowed, but I don't recall it.


You only have to learn what '?' does once. You have to write (and more importantly, read) the Go boilerplate every time.


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.


The difference is that if I thought the Go style is easier to read/debug I couldn't prevent you from writing it in the complex one-liner way.

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.


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.


Same here. I value the readability more over the "oh no it's 20 extra key-strokes" drawback.


What?

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?


Probably from the perspective of someone who doesn't know either language, go is easier to infer what is happening.

Ultimately once you are actually writing code it doesn't matter.


I think the new operator works only with Result. Had it been for Option also, it would have been awesome.


It can be extended for Option in the future, we're just not sure that we're doing it yet.

(I personally am opposed, but I suspect I'm in the minority.)


Write a function to turn Option into a Result.


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; };


But…. these are two completely different statements, the equivalent Rust is

    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?


No macros needed!

    use std::cmp::min;
    min(a, b);
https://doc.rust-lang.org/std/cmp/fn.min.html


Ha. I should have known, thanks.


This doesn't duplicate anything in the language; it moves a stdlib macro to be included in the core language.

Since if is an expression, ternay operators would be wholly redundant.


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.


I tip my hat to you for remaining committed to one of the oldest Rust trolls there is.


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.


https://crates.io/crates/error-chain or https://crates.io/crates/quick-error (which error-chain uses and adds to) provide macros to help with defining error types.

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


How well does that play with packages that don't use it? There seem to be several such packages.


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.


Worth noting:

"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."


Congrats to the team and thank you! As always, my Docker image for Rust has been updated for 1.13: https://hub.docker.com/r/jimmycuadra/rust/


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?


The LLVM IR doesn't define an ABI - in particular, most of the ABI is already "burned in" at the point where LLVM IR exists.


Not obviously. For example, all of those languages mangle names differently.


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.


It's astonishing that, in just 6 weeks 155 people contributed. (1.12 was September 29th)

Rust sure seems to draw a lot of enthusiasm.


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!


I think it's the contributions merged in master that are in the 1.13 release but not 1.12.

However, the timeframe for that is the same -- it is a different period of 6 weeks, but still a 6 week period.


Why is it a 6 week period?

Someone could open a PR today and not see it merged for a year, surely?


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`.

[1]: https://blog.rust-lang.org/2014/12/12/1.0-Timeline.html


The Rust release cycle is six weeks.

They could, but this counts people who have landed a patch, not opened a PR. That many people got a patch into this release.


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.

[1] http://stackoverflow.com/questions/26685666/a-local-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 ).


  >  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.


That's good. Now there's a clean way to handle errors in a block-structured fashion.


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 bails out of the lamda - https://is.gd/0TT8pr

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).


The answer seems obvious; it's the same behavior "return" has, i.e. it returns from the function it's currently in, not something a few levels up.


It might seem obvious, but Ruby, for example:

  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...


Wow, that's a pretty nonsensical semantic. Thankfully, Rust is fairly sensible.


It makes more sense in-context; I did Ruby for years and never had to think about this once.


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.


Good to see bors[0] getting some well deserved credit. That guy's a beast!

[0] https://github.com/bors


Bots are people too!


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?


Yes, 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/


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.


Awesome, thanks. Does returning boxed errors have a performance impact?


It should only have an impact when errors actually occur, i think.


How far along is the effort for efficient code reuse (such as filling the gaps of lack of inheritance)?

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?


Specialization would only be one piece of the puzzle, and as you can see, it's not in stable yet.

All of the bits are still too far out to give a good estimate of.


Thanks. Do you expect that to introduce some non backward compatible changes to language (and for example require a new major version)?


Initial work on specialization has already landed on nightly and is backwards compatible. AFAIK, there are no plans to break that.


That's good!


I do not, no, and I would suspect that it'd be a non-starter if it did require it.


Great, thanks!


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?


https://github.com/rust-lang/rust/issues/27694

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.


Can someone explain to me why rust decided not to support pattern matching with different types?

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.


Why can't you already do this with an enum?

Rust isn't go. You don't have to break out of the type system because it doesn't have generics.


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.


And this is actually implemented by the Any trait.

https://doc.rust-lang.org/std/any/trait.Any.html



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 am thinking about learning Rust sometime in the near future.

I am curious how long it took you to learn and become proficient?


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.


I am in no way proficient, but I've been using it casually for about a year. I'm just now starting to feel really comfortable with it.


I've been coding Rust for three years in my free time and I'm still working on it.


Isn't that only necessary because rust doesn't have exceptions ?


? isn't necessary, it's just nice to have.

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.


You are just answering to the style of my question, while you perfectly understood the meaning and avoided to provide the answer.

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.


> 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.


In some sense, yes. But in another sense, not everyone agrees that exceptions are a good idea, and so would prefer this even if we did.


It's all the same stuff, man. Rust just:

* 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)
    }


It is the same stuff, exactly why errors should be values with some sugar for using them instead of a crazy parallel universe. :-)


You're forgetting about language pads and shenanigans.


Unwinding is an implementation of exceptions. You can implement them as returns, or, in the case of Swift, passing in a pointer for the Error type.


Okay that's fair. I usually equate them because every language I've ever used has implemented them with unwinding.


... landing pads. not language pads. Sheesh!


You should add language pads to your sick sleeve. Never4get.


Your Rust code has a compiler warning - you've ignored the result of b() which is returning a Result which must be handled.


No, `let _ = x` is the official way to ignore a value explicitly. The lint ignores it.

(My code does have an error because I forgot to wrap the result in Ok)


No, this is a standard operator found in many programming languages, including ones with exceptions:

https://en.wikipedia.org/wiki/Safe_navigation_operator


Is it "written in rust" ?


Yes.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: