treecoder
treecoder

Reputation: 45081

Understanding bindings and borrows

I have the following simple program

fn main() {
    let a = 10;
    let b: i32;
    let r: &i32;

    b = a;      // move?
    r = &a;     // borrow?

    println!("{}", a);
    println!("{}", b);
    println!("{}", r);
    println!("{}", &r);
    println!("{}", *r);
}

The output is

10
10
10
10
10
  1. The first print does not fail even when the value is moved. Is this because of primitive type or am I missing something?
  2. The second print seems ok.
  3. The third one prints a reference directly - shouldn't we get the memory address as this is a reference?
  4. The fourth print is a reference to a reference, which should print a memory address, I think?
  5. The fifth print seems reasonable as (I think) * is the value at operator that de-references the reference.

It seems I am not quite getting the whole thing.

Please explain in detail what's going on.

Related: Move vs Copy in Rust

Upvotes: 0

Views: 124

Answers (3)

Shepmaster
Shepmaster

Reputation: 430554

The other answers are mostly right, but have some small errors.

1. i32 implements Copy, so when you assign it to a second variable binding, the first binding does not need to be invalidated. Any type that implements Copy will have this property.

3. You have asked to format the value with {} which corresponds to the Display trait. There is an implementation of this trait for references to types that implement Display:

impl<'a, T> Display for &'a T where T: Display + ?Sized {
    fn fmt(&self, f: &mut Formatter) -> Result { Display::fmt(&**self, f) }
}

This simply delegates to the implementation of the referred-to type.

4. The same as #3 - a reference to a reference to a type that implements Display will just delegate twice. Deref does not come into play.


Here's the sneaky thing that no one else has mentioned. println! is a macro, which means it has more power than a regular function call. One of the things that it does is automatically take a reference to any arguments. That's what allows you to print out a value that doesn't implement Copy without losing ownership.

With this code:

let a = 10;
println!("{}", a);

The expanded version is actually something like this (slightly cleaned up):

let a = 10;

static __STATIC_FMTSTR: &'static [&'static str] = &["", "\n"];

::std::io::_print(::std::fmt::Arguments::new_v1(__STATIC_FMTSTR, &match (&a,) {
    (__arg0,) => [::std::fmt::ArgumentV1::new(__arg0, ::std::fmt::Display::fmt)],
}));

Therefore, everything passed to println! is a reference. It wouldn't be very useful if references printed out memory addresses.

Besides the usefulness, Rust focuses more on value semantics as opposed to reference semantics. When you have values moving and changing addresses frequently, the location of the value isn't very consistent or useful.

See also

Upvotes: 1

Lukas Kalbertodt
Lukas Kalbertodt

Reputation: 88556

Regarding 1: Yes, because it's a primitive variable, more specifically a type that implements the Copy trait. All those Copy-types work with copy semantics instead of move semantics.

Regarding 3: println! automatically dereferences it's arguments -- this is what the user wants in 99% of all cases.

Regarding 4: Again, automatically dereferences arguments... until it's a non-reference type.

Upvotes: 2

tafia
tafia

Reputation: 1562

1, 2 => You are working with i32, which is Copy, so in practice b = a.clone()

3, 4, 5 => You're confused with the Deref trait. I find it easier to reason about ownership/borrowing than references in rust. r = &a means r borrows a so I can access its value later on, someone else will own it and take care of dropping it

Upvotes: 2

Related Questions