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:
- Each value in Rust has an owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value is dropped (its memory is freed).
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.
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.
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.
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.
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.
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.
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.
#[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.
#[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.
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.
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.
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.
#[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.
#[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.
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.
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.
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.
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.
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:
- We may have any number of shared references (
&T) to a value at the same time. - Or we may have exactly one mutable reference (
&mut T), and while it exists, no other reference of any kind may be used.
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).
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.
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.
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.
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.
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.
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.
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(©);
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(©);
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].
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.
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.
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:
- Pass a reference (
&T,&str,&[T]) to functions that only read. Take&mut Tif the function must change the argument. - Take ownership (
T) when the function stores the value or consumes it, for example in a constructor or in a method namedinto_*. - Return owned values when the data is created inside the function.
- Keep borrows short. A mutable borrow that ends early leaves the owner free to use the value.
- Use
clonewhen a real copy is needed, not as a way to avoid compiler errors. - When several owners are really needed, use the smart pointers
RcandArc; for changes through shared access, see the interior mutability types in the mutability tutorial.
Source
The Rust Programming Language: Understanding Ownership
Rust by Example: Ownership and moves
In this article we have worked with ownership, borrowing, and slices in Rust.
Author
List all Rust tutorials.