Skip to content

Beginner · Ownership: Rust's big idea · topic 13 of 50 · 25 min

Ownership and functions

Explanation

Passing a value to a function works like assignment: a String is moved into the function and dropped when the function ends, while Copy types are copied. Returning a value moves ownership back to the caller. Passing ownership in and out all the time is clumsy, which is exactly why Rust has references (next module).

Function calls follow the same rules

A function parameter is just a new variable in the function's scope. So calling a function with a value is like writing let param = value;. For a String, that is a move. For an i32, it is a copy.

rust
fn main() {
    let name = String::from("Kavya");
    let age = 24;

    greet(name);           // name is MOVED into greet
    show_age(age);         // age is COPIED

    // println!("{name}");  // error: borrow of moved value: name
    println!("{age}");     // fine: 24
}

fn greet(person: String) {
    println!("Hello, {person}");
}                          // person goes out of scope here: the String is dropped

fn show_age(years: i32) {
    println!("Age {years}");
}
After greet(name), the heap memory has already been freed inside greet.
output
main: name ──move──> greet: person ──> dropped at greet's closing brace
main: age  ──copy──> show_age: years    (main's age is still 24)
Two calls, two different outcomes.

Returning gives ownership back

A function can return a value, which moves ownership to the caller. A function can also create a String and hand it out.

rust
fn make_greeting(name: &str) -> String {
    let text = format!("Welcome, {name}!");
    text                      // ownership moves to the caller
}

fn add_exclamation(mut s: String) -> String {
    s.push('!');
    s                         // give it back
}

fn main() {
    let g = make_greeting("Arjun");
    let g = add_exclamation(g);   // move in, move back out
    println!("{g}");              // Welcome, Arjun!!
}
format! builds a new String, like println! but without printing. mut s lets the function change its own copy.

The clumsy workaround

Suppose you want a function that tells you the length of a String, and you still want to use the String afterwards. With ownership alone, you would have to pass it in and return it back, together with the answer, in a tuple:

rust
fn length(s: String) -> (String, usize) {
    let n = s.len();
    (s, n)                    // hand the String back with the answer
}

fn main() {
    let s1 = String::from("Hyderabad");
    let (s1, n) = length(s1);
    println!("{s1} has {n} bytes");
}
It works, but it is tiring. Imagine doing this for every function call.

This is exactly the problem references solve. A reference lets a function look at (borrow) a value without taking ownership. The real version is: fn length(s: &String) -> usize, called as length(&s1). You will learn borrowing in the next module, and it will feel like a relief.

Think of it like this: Passing ownership is like giving your notebook to a classmate so they can count the pages: they keep it unless they bring it back. Borrowing is like letting them look at it while you hold it: they count, and you still have it.

Choosing a parameter type (preview)

ParameterMeaningCaller can use the value after?
s: StringTake ownershipNo (moved)
s: &String or s: &strBorrow to readYes
s: &mut StringBorrow to changeYes

Common mistakes and fixes

  • Using a value after passing it to a function (E0382). Fix: if the function only reads it, change the parameter to a reference; or pass a clone; or return it back.
  • Calling a function in a loop with an owned String: for _ in 0..3 { print_name(name); }. The first call moves it. Fix: borrow with &name.
  • Forgetting that a function that takes ownership will drop the value at its end. If you need it later, the function must return it, or borrow instead.
Tip: A good rule of thumb: a function should take ownership (String) only if it needs to keep or consume the value, for example storing it in a struct or a list. If it only reads, take a reference.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Ownership and functions · 1/7

call = let param = value

Passing a value to a function follows the same rules as assignment.

Prefer a video by a teacher? Find free videos on “rust ownership functions passing and returning values” on YouTube ↗

In real life: Building an SMS message

A school's fee reminder system builds each SMS text in one function and returns the String, so ownership moves to the caller, who then moves it into the sending queue. The student's roll number (an integer) is just copied into the function, so the caller keeps using it.

rust
fn reminder(name: &str, roll: u32, due: u32) -> String {
    format!("Dear parent of {name} (roll {roll}), fee of Rs {due} is due.")
}

fn main() {
    let roll = 17;
    let msg = reminder("Ishaan", roll, 4500);
    let mut queue = Vec::new();
    queue.push(msg);              // msg moves into the queue
    println!("{} message(s) queued for roll {roll}", queue.len());
}
Prints: 1 message(s) queued for roll 17

Practice (0/3 done)

Task 1 · Question

Does this compile? fn eat(s: String) {} fn main() { let food = String::from("biryani"); eat(food); eat(food); }

Task 2 · Question

What does this print? fn double(n: i32) -> i32 { n * 2 } fn main() { let a = 21; let b = double(a); println!("{a} {b}"); }

Task 3 · Practice

Write fn shout(s: String) -> String that returns the text in upper case with "!" at the end (use to_uppercase() and push). In main, pass a String in, get it back, and print it. Then write the clumsy fn count_vowels(s: String) -> (String, usize) and use it without losing the String.

Quick quiz

Question 1

1.fn take(s: String) {} What happens to the String at the end of take?

Question 2

2.How does a function give ownership back to the caller?

Question 3

3.Which parameter type lets a function read a String while the caller keeps it?

Question 4

4.Why is passing a u32 to a function never a "moved value" problem?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

fn take(s: String) {} What happens to the String at the end of take?

Answer: It is dropped

take owns s, and s goes out of scope at take's closing brace.

How does a function give ownership back to the caller?

Answer: By returning it

Returning a value moves its ownership to the caller.

Which parameter type lets a function read a String while the caller keeps it?

Answer: s: &String

A reference borrows the value without taking ownership.

Why is passing a u32 to a function never a "moved value" problem?

Answer: u32 is Copy, so it is copied

Copy types are copied into the parameter.

How much data does a move of a 1 MB String copy?Related

Answer: Only the small stack part (pointer, length, capacity)

A move copies the three stack numbers; the heap bytes stay where they are.

From The ownership rules and moves

What is the best fix for "returns a reference to data owned by the current function"?Related

Answer: Return an owned value

Move ownership out instead of borrowing a local.

From Interview questions: ownership, borrowing and lifetimes

What is wrong with: const std::string& name() { std::string s = "Asha"; return s; }Related

Answer: It returns a reference to a local that is destroyed: a dangling reference

s dies when the function returns. Return std::string by value.

From References: another name for a variable

How does a Go function usually report failure?Related

Answer: By returning an error value as the last result

Go functions return an error as the last result; nil means success.

From Functions and multiple return values

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Ownership: Rust's big idea”. The module exam comes next.
  2. Module 3 exam: Ownership · 12 questions · 20 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Borrowing, slices and lifetimes. 50 topics left in the course before the final exam.

← Previous: Copy and Clone