Beginner · Ownership: Rust's big idea · topic 13 of 50 · 25 min
Ownership and functions
Explanation
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.
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}");
}main: name ──move──> greet: person ──> dropped at greet's closing brace
main: age ──copy──> show_age: years (main's age is still 24)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.
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!!
}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:
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");
}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.
Choosing a parameter type (preview)
| Parameter | Meaning | Caller can use the value after? |
|---|---|---|
| s: String | Take ownership | No (moved) |
| s: &String or s: &str | Borrow to read | Yes |
| s: &mut String | Borrow to change | Yes |
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.
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
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.
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());
}Practice (0/3 done)
Does this compile? fn eat(s: String) {} fn main() { let food = String::from("biryani"); eat(food); eat(food); }
What does this print? fn double(n: i32) -> i32 { n * 2 } fn main() { let a = 21; let b = double(a); println!("{a} {b}"); }
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Memory 101: the stack and the heapOwnership: Rust's big idea · same module
- Interview questions: ownership, borrowing and lifetimesRust careers and interviews
- References: another name for a variableC++ Programming (modern C++ and DSA) · References, pointers, arrays and strings
- Functions and multiple return valuesGo Programming (backend and cloud) · Control flow and functions
- Parameters in depth: optional, named, ref, out, params, overloadingC# and .NET · Decisions, loops and methods
- References and borrowingBorrowing, slices and lifetimes
Questions people ask about this
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.
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.
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.
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
- This is the last topic of “Ownership: Rust's big idea”. The module exam comes next.
- Module 3 exam: Ownership · 12 questions · 20 min, without help. 4 topics of this module still to finish first.
- Then the next module, Borrowing, slices and lifetimes. 50 topics left in the course before the final exam.