Skip to content
Select theme

How to pass ownership of a value to a function

Short answer: a parameter whose type is the value’s own type — fn describe(text: String), not fn describe(text: &str) — takes ownership of the argument. Passing it in is a move, so the call is the last place your variable works: the next line that mentions it fails to compile with E0382. If the argument’s type is Copy, like i32, no move happens and the variable carries on as before.

One variable, two calls, no error — because the parameter’s type is Copy:

the function got 42
the function got 42
the caller still has 42

i32 is Copy, so the argument is duplicated into the parameter and score is untouched. Calling describe twice is fine. How to tell whether a type will move or copy is the test for any type, including your own.

Change the parameter’s type — String instead of i32 — and the second call stops compiling:

error[E0382]: use of moved value: `message`
--> src/main.rs:9:14
|
6 | let message = String::from("hello");
| ------- move occurs because `message` has type `String`, which does not implement the `Copy` trait
7 |
8 | describe(message);
| ------- value moved here
9 | describe(message);
| ^^^^^^^ value used here after move
|
note: consider changing this parameter type in function `describe` to borrow instead if owning the value isn't necessary
--> src/main.rs:1:19
|
1 | fn describe(text: String) {
| -------- ^^^^^^ this parameter takes ownership of the value
| |
| in this function
help: consider cloning the value if the performance cost is acceptable
|
8 | describe(message.clone());
| ++++++++
For more information about this error, try `rustc --explain E0382`.

The complaint is about message, not about describe. The first call took ownership on line 8 — value moved here, printed under the argument, because the call is where the move happens — so line 9 reads a binding that owns nothing.

The note: is the part worth reading twice. rustc describes the parameter as this parameter takes ownership of the value, and its first suggestion is to make the parameter a borrow, which is the last section of this page. The second suggestion, message.clone(), compiles: every call gets a buffer of its own and message keeps the original.

The error tells you a move happened. A destructor tells you when, which is the other way to check that the callee really did take the value:

caller buffer: 0x102f1d0d0
consume buffer: 0x102f1d0d0
consume text: "report"
dropping "report"
after the call

Both addresses are the same, so no second buffer was allocated on the way in: the text stayed where it was, and the parameter is that buffer under a new name — the same thing that happens when you move between two bindings in one function (How move semantics work in Rust puts a byte count on it). The hex digits differ every run; that they match is the part that holds.

Then dropping "report" lands before after the call. item is a binding in consume, so it goes out of scope when consume returns and the free happens during the call rather than at the end of main. That’s the callee’s side of the handover, and it’s the thing to look at when you can’t tell whether a function took the value or borrowed it.

A & on the parameter is what the note: was suggesting. The call gets a reference, and ownership stays where it is:

the function got hello
the function got hello
the caller still has hello

Both calls work and message is still usable after them. & is a borrow: no ownership changes hands, and nothing is freed when describe returns. That’s the subject of the Borrowing book — this page stops at the rule that the parameter’s type is what decides.