RustArena

Step 5 of 6

Ownership and functions

Passing a value to a function works exactly like assigning it to a variable: a String argument moves into the parameter, and an i32 argument is copied. The parameter is the new owner, and its scope is the function's body.

fn print_ticket(ticket: String) {   // ticket takes ownership
    println!("Ticket: {}", ticket);
}                                   // ticket goes out of scope: the String is dropped

fn print_seat(seat: u32) {          // seat gets a copy
    println!("Seat: {}", seat);
}

fn main() {
    let t = String::from("T-42");
    let s = 17;
    print_ticket(t);                // t moves into the function
    print_seat(s);                  // s is copied
    println!("seat {} is still usable here", s);
    // println!("{}", t);           // error: t was moved
}
Ticket: T-42
Seat: 17
seat 17 is still usable here

Call print_ticket(t) twice and the second call fails:

fn print_ticket(ticket: String) {
    println!("Ticket: {}", ticket);
}

fn main() {
    let t = String::from("T-42");
    print_ticket(t);
    print_ticket(t);
}
error[E0382]: use of moved value: `t`
  |
6 |     let t = String::from("T-42");
  |         - move occurs because `t` has type `String`, which does not implement the `Copy` trait
7 |     print_ticket(t);
  |                  - value moved here
8 |     print_ticket(t);
  |                  ^ value used here after move
  |
note: consider changing this parameter type in function `print_ticket` to borrow instead if owning the value isn't necessary

The note is the compiler pointing at the next module: a function that only reads a value usually shouldn't take ownership of it.

Giving ownership back

A function can also return a value, which moves it out to the caller. Creating a value inside a function and returning it is completely normal: it isn't dropped at the function's } because it has already moved out.

fn new_ticket(number: u32) -> String {
    let ticket = format!("T-{}", number);
    ticket                              // moves out to the caller
}

fn add_vip(mut ticket: String) -> String {
    ticket.push_str(" (VIP)");
    ticket                              // given back
}

fn main() {
    let t = new_ticket(7);
    let t = add_vip(t);                 // move in, get it back
    println!("{}", t);
}
T-7 (VIP)

Two details:

Returning several values

If a function needs to look at a String and return a result, it has to give the String back as well, for example in a tuple:

fn measure(text: String) -> (String, usize) {
    let n = text.chars().count();
    (text, n)
}

fn main() {
    let msg = String::from("good game");
    let (msg, n) = measure(msg);
    println!("'{}' has {} characters", msg, n);
}
'good game' has 9 characters

It works, but it's clumsy: every function that just wants to look at a value has to hand it back. That's exactly the problem borrowing, the next module, solves.

drop is just a function

You can drop a value early by passing it to drop, a function from the standard library that's always available:

fn main() {
    let report = String::from("imagine 50 MB of text here");
    println!("report has {} bytes", report.len());
    drop(report);                  // freed now, not at the end of main
    println!("memory released");
}
report has 26 bytes
memory released

There's no magic in it. Its whole definition is pub fn drop<T>(_x: T) {}, with an empty body. The value moves into the parameter _x, the function ends, _x goes out of scope, and the value is dropped. Ownership does the rest. (The <T> means "a value of any type"; you'll learn that syntax in the generics module.)

Common mistakes

Your task

Write mark_paid. It takes ownership of an order description, appends [PAID] (with a leading space) and gives the String back. For example, mark_paid(String::from("#17 2x tacos")) returns "#17 2x tacos [PAID]".

Challenge 2

char_count takes ownership of the message and never gives it back, so main can't print it afterwards. Without .clone(), change char_count to return the message and the count as a tuple (String, usize), and destructure it in main with let (message, n) = char_count(message);. The program should print:

gg wp has 5 characters

Challenge 3

Write longer, which takes ownership of two Strings and returns the longer one (by .len()); if they're the same length, return a. The other one is dropped when the function ends: nothing else to do.

Previous: Copy types and clone Next: Ownership and collections