Destruturing via a reference

In:

let p = Person {name: String::from("Michael"), age: 19};
let Person { name, age} = p;
println!("Name is {}, age is {}", name, age);
println!("Name from p is {}", p.name);

The second print bombs, as expected. p.name was moved in the destructure in the second line.

If I destruct using a reference, however, this isn't a problem:

let p = Person {name: String::from("Michael"), age: 19};
let Person { name, age} = &p;
println!("Name is {}, age is {}", name, age);
println!("Name from p is {}", p.name);

I don't know how to think about this. What is the implication of destructuring something through a reference? Apparently name is not moved in the destructure, yet when I fly over the destructure in the source code it doesn't show name as &str, but as a String (which makes it look like a move).

Thanks for any insights.
Michael

Are you sure the type of name isn't &String?

Effectively when you destructure a reference to a struct, what you're doing is:

let name = &p.name;
let age = &p.age;

It used to be the case that to do this, you would have had to write

let &Person { ref name, ref age} = &p;

Where the & before Person explicitly requests that &p be dereferenced to get a Person, and the ref bindings request that e.g. name borrows (*(&p)).name (which is equivalent to p.name) instead of moving. But the compiler was made smarter so it could just infer what you meant when you destructure a reference.

1 Like

So if one of the fields in the struct was defined as &, then that field would be destructed as &&, right?

And yes, the fly over did read &std::string::String I think my eyes were looking for something like std::string::&String

Also, for what it's worth, sometimes I wish the compiler had a --no-shortcuts switch that made me have to use &/ref everywhere it is currently handled with "sugar" so that I could learn the ropes easier. With the compiler doing some things such as how it handles destructuring via a reference, it's actually more confusing for a new person trying to learn the rules. Another case in point is println! macros. I don't have to specify & when I pass in values, yet & gets taken. Makes me think I don't understand the rules but it's just because Rust is bending them for me...

3 Likes

Note the &s here are unnecessary because you can bind a place expression by reference without moving it.

I sharply opposed the automatic referencing (aka "match ergonomics" aka "default binding modes") change in the language, exactly for this reason. I too wish there was such a setting in the compiler.

Alas, I opened an issue in Clippy when this misfeature came out, so that it would optionally warn against the accidental usage of this antipattern. However, it was rejected without proper consideration, because "we don't lint away accepted RFCs".

So, as stupid as it sounds, you and I will have to live with the unfortunate consequences of this change.

Or, you can try to change the mind of Clippy's authors. Given that some of them are Rust language team members, that is unlikely to succeed.

I have opened a new thread on IRLO – you might be interested in it if you also want a lint against default binding modes.