5 ms·
I'm curious why this isn't automatically destructured: struct IntPoint (int, int); fn foo(x: IntPoint) { let IntPoint(a, b) = x; // Note that
by ihnorton 12y ago
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.
- 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.