Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust by example: The ref pattern

Tags:

rust

I have problems understanding the ref pattern in Rust. I am referring to https://rustbyexample.com/scope/borrow/ref.html

Here is the code I don't understand:

let point = Point { x: 0, y: 0 };

let _copy_of_x = {
    // `ref_to_x` is a reference to the `x` field of `point`
    let Point { x: ref ref_to_x, y: _ } = point;

    // Return a copy of the `x` field of `point`
    *ref_to_x
};

I get that the last let expression(?) is some sort of pattern matching. So it's my understanding ref ref_to_x should be equal to 0, the x value of the original point.

But I don't understand what the ref actually does. When I add some code like this:

println!("x: {}", point.x);
println!("ref_to_x: {}", ref_to_x);
println!("*ref_to_x: {}", *ref_to_x);

I always get 0, so there doesn't seem to be a difference. Somehow I'd expect a memory address for ref_to_x while *ref_to_x might be the dereferenced value again.

I can replace both ref ref_to_x and *ref_to_x with myx and the code still works. What's the difference? What does ref do exactly?

edit: after reading dbaupps answer and doing some addition with ref_to_x and *ref_to_x things got a bit clearer; you cannot add an integer to ref_to_x because it's a reference. I guess I got confused because there is no indication of a reference when you print one.

like image 535
somesoaccount Avatar asked Jan 12 '15 22:01

somesoaccount


2 Answers

A reference created with ref is exactly the same as reference taken with &.

The difference is where they're allowed in the syntax. ref on the left side of an assignment is like adding & on the right side.

These expressions are equivalent:

let ref x1 = y;
let x2 = &y;

This redundancy exists because in pattern matching & is used to require that a reference exists already, rather than to make a new one:

let foo = 1;
match foo {
   ref x => {
       /* x == &1 */ 
       match x {
           &y => /* y == 1 */
       }
   },  
}

(discussion)

like image 164
Kornel Avatar answered Nov 15 '22 12:11

Kornel


ref creates a pointer into the piece of memory that is being matched on, in this case, ref_to_x is pointing directly to the memory that stores point.x, it is the same as writing let ref_to_x = &point.x in this case.

The pattern is extremely important, as it allows one to reach deep inside complicated data-structures without disturbing the ownership hierarchy. For example, if one has val: &Option<String>, writing

match *val {
    Some(s) => println!("the string is {}", s),
    None => println!("no string"
}

is not legal, it gives an error like:

<anon>:3:11: 3:15 error: cannot move out of borrowed content
<anon>:3     match *val {
                   ^~~~
<anon>:4:14: 4:15 note: attempting to move value to here
<anon>:4         Some(s) => {}
                      ^
<anon>:4:14: 4:15 help: to prevent the move, use `ref s` or `ref mut s` to capture value by reference
<anon>:4         Some(s) => {}
                      ^

It is not legal to take ownership (move) out of a borrowed value, because that would possibly damage the thing from which the value was borrowed (violating its invariants, causing data to disappear unexpectedly, etc.).

So, one can instead use a reference to just point in the memory with a borrowing & reference.


There's a slight subtlety here because (a) point isn't borrowed, so it is OK to move out of point (which consumes ownership of point too, meaning it can't be used later unless reinitialised), and (b) the type int is Copy, so doesn't move ownership when used by value. This is why using myx instead works fine. If the type of x was, say, String (which isn't Copy) and point was borrowed, then the ref will be necessary.

like image 34
huon Avatar answered Nov 15 '22 11:11

huon