Is ref really a legacy pattern?

The book has this section:

The ref keyword is like the opposite of & in patterns; this says “please bind ref to be a &String , don’t try to move it out. In other words, the & in &Some is matching against a reference, but ref creates a reference. ref mut is like ref , but for mutable references.

Anyway, today’s Rust doesn’t work like this. If you try to match on something borrowed, then all of the bindings you create will attempt to borrow as well. This means that the original code works as you’d expect.

Because Rust is backwards compatible, we couldn’t remove ref and ref mut , and they’re sometimes useful in obscure situations, where you want to partially borrow part of a struct as mutable and another part as immutable. But you may see them in older Rust code, so knowing what they do is still useful.

However, I just struggled for quite a while trying to figure out why updating an array in a destructure wasn't affecting the original array... after all, I received the struct as a reference - why wouldn't it work?

Sample gotcha code:

struct Foo {
    a:[f32;4],
    b:[f32;4]
}

impl Foo {
    pub fn new() -> Foo {
        Foo{
            a: [0.0;4],
            b: [0.0;4],   
        }
    }
}

fn do_stuff(foo:&mut Foo) {
    //this works
    let Foo {ref mut a, mut b} = foo;
    
    //this fails:
    //let Foo {mut a, mut b} = foo;
    
    a[0] = 1.0;
    foo.b[0] = 1.0;
    
    assert!(foo.a[0] == 1.0);
    assert!(foo.b[0] == 1.0);
}

fn main() {
    let mut foo = Foo::new();
    do_stuff(&mut foo);
}

Oh nevermind.... I see that there are other ways of doing it which don't cause that problem...

Specifically, here this works:

let Foo {a, b} = foo;

And in my actual example where I was getting stuck, destructuring like this works:

let Foo {a,b} = &mut self.data;

(though leaving the &mut off there does not...)

I'm still not entirely sure of where "&mut" goes in various circumstances like this... back to the book! :wink:

I believe, that if you specify mut a, mut b then the compiler will think that you're trying to specify the modifiers (ref and mut) and therefore not infer them, and in the second bit of code where you mention the necessity of &mut, if I read it correctly, the docs you quoted meant that specifying you're destructuring a reference to a variable makes all the variables you're destructuring references as well. It's abit murky in this case because [f32; 4] is Clone and therefore will be cloned when necessary.

1 Like

Sorry for replying to an old thread, but I am unable to find that section in the Rust Book. Can you post a link? I keep reading that ref keyword is "legacy" but want to solidify my understanding so I can know when it's actually needed.

It's in chapter 18.3, under 'legacy patterns': All the Pattern Syntax - The Rust Programming Language

1 Like

Aside: this sentence from the quoted part of the docs makes me sad:

This means that the original code works as you’d expect.

I've said this before but because the wording here ("legacy") suggests a recommendation against explicit refs, I feel this is warranted.

I specifically wouldn't expect a non-pointer type (in the pattern) to get accepted in the place of a pointer type (in the expression being pattern matched against), even less so to automagically propagate references to inner subpatterns, because 1. references themselves are patterns, and 2. the distinction between reference and non-reference types is very clear and important in most other parts of the language.

Therefore I still use ref, and consider it a massive readability win over default binding modes, even in new code I write.

3 Likes