Ref keyword versus &

To me the fact that let Foo(ref foo) = *foo; works even when let bar = *foo; can't move out of borrowed context indicates that the language is smarter about navigating (de)references than just executing them one by one in order.

And that's regardless of optimization settings of course. From C I wouldn't expect anything else other than mechanically & taking address, * loading data, and then optimizer trying to optimize that out.

I think we're mixing up language features/semantics vs how they're actually lowered to machine code. I'm no LLVM IR expert but if you look at the debug build IR for let Foo(ref foo) = *foo, it looks to be pretty much exactly the sequence of alloca, store, load etc that you describe. If the optimizer didn't take that out in release builds, Rust would be unbearably slow of course :slight_smile:.

This is turning out to be a great thread for discovering subtle syntax! Can you please explain what this line does? It reminds me of an if let but without the if, which doesn't fit into my mental model for Rust.

It's basically the same thing as if let - pattern matching/destructuring -- without the conditional part. foo is a &Foo, and so this creates a pattern on the left where you end up with a binding, foo, that is a reference to the interior value of Foo. To use a concrete example:

struct Foo(String);

fn bar(foo: &Foo) {
   // let's use `foo2` as the binding name as otherwise there're too many `foo`s.
   let Foo(ref foo2) = *foo;
   // `foo2` is now a `&String`
}

This is occasionally a convenient way to create bindings to a struct's internal fields so you don't need to go via the struct itself. And sometimes you'll see this used to move fields out of a struct entirely:

fn consume_self(self) {
   // moves all those fields out of `self` into the `field1`, `field2`, etc bindings
   let Foo {field1, field2, field3, field4} = self;
}
2 Likes

The secret is that in most places where you assign to a "variable" it's actually a pattern that supports more or less the same syntax as match arms.

1 Like

let Foo(ref foo) = *foo;
is same as
let foo = &foo.0

You only typically use a pattern in such a way with tuple return values.
Like @kornel mentions, you can use them in a functions parameters. (Just not in a traits - saw this change being responsible for breaking a few crates post 1.0)

2 Likes

Does this mean println! can't be written using macro_rules!? I wasn't aware that one could conditionally compile something depending on whether it supports .to_string().

I used to be confused by ref, so I wrote a blog post explaining it.

2 Likes

Can someone confirm these two matches does the same thing? I'm not sure myself.

// Use & create a reference.
match &value {
    r => println!("Got a reference: {:p}", r),
}

// Use ref to create a reference.
match value {
    ref r => println!("Got a reference: {:p}", r),
}

Also these two

let mut mut_value = 6;

// Use `ref mut`
match mut_value {
    ref mut m => {
        // Got a reference. Gotta dereference it before we can
        // add anything to it.
        *m += 10;
        println!("Mutable reference: {:p}", m);
        println!("We added 10. `mut_value`: {:?}", m);
    }
}

    mut_value = 6;

// Use &mut
match &mut mut_value {
    m => {
        // Got a reference. Gotta dereference it before we can
        // add anything to it.
        *m += 10;
        println!("Mutable reference: {:p}", m);
        println!("We added 10. `mut_value`: {:?}", m);
    }
}
1 Like

Yes, they do (in both of your examples).

As far as I understand it, not always. Using the reference as what you're matching takes a reference and consumes only the reference (Not the value). Hence this is possible:

let x = String::from("abc"); //This is not copy.
match &x {
    //
}
println!("{:?}", x);

If combined with other patterns, which do take by value, then the ref is actually just syntax sugar for this:

let ref x = 3;
let x = &3;

Such as here

let x = String::from("abc"); //This is not copy.
match x {
    ref y => {},
    z => {},
}
// println!("{:?}", x);

This is equivalent to:

match x {
    y => {
        let y = &y;
        //Rest of code
    },
    z => {}
}

But in special cases, ref's effects can move into the match (or if let) statement's input:

match x {
    ref y => {}
}
println!("{:?}", x); //Allowed

Since this becomes:

match &x {
    y => {}
}
println!("{:?}", x);

As can be told, this is a bit messy, hence why it's being semi-ignored in most documentation and examples, but I am told there are still good uses for it in very specific cases.


So yes, your examples all do the same thing, but it's best to keep niche cases and the fact that combining ref and non-ref patterns adjacently can make the effects of ref change completely.

Since mixing & and ref can cause some unknown (for newbies like me) behavior I'm gonna avoid it. Thanks for the explanation.

See also my blog post on the matters.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.