Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider mentioning the syntax to destructure a tuple struct #4047

Merged
merged 5 commits into from
Oct 7, 2024

Conversation

jpmelos
Copy link
Contributor

@jpmelos jpmelos commented Sep 29, 2024

I would like to propose that we consider explicitly mentioning the syntax for destructing a tuple struct in the book. One thing that may be surprising to newcomers is that you need to declare the type of the struct that you are destructuring. To me, it is still not clear why this is even needed, given that the compiler should know the type of the struct that you are operating on?

Here's the error I got when I tried without the type:

    let (r, g, b) = black;
   Compiling learn_rust v0.1.0
error[E0308]: mismatched types
 --> src/main.rs:6:9
  |
6 |     let (r, g, b) = black;
  |         ^^^^^^^^^   ----- this expression has type `Color`
  |         |
  |         expected `Color`, found `(_, _, _)`
  |
  = note: expected struct `Color`
              found tuple `(_, _, _)`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `learn_rust` (bin "learn_rust") due to 1 previous error

This fixes it:

    let Color(r, g, b) = black;

Funny enough, this fails:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);

    let Point(r, g, b) = black;

    assert_eq!(r, 0);
}
   Compiling learn_rust v0.1.0 (/Users/jpmelos/devel/learn_rust)
error[E0308]: mismatched types
 --> src/main.rs:7:9
  |
7 |     let Point(r, g, b) = black;
  |         ^^^^^^^^^^^^^^   ----- this expression has type `Color`
  |         |
  |         expected `Color`, found `Point`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `learn_rust` (bin "learn_rust") due to 1 previous error

Which means the compiler does know what it expects, but it wants the programmer to explicitly say it!

Copy link
Contributor

@chriskrycho chriskrycho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll start by explaining why things work the way they do, then circle back to the question of whether we should tweak the text here.

Why Things Work This Way

On the first point, the syntax without the name already means something: a “regular” tuple type:

let regular_tuple = (1, "hello", true);
let (num, str, boolean) = regular_tuple;

While Rust could choose to make that work there, and likewise for structs with named fields, the language has instead chosen to keep them distinct, in part so that the distinction is clear when reading code. You’re not wrong that the compiler definitely should know what the type is , so I can imagine adding some syntax sugar to make it nicer where it is unambiguous, but I don’t see that on the horizon any time soon!

As to the second point you make, the text actually explicitly addresses it in the paragraph preceding:

Note that the black and origin values are different types because they’re instances of different tuple structs. Each struct you define is its own type, even though the fields within the struct might have the same types. For example, a function that takes a parameter of type Color cannot take a Point as an argument, even though both types are made up of three i32 values. Otherwise, tuple struct instances are similar to tuples in that you can destructure them into their individual pieces, and you can use a . followed by the index to access an individual value.

This is one of the reasons that this is useful: you don’t want to end up using a Color where you mean a Point and vice versa, because they have different semantics for those three i32 values. Consider, if the non-named form were allowed:

let (x, y, z) = black;
do_something_in_coord_space(x, y, z);

Whoops. That type-checks, but it’s a bug. And even in the case where it doesn’t type check, your compiler error could end up far away from the mistake:

struct Point(i32, i32, i32);
struct Color(u8, u8, u8);

let black = Color(0, 0, 0);

// mistake is here, because these are all u8 now
let (x, y, z) = black;

// But the type error is here
do_something_with_x_and_y(x, y);
//                        ^  ^

Requiring it to be a bit more explicit makes it much likelier that we get the error at the point we destructure the values.

So while Rust could make it “just work”, and some other languages do, there are tradeoffs here!

Changes to the Text

I did a quick check and we never cover this in the book, so I do think it’s worth mentioning. I don’t think we need to get into all the detail and background I just covered here, of course! I think we can also skip the “does not compile” example and just add a sentence like this to the end of the preceding paragraph:

Unlike tuples, tuple structs require you to name the type of the struct when you destructure them, because they are different types even when they have the same fields.

Let’s also go ahead and revert the changes to the listing. Thanks!

@jpmelos
Copy link
Contributor Author

jpmelos commented Oct 5, 2024

Thank you very much for the detailed explanation. That is super helpful! 😄

I adjusted the PR, but I dropped the last few words ("because they are different types even when they have the same fields") because that's already been mentioned in that same paragraph, so it felt redundant and repetitive when I put it all together.

Let me know if you feel strongly otherwise, and I will re-add that.

Copy link
Contributor

@chriskrycho chriskrycho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re welcome, and thanks for the contributin!

@chriskrycho chriskrycho merged commit 3401154 into rust-lang:main Oct 7, 2024
3 checks passed
@jpmelos jpmelos deleted the small-improvements-to-ch05-2 branch October 8, 2024 07:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants