Ownership

Return Values and Ownership

Returning values from a function is one of the primary ways Rust programs transfer ownership across scopes without cloning. Whether a function returns a newly constructed resource, returns a modified input value, or returns a tuple bundling an owned value alongside a computed result, ownership flows deterministically from the callee's return expression into the caller's receiving binding.

Returning Multiple Owned Values via Tuples

When a function takes ownership of a value (or constructs multiple resources) and needs to return both the original owned value and a computed result, it can return a tuple (OwnerType, ResultType):

fn append_and_measure(mut text: String, suffix: &str) -> (String, usize) {
    text.push_str(suffix);
    let new_len = text.len();
    (text, new_len) // Moves `text` and copies `new_len` out to the caller
}

fn main() {
    let initial = String::from("Rust");
    let (updated, byte_len) = append_and_measure(initial, " 2024");

    println!("Updated: '{updated}' ({byte_len} bytes)");
}
Updated: 'Rust 2024' (9 bytes)

Note: When a function only needs to inspect or mutate a value without storing it elsewhere, prefer taking &T or &mut T over returning ownership in a tuple. Reserve (Self, Output) returns for builder methods or state-transition APIs that intentionally consume self by value.

What Happens When a Caller Ignores a Returned Owned Value

Even if a caller calls a value-returning function as a standalone statement (create_buffer();) without binding the return value to a let variable, the returned value is never leaked!

Rust places the returned value into an unnamed temporary slot in the caller and drops it immediately at the semicolon ;:

struct TempBuffer(&'static str);

impl Drop for TempBuffer {
    fn drop(&mut self) {
        println!("  [Drop] {}", self.0);
    }
}

fn make_buffer(name: &'static str) -> TempBuffer {
    TempBuffer(name)
}

fn main() {
    let _kept = make_buffer("1. bound to `_kept` (lives until end of main)");
    make_buffer("2. ignored return value (dropped immediately at `;`!)");
    println!("Continuing main...");
}
  [Drop] 2. ignored return value (dropped immediately at `;`!)
Continuing main...
  [Drop] 1. bound to `_kept` (lives until end of main)