Rust borrowingownershipReferencesRust

Article & Tutorial

Rust Borrowing and References: &T, &mut T, Borrow Rules and Dangling References

Learn Rust borrowing and references with immutable references, mutable references, borrow rules, dereferencing, non-lexical lifetimes and dangling-reference prevention.

Rust Borrowing and References: &T, &mut T, Borrow Rules and Dangling References article illustration
Rust Borrowing and References: &T, &mut T, Borrow Rules and Dangling References article illustration
Contents

Ownership keeps Rust memory-safe, but moving every value into every function would be inconvenient. Borrowing solves that problem.

A reference lets code access a value without becoming its owner. This is one of the most important Rust skills because well-designed Rust programs borrow data whenever ownership does not need to change.

What is a reference?

A reference refers to a value owned somewhere else. You create an immutable reference using &:

bash
fn main() { let message = String::from("Hello Rust"); let len = calculate_length(&message); println!("{message} has {len} bytes"); } fn calculate_length(text: &String) -> usize { text.len() }

calculate_length can read the String, but it does not own it. When the function returns, the original message remains available.

bash
Owner message ──────────> String heap data │ └── &message ──> borrowed reference no ownership transfer

What is borrowing?

Creating a reference is called borrowing. Think of it as temporary access:

  • The owner remains responsible for the value.
  • The borrower can use the value according to the reference's permissions.
  • The compiler ensures the reference cannot outlive the value it refers to.

Immutable references: &T

bash
fn print_name(name: &String) { println!("Name: {name}"); } fn main() { let name = String::from("Melochip"); print_name(&name); print_name(&name); println!("Owner still has: {name}"); }

Immutable references are read-only. Rust can allow multiple immutable references because none can modify the shared value.

Why can't an immutable reference modify data?

bash
fn change(text: &String) { text.push_str(" updated"); }

The compiler rejects this because &String does not provide mutable access.

Mutable references: &mut T

To modify a borrowed value, both the owner and the reference must permit mutation:

bash
fn change(text: &mut String) { text.push_str(" updated"); } fn main() { let mut message = String::from("status"); change(&mut message); println!("{message}"); }

Expected output:

bash
status updated

The core reference rules

At a given time, Rust's borrowing model enforces an important rule:

  • You may have multiple immutable references to a value, or
  • you may have a mutable reference that requires exclusive mutable access for the relevant borrow period.

This prevents data races and iterator/reference invalidation problems from appearing unchecked.

Multiple immutable references

bash
let text = String::from("Rust"); let r1 = &text; let r2 = &text; println!("{r1} {r2}");

This is safe because both references only read.

Conflicting mutable references

bash
let mut text = String::from("Rust"); let r1 = &mut text; let r2 = &mut text; println!("{r1} {r2}");

If the two mutable borrows overlap in use, the compiler rejects the code because concurrent mutation through multiple aliases could be unsafe.

Mutable and immutable borrows cannot conflict

bash
let mut text = String::from("Rust"); let reader = &text; let writer = &mut text; println!("{reader} {writer}");

If these borrows overlap, Rust prevents the mutable reference from coexisting with active immutable references.

Non-lexical lifetimes: borrow ends after its last use

Modern Rust often recognizes that a reference is no longer needed before the closing brace:

bash
let mut text = String::from("Rust"); let r1 = &text; let r2 = &text; println!("{r1} {r2}"); // last use of immutable borrows let r3 = &mut text; r3.push_str(" language"); println!("{r3}");

This works because the immutable borrows are no longer used when the mutable borrow begins.

Dereferencing with *

The * operator accesses the value behind a reference:

bash
fn main() { let number = 10; let reference = &number; assert_eq!(10, number); assert_eq!(10, *reference); }

For method calls and field access, Rust often performs convenient automatic referencing/dereferencing, so explicit * is not always necessary.

Borrowing is not copying

OperationOwnershipTypical syntax
MoveTransfers ownershiplet b = a; for non-Copy owned values
CloneCreates another owned valuea.clone()
BorrowOwner stays the same&a
Mutable borrowOwner stays the same; temporary write access&mut a

Prefer &str over &String for read-only string parameters

When a function only needs to read text, a string slice is often more flexible:

bash
fn greet(name: &str) { println!("Hello, {name}!"); } fn main() { let owned = String::from("Kaushik"); greet(&owned); greet("Melochip"); }

We explore String, &str and slices in detail in Chapter 8.

Dangling references

A dangling reference would point to memory that has already been released. Safe Rust prevents this at compile time.

bash
fn dangle() -> &String { let text = String::from("temporary"); &text }

This cannot work: text is destroyed when the function ends, so returning a reference to it would be invalid.

Return the owned value instead:

bash
fn no_dangle() -> String { String::from("safe owned value") }

Practical example: update configuration

bash
struct Config { environment: String, retries: u32, } fn print_config(config: &Config) { println!("env={}, retries={}", config.environment, config.retries); } fn increase_retries(config: &mut Config) { config.retries += 1; } fn main() { let mut config = Config { environment: String::from("dev"), retries: 3, }; print_config(&config); increase_retries(&mut config); print_config(&config); }

The read-only function borrows with &Config; the update function borrows with &mut Config. Neither function takes ownership.

Common borrowing errors

Cannot borrow as mutable

bash
let message = String::from("hello"); message.push_str(" world");

Fix the owner:

bash
let mut message = String::from("hello"); message.push_str(" world");

Cannot borrow as mutable because it is also borrowed as immutable

Look for an immutable reference that is still used after you attempt the mutable borrow. Often the correct fix is to shorten the immutable borrow's usage rather than cloning the value.

Reference may outlive borrowed value

Make sure the owner lives long enough, or return/move ownership instead of trying to return a reference to a local temporary value.

Hands-on mini lab

bash
cargo new borrowing_lab cd borrowing_lab
  1. Write a function accepting &String and returning its length.
  2. Change it to accept &str .
  3. Create a function accepting &mut String that appends text.
  4. Try two simultaneous active mutable borrows and read the compiler message.
  5. Try returning a reference to a locally created String and understand why Rust rejects it.
bash
cargo fmt cargo clippy cargo run

Borrowing checklist

  • Does the function need ownership? If not, borrow.
  • Does it only read? Prefer an immutable reference.
  • Does it modify? Use a mutable owner plus &mut .
  • For text-only read parameters, consider &str .
  • Do not reach for clone() automatically when the compiler reports a move problem.

Next chapter

Now that references make sense, continue with Chapter 8: Rust Strings and Slices to understand String, &str, UTF-8 and safe slicing.

Official reference

See the official Rust Book section References and Borrowing.

Did this help?

Yes No

Join the discussion

0 Comments

Ask questions, add context, or share what worked.

Sign in to join the discussion

No comments yet. Start the discussion.

More from Melochip Editorial

Related articles

Stay in the loop. Get new Melochip articles

Receive Melochip article updates when new articles are available. Explicit opt-in only.

No pre-checked boxes. Unsubscribe support is part of the subscriber foundation.