Mutable reference pattern matching

I have a function add_list that is constructing and adding a new NamedList (which is really just a Vec) to the end of a Vec<NamedList>. I want it to return a mutable reference to the new inner vector, but I can't seem to figure out how getting a mutable reference in a pattern match is supposed to work, every syntactic permutation I try fails. What am I missing?

#![allow(dead_code)]
#![allow(unused_variables)]

struct NamedList {
    name: String,
    list: Vec<i64>
}

fn add_list<'a,'b>(named_lists: &'a mut Vec<NamedList>, name: &'b str) -> &'a mut Vec<i64>
{
    named_lists.push(NamedList{ name: name.into(), list: Vec::default() });
    
    // this doesn't work nor any version below
    let NamedList{ ref name, ref mut list } = &mut named_lists.last().unwrap(); // cannot borrow as mutable
    
    //let NamedList{ ref name, ref mut list } = named_lists.last().unwrap(); // cannot borrow as mutable
    //let NamedList{ ref name, mut list } = named_lists.last().unwrap(); // mismatched types
    //let NamedList{ ref mut name, ref mut list } = named_lists.last().unwrap(); // cannot borrow as mutable
    //let mut NamedList{ ref mut name, ref mut list } = named_lists.last().unwrap(); // `mut` must be followed by a named binding
    list
}

Playground link

You want last_mut, not last.

struct NamedList {
    name: String,
    list: Vec<i64>,
}

fn add_list<'a, 'b>(named_lists: &'a mut Vec<NamedList>, name: &'b str) -> &'a mut Vec<i64> {
    named_lists.push(NamedList {
        name: name.into(),
        list: Vec::default(),
    });

    &mut named_lists.last_mut().unwrap().list
}
1 Like

Further explanation: once you have gotten an & reference, you can never get an &mut reference out of it[1]. So, whenever you are failing to obtain a mutable reference, you need to make sure that nothing “on the path” from the source (named_lists) to the reference you want is an &-creating operation.


  1. assuming no interior mutability is involved ↩︎

1 Like

Oof, thanks, none of the errors gave me a clue. They just kept saying list wasn't declared mutable, that's why I kept trying to attach mut to it.

Alright that almost gets me all the way but rustc is now upset for a different reason:

error[E0515]: cannot return value referencing temporary value
  --> src/lib.rs:20:5
   |
14 |     let NamedList{ ref name, ref mut list } = &mut named_lists.last_mut().unwrap(); // cannot borrow as mutable
   |                                                    ------------------------------- temporary value created here
...
20 |     list
   |     ^^^^ returns a value referencing data owned by the current function

Except that the option should a &mut NamedList, and I'm just returning a reference into that which is not part of the temporary...?

Delete the &mut after the =. It's redundant, since the result of the unwrap is going to be an &mut NamedList anyway, and it's what's causing the “temporary value” to exist.

When you write &mut some_function() (here the function is unwrap), the compiler effectively assigns some_function() to a sort of variable (the temporary), and then takes a reference to it. That reference can't outlive the temporary, which is what the error message is objecting to.

(That said, I think it would be reasonable of the compiler to do what you meant, instead. I'm only explaining the perspective to use to make the error message make sense, not explaining how it has to work.)

2 Likes

I'm a little confused by that, because I've gotten used to rustc type checking before it borrow checks, and it seems like with the extra &mut it should fail to type check? The RHS is a reference to reference, and the LHS is not.

If Rust were insisting that your pattern's type exactly matched your expression's type, then you would be required to write:

let &mut NamedList { ref name, ref mut list } =
    named_lists.last_mut().unwrap();

because the result of the unwrap is an &mut NamedList, not a NamedList. In fact, you used to have to do that; a feature called match ergonomics was introduced which says that you don't have to write out & or &mut when matching a reference to a structure — and, when you do, the ref mut is implicit. So, actually, you can even write this:

let NamedList { name, list } = named_lists.last_mut().unwrap();

and list will be a mutable reference, implicitly. This is a highly convenient feature and you have quite likely used it previously without noticing! If you would like to try programming without it (which can be useful to build understanding, or in situations with unsafe code where the difference between a reference and its referent matters and can't be caught by type checking), then you can declare

#![deny(clippy::pattern_type_mismatch)]

and run cargo clippy, and it will reject any program where the LHS and RHS types do not match exactly. However, this won't work on programs that don't yet compile.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.