
Rust for C++ Programmers Part 7: Data Types - ihnorton
http://featherweightmusings.blogspot.com/2014/05/rust-for-c-programmers-part-7-data-types.html
======
Nycto
I would love to see an article like this for error handling in Rust. I was
really interested in using conditions, but those were apparently backed out.
Which leaves error handling through return values and macros. This seems like
a step back from Exceptions to me. I want to be convinced otherwise, but I'm
struggling to see how this is better than other mechanisms.

~~~
TheHydroImpulse
Error handling in Rust is actually pretty awesome. There's a standard
`Result<T, E>` type. One can write a potentially fail-able function like:

    
    
        fn can_fail(arg: bool) -> Result<(), StrBuf> {
            if arg {
                Ok(())
            } else {
                Err(StrBuf::from_str("Oops! Something went wrong."))
            }
        }
    

Here, there's no value when the function succeeds `()`. When it fails (I don't
mean fail as in a `fail!()` or a panic or anything), you get a string back.

You can pattern match the return value:

    
    
        match can_fail(true) {
            Ok(_) => {},
            Err(err) => {}
        }
    

This can get quite cumbersome, however. That's why there's a `try!` macro that
adds composability. The idea is that if you have a function returning a
`Result`, wherever that function is being called could also return a `Result`.

    
    
         fn higher_up() -> Result<(), StrBuf> {
             try!(can_fail(true));
         }
    

`try!` is simply:

    
    
        match $e { Ok(e) => e, Err(e) => return Err(e) }
    

This allows error to propagate up the chain.

When you're working in big-ish projects, it'd be best to have something better
than a simple `StrBuf` for an error. You'd probably want a struct:

    
    
        pub struct LibError {
            message: StrBuf,
            error: Error
        }
    
        pub enum Error {
            One,
            Two,
            Three
        }
    

Where `Lib` is the library/project name.

You can then create a new result type based on your new error type:

    
    
        type LibResult<T> = Result<T, LibError>;
    

Then you can use `LibResult<T>` everywhere in your app.

You can view this example done in a few of my own libraries
([https://github.com/TheHydroImpulse/gossip.rs/blob/master/src...](https://github.com/TheHydroImpulse/gossip.rs/blob/master/src/gossip/util.rs#L14))
and cargo
([https://github.com/carlhuda/cargo/blob/master/src/cargo/util...](https://github.com/carlhuda/cargo/blob/master/src/cargo/util/result.rs)).

That's a simple overview of it. Error handling is super simple, not verbose
(thanks to try!) and in your control. Because of Rust's type system, things
like `Result<T, E>` is available and are so much better than simple return
values (like integers: -1 vs 0 uhhh)

~~~
Pxtl
I tend to practice "only catch what you can handle" in exception-enabled
languages - I haven't written in a systems language in almost a decade, mostly
bad memories of C. How much does error-handling get in the way when you have
to live without stack unwinding?

~~~
teacup50
"only catch what you can handle" is incredible nonsense. Your code is the only
code that knows how the code it's calling might fail -- it MUST catch all
exceptions and either handle them, or re-raise them with a well defined type
that is documented and declared in your API.

Anything else just leads to buggy software that has a try/catch block at the
top level of the event loop/main/thread start function to deal with all the
errors that leak out of its implementation and leave the process in an
undefined state.

Exceptions are simply broken and awful. Java does them sorta right with
checked exceptions, but the only safe thing is to not do them at all.

~~~
Pxtl
Obviously you should be converting exceptions that leave your library into
other areas, but internally? Converting exceptions over and over and over
again just means losing information from those exceptions, or worse hiding
them. If I'm forced to dump a stack-trace to the text file, I want the exact
exception that caused the problem, not some vague "Operation Exception" that
quintuply wraps my actual desired exception, or worse completely threw it out
to "cleaned it up for me" and tells me nothing about what went wrong.

I just helped a teammate work through a bug the other day where somebody
decided to "handle" a case-sensitivity problem in their home-brewed SqlLite
data-access code by simply returning null for the data member if you got the
wrong case. This resulted in improperly-cased column names producing objects
with null members - no error happened because they were valid SQL queries, but
the dictionary-reading code was silently failing when it was reading the
result-set. If the program had just blown up when there was a miss on the
dictionary of column names? We would've quickly found out about that stupid
case-sensitivity.

Defensive coding just means your bugs go non-local and become data problems
instead of exceptions.

~~~
teacup50
That doesn't make any sense. Defensive coding means not silently discarding
errors (by returning null, in this case), and has nothing at all to do with
exceptions.

As for rewrapping errors, yes, each subsystem should have its own error space.
You don't lose data by nesting errors; on the contrary, each level can add
additional context to an error result that makes debugging an unexpected issue
far easier.

------
ihnorton
I'm curious why this isn't automatically destructured:

    
    
        struct IntPoint (int, int);
    
        fn foo(x: IntPoint) {
            let IntPoint(a, b) = x;  // Note that we need the name of the tuple
                                     // struct to destructure.

~~~
kibwen
What do you mean by "automatically destructured"? If you mean the extra step
of destructuring in the function body, that's not necessary. You can
destructure like that anywhere that a pattern is accepted, which includes
function parameter lists:

    
    
      struct Foo(int, int);
    
      fn bar(Foo(a, b): Foo) {
          println!("a: {}, b: {}", a, b);
      }
    
      fn main() {
          let qux = Foo(1, 2);
          bar(qux);  // a: 1, b: 2
      }

~~~
kzrdude
What they mean is that there would be special cases for let patterns so that

    
    
         let (a, b) = Foo(a,b);
    

is a fine destructuring. It would be a special case for let, since the pattern
would have to be more explicit in function arguments and in match, but I think
they have a good point.

~~~
kibwen
I should emphasize that tuple structs are a somewhat obscure feature. Their
main use case is to support newtyping:

    
    
      struct Meters(f64);
      struct Miles(f64);
      let meters = Meters(10.4);
      let miles = metric_to_imperial(meters);
      let Miles(raw_miles) = miles;
    

The single-arity case above constitutes the vast majority of tuple struct
usage. And, as you may expect, in any other context besides tuple structs a
single-arity tuple is completely silly (the only reason that we have syntax
for single-arity tuples at all is to make writing macros easier).

Ultimately it's just not a feature that would be pulling its weight. If you
want a structure with multiple fields where destructuring is not necessary,
just use a struct in the first place. Honestly, if we found a better way to
support newtyping then I wouldn't be sad if we got rid of tuple structs
entirely.

------
winter_blue
I just read the first section, just the section on structs -- what's different
here, from C? It provides all of the same features, with a slightly different
syntax.

~~~
nrc
With structs, pretty much the only difference is the syntax. Enums are
probably where the biggest differences are from C, as far as data types go.

