7 ms·
Rust for C++ Programmers Part 7: Data Types
- ihnorton 12y agoI'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.
- stormbrew 12y agoFor 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.
- kibwen 12y agoTuple 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.
- stormbrew 12y agoSure, and all of that makes sense for function arguments, but for a destructuring assignment I'm not sure the same constraints are meaningful.
- nrc 12y agoIn 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).
- kibwen 12y agoWhat 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 12y agoWhat 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 12y agoI 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.
- Nycto 12y agoI 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 12y agoError 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/gossip/util.rs#L14 https://github.com/TheHydroImpulse/gossip.rs/blob/master/src...) and cargo (https://github.com/carlhuda/cargo/blob/master/src/cargo/util/result.rs 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<T, E>` is available and are so much better than simple return values (like integers: -1 vs 0 uhhh)
- winter_blue 12y agoI 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.