ZetCode

Rust ownership and borrowing

last modified October 25, 2026

In this article we explain ownership, borrowing, and slices in Rust.

Ownership is what makes Rust unique among mainstream programming languages. Most languages fall into one of two camps: either they use a garbage collector that periodically frees unused memory (Go, Java, Python), or they require the programmer to manually allocate and free memory (C, C++). Both approaches have downsides. Garbage collectors add runtime overhead and unpredictable pauses, while manual management is error-prone and leads to bugs such as use-after-free, double-free, and memory leaks.

Rust takes a third path. The compiler tracks who owns each piece of data through a set of rules checked at compile time. There is no garbage collector, no runtime cost, and no manual malloc/free. If the code violates the ownership rules, it does not compile. This eliminates entire classes of bugs before the program ever runs.

The ownership system is built on three rules:

Closely related to ownership are borrowing and slices. Borrowing lets us use a value without taking ownership of it, via references (&T for shared access and &mut T for exclusive mutable access). Slices are references to a contiguous part of a collection.

A move transfers ownership of a value from one binding to another, for example in an assignment or when passing an argument by value. A move does not copy the data on the heap; it only changes who is responsible for it. After the move, the previous binding is invalid, and any later use is rejected at compile time. A borrow, by contrast, creates a reference that grants temporary access, while the owner keeps the value.

Concept Ownership Aliasing Mutability After use
Move Transferred No aliasing Depends on new owner Old binding invalid
Shared borrow (&T) Retained by owner Many aliases allowed Read-only Ends after the last use of the reference
Mutable borrow (&mut T) Retained by owner Exclusive alias Read/write Ends after the last use of the reference

The table summarizes how moves and borrows differ. The examples were tested with Rust 1.97 and the 2024 edition and are run as Cargo projects with cargo run -q. The error messages are the real output of the compiler.

Scope and drop

A variable is valid from its declaration to the end of the block in which it is defined. When the variable goes out of scope, Rust calls the drop method of its type automatically and frees the resources.

main.rs
struct Resource {
    name: String,
}

impl Drop for Resource {
    fn drop(&mut self) {
        println!("dropping {}", self.name);
    }
}

fn main() {
    let _a = Resource { name: String::from("a") };
    println!("created a");

    {
        let _b = Resource { name: String::from("b") };
        println!("created b");
    }
    println!("left the inner block");

    let _c = Resource { name: String::from("c") };
    println!("created c");
}

The Resource type implements the Drop trait to report when a value is dropped.

impl Drop for Resource {
    fn drop(&mut self) {
        println!("dropping {}", self.name);
    }
}

The drop method runs just before the memory of the value is released. We never call it directly; the compiler inserts the call at the right place.

{
    let _b = Resource { name: String::from("b") };
    println!("created b");
}

The resource b is dropped at the closing brace of the inner block, before the program prints "left the inner block". The resources a and c are dropped when main ends, in the reverse order of their creation: c first, then a. The leading underscore of the names tells the compiler that the unused variables are intentional. This deterministic cleanup is known as RAII; it works for memory, files, sockets, and locks alike.

$ cargo run -q
created a
created b
dropping b
left the inner block
created c
dropping c
dropping a

Move semantics

When we assign a value that owns heap data to another variable, ownership is moved.

main.rs
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{s2}");
}

The String is moved from s1 to s2.

let s2 = s1;

A String consists of a pointer to the characters on the heap, a length, and a capacity. The three fields live on the stack. The assignment copies these fields to s2 but not the characters on the heap. If both variables were valid, they would both try to free the same heap memory when they go out of scope, which is a double free. To prevent it, Rust considers s1 invalid after the assignment, so only s2 drops the string.

$ cargo run -q
hello

Using a moved value

Using a variable after its value has been moved is a compile-time error.

main.rs
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{s1}, {s2}");
}

We use both s1 and s2 after the assignment.

let s2 = s1;

println!("{s1}, {s2}");

The compiler reports the error E0382: borrow of moved value. It points to the place where the type is declared not to implement Copy, to the move, and to the later use, and it suggests cloning the value. The error appears at compile time; the program never runs.

$ cargo run -q
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:16
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |
5 |     println!("{s1}, {s2}");
  |                ^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let s2 = s1.clone();
  |                ++++++++

For more information about this error, try `rustc --explain E0382`.

Ownership and functions

Passing a value to a function follows the same rules as assignment: the value is moved into the parameter unless its type is Copy. Returning a value from a function moves it to the caller.

main.rs
fn takes_ownership(s: String) {
    println!("got {s}");
}

fn makes_copy(n: i32) {
    println!("got {n}");
}

fn gives_ownership() -> String {
    String::from("yours now")
}

fn takes_and_gives_back(s: String) -> String {
    s + "!"
}

fn main() {
    let s1 = String::from("hello");
    takes_ownership(s1);

    let n = 42;
    makes_copy(n);
    println!("n is still {n}");

    let s2 = gives_ownership();
    println!("{s2}");

    let s3 = takes_and_gives_back(s2);
    println!("{s3}");
}

We pass values to functions and get values back.

fn takes_ownership(s: String) {
    println!("got {s}");
}

The parameter s becomes the owner of the string. When the function ends, s goes out of scope and the string is dropped.

let n = 42;
makes_copy(n);
println!("n is still {n}");

An i32 is copied, so n remains valid after the call.

fn gives_ownership() -> String {
    String::from("yours now")
}

The function creates a string and moves it out to the caller. The value is not dropped at the end of the function, because it now has a new owner.

fn takes_and_gives_back(s: String) -> String {
    s + "!"
}

A function can take a value, change it, and return it. The caller must re-bind the result: s3 is the new owner and s2 is moved. Passing ownership back and forth is tedious, which is why we usually use references instead.

$ cargo run -q
got hello
got 42
n is still 42
yours now
yours now!

Using a value after passing it to a function

The caller cannot use a value that it has moved into a function.

main.rs
fn takes_ownership(s: String) {
    println!("got {s}");
}

fn main() {
    let s = String::from("hello");
    takes_ownership(s);

    println!("{s}");
}

We print the string after takes_ownership has consumed it.

takes_ownership(s);

println!("{s}");

The compiler reports E0382 again. This time it also points to the function parameter and suggests changing the parameter to a borrow if owning the value is not necessary. This is usually the right fix.

$ cargo run -q
error[E0382]: borrow of moved value: `s`
 --> src/main.rs:9:16
  |
6 |     let s = String::from("hello");
  |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
7 |     takes_ownership(s);
  |                     - value moved here
8 |
9 |     println!("{s}");
  |                ^ value borrowed here after move
  |
note: consider changing this parameter type in function `takes_ownership` to borrow instead if owning the value isn't necessary
 --> src/main.rs:1:23
  |
1 | fn takes_ownership(s: String) {
  |    ---------------    ^^^^^^ this parameter takes ownership of the value
  |    |
  |    in this function
help: consider cloning the value if the performance cost is acceptable
  |
7 |     takes_ownership(s.clone());
  |                      ++++++++

For more information about this error, try `rustc --explain E0382`.

Clone

When we need two independent copies of heap data, we call clone. Unlike a move, clone duplicates the data, so both variables remain valid.

main.rs
fn main() {
    let s1 = String::from("hello");
    let mut s2 = s1.clone();
    s2.push_str(", world");

    println!("s1 = {s1}");
    println!("s2 = {s2}");
}

We clone a string and change the copy.

let mut s2 = s1.clone();
s2.push_str(", world");

The clone owns its own heap buffer. Changing s2 does not affect s1, and dropping one does not affect the other. Cloning is explicit and can be expensive, because it allocates memory and copies the data, so we use it deliberately and not to silence the compiler.

$ cargo run -q
s1 = hello
s2 = hello, world

Copy types

Simple types that live entirely on the stack implement the Copy trait. Assigning such a value copies its bits, and both variables remain usable.

main.rs
#[derive(Debug, Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let x: i32 = 5;
    let y = x;
    println!("x = {x}, y = {y}");

    let flag = true;
    let other = flag;
    println!("flag = {flag}, other = {other}");

    let pair = (1, 'a');
    let pair2 = pair;
    println!("{pair:?} {pair2:?}");

    let p1 = Point { x: 1, y: 2 };
    let p2 = p1;
    println!("{p1:?} {p2:?} {}", p1.x + p2.y);
}

We assign integers, booleans, a tuple, and a struct that implements Copy.

let x: i32 = 5;
let y = x;

The value of x is copied. Copy types include all integer and floating-point types, bool, char, shared references &T, and tuples and arrays of Copy types. String and Vec<T> are not Copy, because they own heap memory.

#[derive(Debug, Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

Our own types can be Copy if we derive both Clone and Copy. The tuple (1, 'a') is Copy because both of its elements are. A mutable reference &mut T is not Copy.

$ cargo run -q
x = 5, y = 5
flag = true, other = true
(1, 'a') (1, 'a')
Point { x: 1, y: 2 } Point { x: 1, y: 2 } 3

Copy requires Copy fields

A struct can be Copy only if all its fields are.

main.rs
#[derive(Debug, Clone, Copy)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    let p = Person { name: String::from("Alice"), age: 30 };
    println!("{p:?}");
}

We derive Copy for a struct with a String field.

#[derive(Debug, Clone, Copy)]
struct Person {
    name: String,
    age: u32,
}

The compiler reports E0204: the trait Copy cannot be implemented for this type, and it marks the field that does not implement it. A type that owns resources must not be copied bit by bit; a type that implements Drop cannot be Copy for the same reason.

$ cargo run -q
error[E0204]: the trait `Copy` cannot be implemented for this type
 --> src/main.rs:2:8
  |
1 | #[derive(Debug, Clone, Copy)]
  |                        ---- in this derive macro expansion
2 | struct Person {
  |        ^^^^^^
3 |     name: String,
  |     ------------ this field does not implement `Copy`

For more information about this error, try `rustc --explain E0204`.

Moves in loops

A for loop calls into_iter on the value after in. With a vector, this moves the vector into the loop, and each element is moved out in turn. With a reference to the vector, the loop borrows the elements.

main.rs
fn main() {
    let names = vec![String::from("Alice"), String::from("Bob")];

    for name in &names {
        println!("borrowed {name}");
    }
    println!("still have {} names", names.len());

    for name in names {
        println!("owned {name}");
    }
}

We loop over a vector twice.

for name in &names {
    println!("borrowed {name}");
}

Here name is a &String. The vector is only borrowed, so we can use it after the loop.

for name in names {
    println!("owned {name}");
}

Here name is a String. The loop consumes the vector; each string is dropped at the end of its iteration. We cannot use names afterwards.

$ cargo run -q
borrowed Alice
borrowed Bob
still have 2 names
owned Alice
owned Bob

Using a vector after a consuming loop

The compiler enforces the consumption.

main.rs
fn main() {
    let names = vec![String::from("Alice"), String::from("Bob")];

    for name in names {
        println!("owned {name}");
    }

    println!("{}", names.len());
}

We call len on a vector that was moved into a loop.

for name in names {
    println!("owned {name}");
}

println!("{}", names.len());

The error message says that names was moved due to the implicit call to into_iter, which takes self by value. It suggests iterating over a slice of the vector (&names) to avoid the move. The note refers to the source of the standard library; the path depends on where Rust is installed.

$ cargo run -q
error[E0382]: borrow of moved value: `names`
   --> src/main.rs:8:20
    |
  2 |     let names = vec![String::from("Alice"), String::from("Bob")];
    |         ----- move occurs because `names` has type `Vec<String>`, which does not implement the `Copy` trait
  3 |
  4 |     for name in names {
    |                 ----- `names` moved due to this implicit call to `.into_iter()`
...
  8 |     println!("{}", names.len());
    |                    ^^^^^ value borrowed here after move
    |
note: `into_iter` takes ownership of the receiver `self`, which moves `names`
   --> /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/collect.rs:312:18
    |
312 |     fn into_iter(self) -> Self::IntoIter;
    |                  ^^^^
help: consider iterating over a slice of the `Vec<String>`'s content to avoid moving into the `for` loop
    |
  4 |     for name in &names {
    |                 +

For more information about this error, try `rustc --explain E0382`.

Moving out of a vector

An element of a vector cannot be moved out by indexing, because that would leave a hole in the vector.

main.rs
fn main() {
    let names = vec![String::from("Alice"), String::from("Bob")];

    let first = names[0];

    println!("{first}");
}

We try to take the first string out of a vector.

let first = names[0];

The compiler reports E0507: cannot move out of index of Vec<String>. It suggests borrowing the element with &names[0] or cloning it. To really take an element out, we can use remove, swap_remove, or pop, which return the owned value and fix up the vector. Elements of Copy types, such as integers, can be read by indexing.

$ cargo run -q
error[E0507]: cannot move out of index of `Vec<String>`
 --> src/main.rs:4:17
  |
4 |     let first = names[0];
  |                 ^^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
  |
help: consider borrowing here
  |
4 |     let first = &names[0];
  |                 +
help: consider cloning the value if the performance cost is acceptable
  |
4 |     let first = names[0].clone();
  |                         ++++++++

For more information about this error, try `rustc --explain E0507`.

Partial moves

A field can be moved out of a struct. The struct is then partially moved: the moved field is gone, but the other fields can still be used.

main.rs
#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    let person = Person { name: String::from("Alice"), age: 30 };

    let name = person.name;
    println!("moved the name: {name}");
    println!("the age is still available: {}", person.age);
}

We move the name out of a Person and read the age.

let name = person.name;
println!("the age is still available: {}", person.age);

The name field is moved to a new variable. The age field is a Copy type and was never moved, so it can be read. The next example shows what happens when we use the whole struct.

$ cargo run -q
moved the name: Alice
the age is still available: 30

Using a partially moved struct

The struct as a whole cannot be used after one of its fields has been moved.

main.rs
#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    let person = Person { name: String::from("Alice"), age: 30 };

    let name = person.name;

    println!("{name}");
    println!("{person:?}");
}

We print the whole struct after moving its name.

let name = person.name;

println!("{person:?}");

The compiler reports E0382: borrow of partially moved value. The Debug output would need the moved field. To avoid the problem we borrow the field with &person.name or clone it.

$ cargo run -q
error[E0382]: borrow of partially moved value: `person`
  --> src/main.rs:13:16
   |
10 |     let name = person.name;
   |                ----------- value partially moved here
...
13 |     println!("{person:?}");
   |                ^^^^^^ value borrowed here after partial move
   |
   = note: partial move occurs because `person.name` has type `String`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0382`.

Moving out of a reference

A value cannot be moved out of a reference, because the reference does not own it.

main.rs
struct Task {
    name: String,
}

fn main() {
    let task = Task { name: String::from("write") };
    let r = &task;

    let name = r.name;

    println!("{name}");
}

We try to move a field out through a shared reference.

let r = &task;
let name = r.name;

The compiler reports E0507: cannot move out of r.name, which is behind a shared reference. The owner would be left with an invalid field. We must borrow (&r.name) or clone (r.name.clone()).

$ cargo run -q
error[E0507]: cannot move out of `r.name` which is behind a shared reference
 --> src/main.rs:9:16
  |
9 |     let name = r.name;
  |                ^^^^^^ move occurs because `r.name` has type `String`, which does not implement the `Copy` trait
  |
help: consider borrowing here
  |
9 |     let name = &r.name;
  |                +
help: consider cloning the value if the performance cost is acceptable
  |
9 |     let name = r.name.clone();
  |                      ++++++++

For more information about this error, try `rustc --explain E0507`.

Taking values out of mutable references

Through a mutable reference we can swap a new value in and take the old one out. The standard library has helpers for this: Option::take, mem::replace, and mem::take.

main.rs
use std::mem;

#[derive(Debug)]
struct Task {
    name: String,
    notes: Option<String>,
}

fn main() {
    let mut task = Task {
        name: String::from("write article"),
        notes: Some(String::from("check the examples")),
    };

    let notes = task.notes.take();
    println!("took {notes:?}, left {:?}", task.notes);

    let old = mem::replace(&mut task.name, String::from("publish article"));
    println!("replaced {old:?} with {:?}", task.name);

    let name = mem::take(&mut task.name);
    println!("took {name:?}, left {:?}", task.name);

    println!("{task:?}");
}

We take values out of a struct without moving the struct itself.

let notes = task.notes.take();

The take method of Option returns the current value and leaves None in its place.

let old = mem::replace(&mut task.name, String::from("publish article"));
let name = mem::take(&mut task.name);

The mem::replace function puts a new value into the place and returns the old one. The mem::take function replaces the value with the default of its type, an empty string here. In all three cases the struct stays valid: no field is ever left without a value.

$ cargo run -q
took Some("check the examples"), left None
replaced "write article" with "publish article"
took "publish article", left ""
Task { name: "", notes: None }

Borrowing

Instead of moving a value, we can pass a reference to it. This is called borrowing. The original owner keeps the value, and the reference is valid while the owner is.

main.rs
fn calculate_length(s: &str) -> usize {
    s.len()
}

fn print_twice(s: &str) {
    println!("{s} {s}");
}

fn main() {
    let s = String::from("hello");

    let len = calculate_length(&s);
    println!("the length of '{s}' is {len}");

    let r1 = &s;
    let r2 = &s;
    println!("{r1} {r2} {}", r1 == r2);

    print_twice(&s);
    print_twice("literal");
}

The function borrows the string to calculate its length.

fn calculate_length(s: &str) -> usize {
    s.len()
}

The & symbol in a type denotes a reference. The parameter is a string slice &str, the most flexible type for read-only access to text. When we call the function, we write &s to create a reference to the String; the compiler converts the &String to a &str automatically (deref coercion). The function does not own the string, so nothing is dropped when it returns.

let r1 = &s;
let r2 = &s;
println!("{r1} {r2} {}", r1 == r2);

There can be any number of shared references at the same time. References are used like the values they point to; comparing them compares the values. The same function can also be called with a string literal, which is a &str already.

$ cargo run -q
the length of 'hello' is 5
hello hello true
hello hello
literal literal

Mutable borrowing

A &mut reference allows modification of the borrowed value. The variable must be declared mut.

main.rs
fn append_world(s: &mut String) {
    s.push_str(", world");
}

fn main() {
    let mut s = String::from("hello");

    append_world(&mut s);
    println!("{s}");

    let r = &mut s;
    r.push('!');
    r.push('!');
    println!("{r}");

    println!("{s}");
}

We change a string through a mutable reference.

fn append_world(s: &mut String) {
    s.push_str(", world");
}

The function receives a mutable reference and changes the string in place. At the call site we write &mut s, so it is visible in the code that the function can modify the argument.

let r = &mut s;
r.push('!');
r.push('!');
println!("{r}");

println!("{s}");

We can also create a mutable reference in a variable. While r is in use, the original variable s cannot be touched. After the last use of r, the borrow ends and the owner can use the value again.

$ cargo run -q
hello, world
hello, world!!
hello, world!!

Two mutable references

There can be only one mutable reference to a value at a time.

main.rs
fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;

    r1.push('!');
    r2.push('?');
    println!("{s}");
}

We create two mutable references to the same string.

let r1 = &mut s;
let r2 = &mut s;

r1.push('!');

The compiler reports E0499: cannot borrow s as mutable more than once at a time. It shows where the first borrow starts, where the second one starts, and that the first one is used later. This rule prevents data races at compile time: two pointers to the same data, at least one of them writing, without synchronization.

$ cargo run -q
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 |
7 |     r1.push('!');
  |     -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.

Borrow rules

The borrow checker enforces two rules:

In addition, references must always be valid. A borrow lasts from its creation to its last use, not to the end of the block. This is called non-lexical lifetimes (NLL).

main.rs
fn main() {
    let mut s = String::from("Rust");

    let r1 = &s;
    let r2 = &s;
    println!("r1 = {r1}, r2 = {r2}");

    let r3 = &mut s;
    r3.push_str(" ownership");
    println!("{r3}");
}

We read the string through two shared references and then change it through a mutable one.

let r1 = &s;
let r2 = &s;
println!("r1 = {r1}, r2 = {r2}");

let r3 = &mut s;

The references r1 and r2 are last used in the first println!. After this line their borrows are over, so creating the mutable reference r3 is allowed. If we used r1 after creating r3, the program would not compile, as the next example shows.

$ cargo run -q
r1 = Rust, r2 = Rust
Rust ownership

Mixing shared and mutable references

A mutable reference is not allowed while a shared reference is still going to be used.

main.rs
fn main() {
    let mut s = String::from("Rust");

    let r1 = &s;
    let r2 = &mut s;

    r2.push('!');
    println!("{r1}");
}

We create a mutable reference and then use an older shared one.

let r1 = &s;
let r2 = &mut s;

r2.push('!');
println!("{r1}");

The compiler reports E0502: cannot borrow s as mutable because it is also borrowed as immutable. The shared reference r1 is used after the mutable borrow was created, so the borrows overlap. The readers of r1 would see the string change under their feet, or even see freed memory if the string had to reallocate.

$ cargo run -q
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:14
  |
4 |     let r1 = &s;
  |              -- immutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ mutable borrow occurs here
...
8 |     println!("{r1}");
  |                -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.

Dangling references

In languages with manual memory management it is easy to return a pointer to memory that has already been freed – a dangling pointer. Rust prevents this at compile time. A reference must never outlive the value it points to.

main.rs
fn dangle() -> &String {
    let s = String::from("hello");
    &s
}

fn main() {
    let reference = dangle();
    println!("{reference}");
}

The function creates a string and tries to return a reference to it.

fn dangle() -> &String {
    let s = String::from("hello");
    &s
}

The string s is dropped at the end of the function, so a reference to it would point to freed memory. The compiler reports E0106: missing lifetime specifier. The return type is a reference, but there is no argument from which it could be borrowed. The message suggests the right fix: return an owned String. Lifetimes are explained in the lifetimes tutorial.

$ cargo run -q
error[E0106]: missing lifetime specifier
 --> src/main.rs:1:16
  |
1 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
  |
1 | fn dangle() -> &'static String {
  |                 +++++++
help: instead, you are more likely to want to return an owned value
  |
1 - fn dangle() -> &String {
1 + fn dangle() -> String {
  |

For more information about this error, try `rustc --explain E0106`.

Returning a reference to a local variable

Even when the lifetime is given, the compiler checks that the returned reference stays valid.

main.rs
fn dangle() -> &'static str {
    let s = String::from("hello");
    &s
}

fn main() {
    let reference = dangle();
    println!("{reference}");
}

This function promises a &'static str, a reference that is valid for the whole program.

fn dangle() -> &'static str {
    let s = String::from("hello");
    &s
}

The compiler reports E0515: cannot return reference to local variable s. It "returns a reference to data owned by the current function".

$ cargo run -q
error[E0515]: cannot return reference to local variable `s`
 --> src/main.rs:3:5
  |
3 |     &s
  |     ^^ returns a reference to data owned by the current function

For more information about this error, try `rustc --explain E0515`.

Returning owned values and borrowed slices

There are two correct ways to return data from a function: return an owned value, which moves ownership to the caller, or return a reference to data that the caller owns.

main.rs
fn no_dangle() -> String {
    String::from("hello")
}

fn longest_line(text: &str) -> &str {
    text.lines().max_by_key(|line| line.len()).unwrap_or("")
}

fn main() {
    let s = no_dangle();
    println!("{s}");

    let text = String::from("one\nthree\ntwo");
    let line = longest_line(&text);
    println!("longest line: {line}");
}

We use both approaches.

fn no_dangle() -> String {
    String::from("hello")
}

The string is moved to the caller, so nothing dangles.

fn longest_line(text: &str) -> &str {
    text.lines().max_by_key(|line| line.len()).unwrap_or("")
}

The function returns a slice of the text that the caller passed in. The compiler infers that the returned reference borrows from the argument, so it stays valid as long as text does. The empty string literal is valid for the whole program.

$ cargo run -q
hello
longest line: three

Mutating a collection while iterating

A common beginner mistake is to modify a collection while iterating over it.

main.rs
fn main() {
    let mut s = String::from("abc");

    for c in s.chars() {
        s.push(c);
    }

    println!("{s}");
}

The loop appends to the string that it iterates over.

for c in s.chars() {
    s.push(c);
}

The call s.chars() returns an iterator that holds a shared borrow of s for the entire loop. Inside the body, push needs a mutable borrow. The compiler reports E0502 and marks the immutable borrow in the loop header and the mutable borrow in the body. If this were allowed, push might reallocate the buffer and the iterator would point to freed memory.

$ cargo run -q
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:9
  |
4 |     for c in s.chars() {
  |              ---------
  |              |
  |              immutable borrow occurs here
  |              immutable borrow later used here
5 |         s.push(c);
  |         ^^^^^^^^^ mutable borrow occurs here

For more information about this error, try `rustc --explain E0502`.

Fixing borrow conflicts in loops

There are several ways to achieve what the loop wanted, depending on the goal.

main.rs
fn main() {
    let s = String::from("abc");
    let mut doubled = String::new();
    for c in s.chars() {
        doubled.push(c);
        doubled.push(c);
    }
    println!("{doubled}");

    let mut digits = String::from("a1b2c3");
    digits.retain(|c| c.is_alphabetic());
    println!("{digits}");

    let mut s = String::from("abc");
    let copy = s.clone();
    s.push_str(&copy);
    println!("{s}");
}

We show three fixes.

let mut doubled = String::new();
for c in s.chars() {
    doubled.push(c);
    doubled.push(c);
}

Build the result in a new string. The new string does not overlap with the borrow of s.

digits.retain(|c| c.is_alphabetic());

To remove elements, use retain. It iterates and modifies in one operation, which the library implements safely.

let copy = s.clone();
s.push_str(&copy);

To append a string to itself, we need a copy first, because the argument of push_str is borrowed while the string is being changed.

$ cargo run -q
aabbcc
abc
abcabc

Slices

A slice is a reference to a contiguous part of a collection. It borrows a window into the data without taking ownership. The most common slice type is &str – a string slice that points into a String or a string literal. For arrays and vectors, the slice type is &[T].

main.rs
fn first_word(s: &str) -> &str {
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}

fn main() {
    let sentence = String::from("hello there world");

    let word = first_word(&sentence);
    println!("first word: {word}");

    println!("literal: {}", first_word("single"));
}

The function returns the first word of a text.

fn first_word(s: &str) -> &str {
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}

The find method returns the index of the first space or None. The expression &s[..i] creates a slice from the start up to, but not including, index i. The result borrows from the same data: there is no allocation and no copy. The function accepts a &String as well as a literal.

$ cargo run -q
first word: hello
literal: single

Slices keep the owner honest

Because a slice borrows the data, the compiler guarantees that the data cannot change while the slice exists. If first_word returned an index instead of a slice, the index could become invalid when the string changes. The borrow checker prevents this bug for slices.

main.rs
fn first_word(s: &str) -> &str {
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}

fn main() {
    let mut sentence = String::from("hello there world");

    let word = first_word(&sentence);
    sentence.clear();

    println!("first word: {word}");
}

We clear the string while a slice of it is still in use.

let word = first_word(&sentence);
sentence.clear();

println!("first word: {word}");

The compiler reports E0502: clear needs a mutable borrow, but word still borrows the string and is used afterwards. Without the check, word would refer to text that no longer exists.

$ cargo run -q
error[E0502]: cannot borrow `sentence` as mutable because it is also borrowed as immutable
  --> src/main.rs:12:5
   |
11 |     let word = first_word(&sentence);
   |                           --------- immutable borrow occurs here
12 |     sentence.clear();
   |     ^^^^^^^^^^^^^^^^ mutable borrow occurs here
13 |
14 |     println!("first word: {word}");
   |                            ---- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.

Array and vector slices

We create slices with range expressions: start..end, ..end, start.., and ... The end index is exclusive.

main.rs
fn print_sum(slice: &[i32]) {
    let total: i32 = slice.iter().sum();
    println!("{slice:?} sums to {total}");
}

fn double_each(slice: &mut [i32]) {
    for x in slice.iter_mut() {
        *x *= 2;
    }
}

fn main() {
    let nums = [1, 2, 3, 4, 5];
    print_sum(&nums);
    print_sum(&nums[1..4]);

    let scores = [88, 92, 75, 61, 95, 83];
    print_sum(&scores[..2]);
    print_sum(&scores[4..]);

    let mut data = [10, 20, 30, 40, 50];
    double_each(&mut data[1..4]);
    println!("{data:?}");
}

We pass slices of arrays to functions.

fn print_sum(slice: &[i32]) {
    let total: i32 = slice.iter().sum();
    println!("{slice:?} sums to {total}");
}

The function accepts any contiguous sequence of i32 values: a reference to an array, a reference to a vector (a &Vec<i32> coerces to a slice), or a part of them. Taking &[T] instead of &Vec<T> is the idiomatic way to write functions that only read.

print_sum(&nums[1..4]);
print_sum(&scores[..2]);
print_sum(&scores[4..]);

These calls pass the elements at indexes 1 to 3, the first two elements, and all elements from index 4. No data is copied.

fn double_each(slice: &mut [i32]) {
    for x in slice.iter_mut() {
        *x *= 2;
    }
}

double_each(&mut data[1..4]);

A mutable slice &mut [T] borrows part of a collection with write access. The same exclusivity rule applies: while it exists, nothing else can use that part of the array. Only the elements at indexes 1 to 3 are doubled. The slices tutorial covers more methods.

$ cargo run -q
[1, 2, 3, 4, 5] sums to 15
[2, 3, 4] sums to 9
[88, 92] sums to 180
[95, 83] sums to 178
[10, 40, 60, 80, 50]

Guidelines

The ownership rules lead to a few practical guidelines:

Source

The Rust Programming Language: Understanding Ownership

Rust by Example: Ownership and moves

Rust by Example: Borrowing

In this article we have worked with ownership, borrowing, and slices in Rust.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Rust tutorials.