Live data from Hacker News

Rust for C++ Programmers Part 7: Data Types

featherweightmusings.blogspot.com

1–10 of 39 posts

Re: Rust for C++ Programmers Part 7: Data Types

#3
post #2

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.

For some reason I have an instinctual reaction that you should have to specify the type, but on reflection I'm not sure why. It is completely redundant, and since the fields aren't named for the purposes of a destructuring assignment like this any 2-field tuple is essentially equivalent.

Hm.

Re: Rust for C++ Programmers Part 7: Data Types

#4
post #2

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.

In Rust, we are quite strict about the syntax used for destructuring matching the syntax used for instantiation. There is a name in the tuple-struct declaration, therefore you must use the name when pattern matching (even though it is redundant).

Re: Rust for C++ Programmers Part 7: Data Types

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

Re: Rust for C++ Programmers Part 7: Data Types

#6
post #2

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.

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
  }

Re: Rust for C++ Programmers Part 7: Data Types

#7
post #2

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.

For some reason I have an instinctual reaction that you should have to specify the type, but on reflection I'm not sure why. It is completely redundant, and since the fields aren't named for the purposes of a destructuring assignment like this any 2-field tuple is essentially equivalent. Hm.

Tuple structs aren't used often, but the whole point of them is to force you to name a type. The idea is to restrict the types that you can call a function with; it turns a given tuple from a structural type to a nominal type.

For example, say you have two functions, where each function takes a single tuple of two floating point numbers:

  // Converts a Cartesian coordinate to a polar coordinate
  fn to_polar(coord: (f64, f64)) -> (f64, f64) { ... }

  // Calculates the area of a rectangle
  fn area(rect: (f64, f64)) -> f64 { ... }
Now, if you have a tuple that represents a coordinate, perhaps you don't want to feed it to the `area` function. Likewise for feeding a rectangle to the `to_polar` function. But because tuples are just structural types, something like `area(to_polar((2.5, 3.7))` is completely legal.

If you didn't want to allow this, or even if you just wanted to have greater control over all these anonymous tuples floating around, you'd use tuple structs to give them names:

  struct CarteCoord(f64, f64);
  struct PolarCoord(f64, f64);
  struct Rectangle(f64, f64);
  struct Area(f64);  // bonus round!

  fn to_polar(coord: CarteCoord) -> PolarCoord { ... }
  fn area(rect: Rectangle) -> Area { ... }
Taking the above steps makes `area(to_polar(CarteCoord(2.5, 3.7)))` a compile-time error. It's all about how strict you want to be with your types.

Re: Rust for C++ Programmers Part 7: Data Types

#8
post #5

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.

Error handling in Rust is actually pretty awesome. There's a standard `Result` type. One can write a potentially fail-able function like:

    fn can_fail(arg: bool) -> Result {
        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 {
         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 = Result;
Then you can use `LibResult` 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...) and cargo (https://github.com/carlhuda/cargo/blob/master/src/cargo/util...).

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` is available and are so much better than simple return values (like integers: -1 vs 0 uhhh)

Re: Rust for C++ Programmers Part 7: Data Types

#9
post #5

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.

Is it maybe explicit programming? In my own mind I don't understand why errors should receive special treatment in the language. I prefer to explicitly handle them always.

Re: Rust for C++ Programmers Part 7: Data Types

#10
post #5

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.

Error handling in Rust is actually pretty awesome. There's a standard `Result ` type. One can write a potentially fail-able function like: fn can_fail(arg: bool) -> Result { 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 p…

In addition to the (very handy) try! macro you can also map over Results and even chain monadic-like operations on them (either on the Ok or Err sides of a Result):

    enum FooError {
        XWasFalse,
        XWasUnknown
    }

    enum BarError {
        VectorTooLarge,
        FooErr(FooError)
    }

    fn foo(x: Option) -> Result {
        match x {
            Some(true) => Ok(42),
            Some(false) => Err(XWasFalse),
            _ => Err(XWasUnknown)
        }
    }

    fn bar() -> Result, BarError> {
        foo(None).or_else(|e| {
            // We can recover from an XWasUnknown error returned by
            // Foo, but not from a XWasFalse, so we return the error wrapped
            // in bar's error type.
            match e {
                XWasFalse => Err(FooErr(e)),
                XWasUnknown => Ok(99)
            }
        }).and_then(|n| {
            if n  = Vec::with_capacity(n);
                Ok(vec)
            } else {
                Err(VectorTooLarge)
            }
        }).map(|vec| {
            vec.iter().map(|_| { 42 }).collect()
        })
    }
edit: added a better example
Post reply on HN