Doubt regarding ownership in Rust

I went through the ownership chapter in the rust book, then decided to write some code to see it in action.
I wrote the following code -

fn test()-> u32{
    let x  = 5;
    print!("The value of x is: {}", x);
    return x;
}

and was calling test function in main.

Expected behavour according to me was to fail because print would have taken ownership of x and x would be no longer valid after that.

Actual behavious is it is compiling perfectly.

Can some help me how I am thinking wrong?

u32 implements the Copy trait (meaning it can be duplicately by simply copying bits in memory).

When a value has a type that is Copy and you try to move ownership of it, it just gets copied instead, with the original version still being accessible.

This means that using Copy types (like numeric primitives, or booleans, or char) isn't ideal for understanding how ownership works. You would be better off using something like String for these kinds of examples.

That said, if you change your example to use String, it still works, so what gives?!

This is because the print macro does some magic under the hood that allows it to read variables by reference, even if it looks like you're passing them by value (yes, this is a bit confusing).

This example better illustrates a change of ownership:

fn test() -> String {
    let x = String::from("foo");
    std::mem::drop(x); // here, ownership of 'x' is moved into the 'drop' function
    return x;
}
error[E0382]: use of moved value: `x`
 --> src/lib.rs:4:12
  |
2 |     let x = String::from("foo");
  |         - move occurs because `x` has type `String`, which does not implement the `Copy` trait
3 |     std::mem::drop(x);
  |                    - value moved here
4 |     return x;
  |            ^ value used here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     std::mem::drop(x.clone());
  |                     ++++++++
6 Likes

Thank you for the reply @17cupsofcoffee
Is String also a primitive type?

fn test()-> String{
    let x  = String::from("Hello");
    println!("The value of x is: {}", x);
    return x;
}

As this code is also compiling

In addition to what @17cupsofcoffee explained the println! macro does not take ownership of its arguments but only accesses them by-reference.

Edit: Looks like everyone was working on the same kind of follow-up question (writing or answering it respectively) at the same time.

3 Likes

Good spot - I've updated my original reply to cover that too :slight_smile:

Is there a way I can identify which functions which take ownership and which don't? Looking at documentation there is no specific mention.

For a function or method, you can tell from the argument type:

  • Something / self takes ownership,
  • &mut Something / &mut self takes an exclusive reference, and
  • &Something / &self takes a shared reference

Macros (foo!(...)) can sort of do whatever they want, so you just have to read their documentation.


The actual details are a little bit more complicated...

Really, functions and methods always take ownership, but they might be taking ownership of a reference type— It's the & and &mut operators that actually borrow a value. This is obscured a bit for method receivers (self), which can have these referencing operators automatically inserted by the compiler to make the types match up.

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.