Ownership

Ownership Transfer from Functions

When a function returns a non-Copy value (-> String, -> Vec<T>), ownership of that value moves out of the function and into the caller's receiving variable. Because ownership is transferred out before the function's stack frame is popped, the returned heap buffer is not dropped when the function exits.

Creating and Returning Owned Values

In C, returning a dynamically allocated buffer from a function requires the caller to remember to call free() later. In Rust, a function can allocate a String or Vec<T> locally and return it by value; the caller becomes the sole owner and automatically drops it when the caller's scope ends:

Callee (`create_greeting`)                   Caller (`main`)
+----------------------------------+         +------------------------------------+
| fn create_greeting() -> String { |         | fn main() {                        |
|     let s = String::from("Hi");  |         |     let msg = create_greeting();   |
|     s ───────────────────────────+─(Move)─>|     println!("{msg}");             |
| } (NOT dropped at `}`!)          |         | } <-- `msg` is DROPPED here!       |
+----------------------------------+         +------------------------------------+
fn build_path(dir: &str, file: &str) -> String {
    let mut full = String::with_capacity(dir.len() + 1 + file.len());
    full.push_str(dir);
    full.push('/');
    full.push_str(file);
    full // Ownership moves out to the caller!
}

fn main() {
    let config_path: String = build_path("/etc/app", "settings.toml");
    println!("Constructed path: {config_path}");
} // `config_path` is dropped here at the end of `main`
Constructed path: /etc/app/settings.toml

Taking Ownership, Transforming in Place, and Returning Ownership

A function can accept ownership of a String or Vec<T> (mut buf: String), modify it in place using its existing heap capacity, and return ownership back to the caller:

fn redact_digits(mut text: String) -> String {
    // Modify the UTF-8 buffer in place and return ownership:
    unsafe {
        for byte in text.as_bytes_mut() {
            if byte.is_ascii_digit() {
                *byte = b'*';
            }
        }
    }
    text
}

fn main() {
    let pin_msg = String::from("Code: 4829");
    let safe_msg = redact_digits(pin_msg); // Moves in and moves back out!
    println!("{safe_msg}");
}
Code: ****

Return Value Optimization (RVO / NRVO)

When a function returns a large struct, array, or String by value, rustc and LLVM pass a hidden pointer to the caller's destination stack slot (the return slot) and construct the returned value directly inside the caller's stack frame, avoiding even a stack copy on return.