Kevin W Matthews
Kevin W Matthews

Reputation: 744

How are Tuples destructured into references?

I'm looking at the Condvar example and am curious how the tuples pair and pair2 are destructured:

let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = pair.clone();
// ...

thread::spawn(move|| {
    let &(ref lock, ref cvar) = &*pair2;
    // ...
}

Removing the & from pair2:

let &(ref lock, ref cvar) = *pair2;

gives a compiler error as I expected:

11 |     let &(ref lock, ref cvar) = *pair2;
   |         ^^^^^^^^^^^^^^^^^^^^^ expected tuple, found reference
   |
   = note: expected type `(std::sync::Mutex<bool>, std::sync::Condvar)`
              found type `&_`

However, it seems to compile and run fine if the & around the tuple is removed:

let (ref lock, ref cvar) = &*pair2;

or if both of the &'s are removed:

let (ref lock, ref cvar) = *pair2;

or even with

let (lock, cvar) = &*pair2;

Is the compiler is helping us in the latter cases?

Upvotes: 1

Views: 1116

Answers (1)

Kevin W Matthews
Kevin W Matthews

Reputation: 744

The compiler is helping us using match ergonomics. Match ergonomics applies to tuple destructuring and the match expression; we'll look at the case of the match expression first.

Match ergonomics simplifies how Rust binds variables when a reference is matched to a non-reference pattern:

let x = Some(42);
let x_ref = &x;

match x_ref { // <-- reference match expression: `x_ref`
    Some(a) => {}, // <-- non-reference pattern: `Some(a)`
    None => {},
}

Older version of the Rust compiler didn't allow this. Instead, one had to either specify references (&) in the pattern:

// Old Rust
match x_ref {
    &Some(a) => {},
    &None => {},
}

or dereference before matching:

// Old Rust
match *x_ref {
    Some(a) => {},
    None => {},
}

Notice that a owns the Option's inner value, which is problematic for non-Copy types. To avoid this, one also had to borrow the inner value. This is done by binding a as a reference using the ref keyword:

// Old Rust
let x = Some(Box::new(42));
let x_ref = &x;

match x_ref {
    &Some(ref a) => {},
    &None => {},
}

or

// Old Rust
match *x_ref {
    Some(ref a) => {},
    None => {},
}

Rust's new match ergonomics allow a simplified version:

// New Rust
match x_ref {
    Some(a) => {
        // x_ref is automatically dereferenced
        // a is automatically bound as a reference
    },
    None => {},
}

for both Copy and non-Copy types.

Applying this to tuple destructuring,

let pair = Arc::new((Mutex::new(false), Condvar::new()));
let (lock, cvar) = &*pair;
//                 ^^^^^^ reference match expression
//  ^^^^^^^^^^^^ non-reference pattern
  • *pair is a tuple
  • &*pair is a reference to a tuple
  • Both lock and cvar are bound as references

Also see these Stack Overflow posts on:

Upvotes: 3

Related Questions