Allow disabling of ergonomic features on a per-crate basis?

I agree, problems often crop up when what I believe is occurring and what actually occurs disagree. I've often found, however, that what I think is occurring seldom matters compared to the trust I put in my tooling. For example, when I program Rust, I rarely care about precise lifetimes and references. What I care about is that it prevents me from making mistakes. Put another way, references and lifetimes are tools, not ends in themselves.

From that perspective, I'd much rather see the problems @novacrazy and others point out addressed by improving the tooling (by, say, creating an editor plugin for lifetime tracing, or adding quick-fixes to RLS) than roll back a feature that eliminates boilerplate.

That said, there are aspects of Rust that I'm inclined to gloss over because I rely so heavily on intuition. That's why I reached for an example, above. Understanding the point at which @novacrazy's intuition didn't match the code informs the ergonomic debate, allowing correction in cases where it causes confusion.

This is about as close to a minimal example as I can get:

struct SomeStruct { y : SomeOtherStruct }
struct SomeOtherStruct { z: i32 }

impl SomeOtherStruct { fn frob(&self) { println!("frobbed {:?}", self.z) } }

pub fn main() {
    let x = &SomeStruct { y: SomeOtherStruct { z: 5 } };
    let SomeStruct { y } = x;
    y.frob();
    foo(y) // expected struct `SomeOtherStruct`, found reference
}

fn foo(b: SomeOtherStruct) {
    println!("{}", b.z)
}

In the above case, it's not entirely obvious that let SomeStruct { y } = x; is match ergonomics for let SomeStruct { ref y } = *x let &SomeStruct { ref y } = x. I suspect your example of let SomeStruct { y } = *x; is incorrect, because the compiler complains (as I would expect), about moving out of x. The move error also occurs when foo(y) is replaced by foo(*y).

Assuming I replicated your problem closely enough, I wouldn't blame match ergonomics for this, because it is clear that x (which is analogous to self in your example) is a reference. (In my code, this is clear because I take the reference on the preceding line, while for an inherent method it is clear from the signature.) Because SomeOtherStruct is not Copy, there's no other interpretation of y except that it is a reference, And, if y were Copy, then let SomeStruct { y } = *x; destructures to a copy into y, and the whole thing compiles cleanly.

I should recognize, however, that this is a toy example. This may not be as clear-cut in real-world scenarios.

2 Likes