Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.




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

Search: