Rust: Rust References and Borrows

Last updated: 2026-08-26

A citation is like a library card—you can borrow it (read-only citation) or request exclusive permission to rewrite it (editable citation), but you can’t do both at the same time.

Rust's reference mechanism allows you to access data without transferring ownership. This is called "borrowing."


1. What You'll Learn



2. Conceptual Diagrams

100%
flowchart LR
    subgraph "Transfer of Ownership"
        A1["Data String"] -->|"let s2 = s1<br>move"| B1["New Owner s2"]
        B1 -->|"s1 Failure"| C1["Compilation Error<br>println!(s1)"]
    end
    subgraph "Citation, Borrowing"
        A2["Data String"] -->|"let r = &s<br>borrow"| B2["Quote r"]
        B2 -->|"r Return after use<br>s Still valid"| C2["✅ Secure Access"]
    end


3. The Story of a Library

(1) Heartbreak: The book was torn to pieces

There is a popular book titled Programming in Rust in Tom's library, and it can only be checked out once:

"If only I could let multiple people read the same book at the same time without them tearing it apart..."

(2) Solutions for Rust References

RUST
fn main() {
    let book = String::from("Rust Programming");

    // Multiple immutable references: Multiple people at the same time "read-only" check out
    let reader1 = &book;   // Reader 1 checks out (read-only)
    let reader2 = &book;   // Reader 2 checks out (read-only)
    println!("Readers 1 See: {}", reader1);
    println!("Readers 2 See: {}", reader2);
    // Both references are valid at the same time -- because everyone has read-only access

    let mut notebook = String::from("Notebook");

    // Mutable References: write-only permission
    let writer = &mut notebook;  // Only one person can make changes
    writer.push_str("-- I wrote some notes");
    println!("Rewriter: {}", writer);
    // writer ends scope here, only then can others borrow it again.
}

Citation rules are like library rules: Either multiple people can read at the same time (multiple &T), or one person can edit alone (one &mut T)—but they cannot happen simultaneously.



4. Borrowing Rules

100%
graph TB
    A[Borrowing Rules] --> B[You can only choose one of them.]
    B --> C[Multiple immutable references &T]
    B --> D[Or a mutable reference &mut T]
    B --> E[Cannot coexist]
    A --> F[References must always be valid.]
    F --> G[There must be no dangling references.]
Rule Description Consequences of Violation
Multiple &T or a single &mut T Immutable and mutable references cannot coexist Compilation error
References must be valid A reference cannot outlive the object it refers to Compilation error
When a reference is immutable, the original value cannot be modified either When &T exists, the original value cannot be modified via &mut T Compilation error
NLL Scope The scope of &mut T ends after its last use Compiler-driven optimization

(2) Comparison of Reference Types

Citation Type Syntax Permissions Maximum Number Allowed Typical Uses
Immutable Reference &T Read-only Multiple Read-only access to function parameters
Variable reference &mut T Read/write 1 Function parameter modifies data
Fat Pointer (Slice) &[T] Read-Only Multiple Array/String Slice

(3) Quick Reference for Borrowing Scenarios

Scenario Recommended Approach Example
Read-Only Function Access &T fn len(s: &String) -> usize
Functions that Modify Data &mut T fn push(s: &mut String, ch: char)
Function Data Usage T (pass-by-value) fn consume(s: String)
Function Return Data T (Return Value) fn create() -> String
Multiple Functions Share Read-Only Access &T Multiple Functions Receive the Same &T
Alternating Modifications &mut T (Time-Division Multiplexing) NLL Allows Alternating Creation of &mut T


5. Citation Examples

▶ Example 1: Immutable References—Read-Only Access (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
'Hello, Rust!' The length of is: <len>
r1: Hello, Rust!, r2: Hello, Rust!
RUST
// ============================================
// Immutable References &T: Multiple readers can borrow items at the same time
// ============================================

fn calculate_length(s: &String) -> usize {
    // s is a String reference, no ownership
    s.len()
}  // s goes out of scope, but since it's a reference, it will not destroy the String

fn main() {
    let s = String::from("Hello, Rust!");

    let len = calculate_length(&s);  // Borrow s, no transfer of ownership
    println!("'{}' The length of is: {}", s, len);  // ✅ s Still available

    // Multiple immutable references can coexist
    let r1 = &s;
    let r2 = &s;
    println!("r1: {}, r2: {}", r1, r2);  // ✅ Read simultaneously
}

Output:

TEXT 📖 Display only
'Hello, Rust!' The length of is: 12
r1: Hello, Rust!, r2: Hello, Rust!

Output:

TEXT 📖 Display only
Modifying Through a Variable Reference: <r>

&s creates a reference to s; when passed to a function, ownership is not transferred. After the function returns, the reference becomes invalid, but s still exists. This is what is known as a "borrow"—you use it and then return it.


▶ Example 2: Mutable References—Exclusive Modification Permissions (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
Modifying Through a Variable Reference: <r>
Another mutable reference: <r2>
<ref1>, <ref2>
RUST
// ============================================
// Mutable References &mut T: Only one writer at a time
// ============================================

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

    // Create a mutable reference
    let r = &mut s;
    r.push_str(", world!");
    println!("Modifying Through a Variable Reference: {}", r);
    // r is used here for the last time, NLL ends

    // After the mutable reference ends, you can create a new mutable reference.
    let r2 = &mut s;
    r2.push_str("!!");
    println!("Another mutable reference: {}", r2);

    // --- Examples of Errors (Uncomment to view compilation errors) ---
    // let mut s2 = String::from("test");
    // let ref1 = &mut s2;
    // let ref2 = &mut s2;  // ❌ There cannot be two mutable references at the same time.
    // println!("{}, {}", ref1, ref2);
}

Output:

TEXT 📖 Display only
r1: Key Data, r2: Key Data
r3: <r3>
Demo, <r_b>




&mut is mutable—the value of the reference can be modified. However, Rust enforces the rule that only one mutable reference can exist at a time. This prevents data races—no locks or atomic operations are needed; the issue is resolved entirely at compile time.


▶ Example 3: Immutable and mutable references cannot coexist (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
r1: Key Data, r2: Key Data
r3: <r3>
Demo, <r_b>
RUST
// ============================================
// Demonstration of Conflicts Between Immutable and Mutable References
// ============================================

fn main() {
    let mut data = String::from("Key Data");

    let r1 = &data;       // ✅ Immutable References 1
    let r2 = &data;       // ✅ Immutable References 2
    println!("r1: {}, r2: {}", r1, r2);
    // r1 and r2 are used here for the last time

    let r3 = &mut data;   // ✅ At this point, you can create a mutable reference.
    r3.push_str("--Modified");
    println!("r3: {}", r3);

    // --- Examples of Errors: A mutable reference cannot be created while an immutable reference still exists. ---
    // let mut s = String::from("Demo");
    // let r_a = &s;        // Immutable References
    // let r_b = &mut s;    // ❌ Compilation Error: immutable reference already exists
    // println!("{}, {}", r_a, r_b);
}

Output:

TEXT 📖 Display only
Safe Return Values: hello
Referencing Local Variables: Local Data





Key point: The immutable references r1 and r2 go out of scope after their last use (println!), and only then can the mutable reference r3 be created. This is NLL (Non-Lexical Lifetime)—the compiler intelligently determines when a reference is no longer in use, rather than waiting for the end of the block.


▶ Example 4: Preventing Hanging References (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
Safe Return Values: hello
Referencing Local Variables: Local Data
RUST
// ============================================
// How Do Compilers Prevent Dangling References?
// ============================================

// Examples of Errors: Return a reference to a local variable
// fn dangle() -> &String {
//     let s = String::from("hello");
//     &s  // ❌ Compilation Error: s is destroyed at the end of the function, the reference would be dangling
// }

// The Correct Approach: Return String directly (transfer of ownership)
fn no_dangle() -> String {
    let s = String::from("hello");
    s  // Return String directly, ownership is transferred to the caller
}

fn main() {
    let s = no_dangle();
    println!("Safe Return Values: {}", s);

    // The Correct Way to Reference Local Variables: Use within the scope
    let local = String::from("Local Data");
    let r = &local;          // Quote
    println!("Referencing Local Variables: {}", r);
    // r ends here, local is still valid
}  // local is destroyed here (later than r), safe

Output:

TEXT 📖 Display only
The deposit amount must be greater than 0
Deposit <amount> Yuan Chenggong
Transfer Failed: Insufficient balance or invalid amount
Transfer <amount> yuan: <from.owner> -> <to.owner>
--- Account List ---
<acc.owner>. <acc.balance> balance: <i + 1> yuan
<check_balance(&alice)>

A dangling reference occurs when the memory pointed to by a reference has been deallocated. The Rust compiler can detect this situation at compile time—if the object pointed to by a reference is destroyed after the reference is created, the compiler will report an error. This completely eliminates bugs such as "dangling pointers."


▶ Example 5: Comprehensive Exercise—Bank Account Operations (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
The deposit amount must be greater than 0
Deposit <amount> Yuan Chenggong
Transfer Failed: Insufficient balance or invalid amount
Transfer <amount> yuan: <from.owner> -> <to.owner>
--- Account List ---
<acc.owner>. <acc.balance> balance: <i + 1> yuan
<check_balance(&alice)>
<check_balance(&bob)>
<check_balance(&alice)>
Total Deposits: <total> yuan
RUST
// ============================================
// Comprehensive Example: Applications of References in Real-Life Scenarios
// ============================================

struct Account {
    owner: String,
    balance: f64,
}

fn check_balance(account: &Account) -> String {
    format!("{} balance: {:.2} yuan", account.owner, account.balance)
}

fn deposit(account: &mut Account, amount: f64) {
    if amount <= 0.0 {
        println!("The deposit amount must be greater than 0");
        return;
    }
    account.balance += amount;
    println!("Deposit {:.2} Yuan Chenggong", amount);
}

fn transfer(from: &mut Account, to: &mut Account, amount: f64) -> bool {
    if amount <= 0.0 || from.balance < amount {
        println!("Transfer Failed: Insufficient balance or invalid amount");
        return false;
    }
    from.balance -= amount;
    to.balance += amount;
    println!("Transfer {:.2} yuan: {} -> {}", amount, from.owner, to.owner);
    true
}

fn show_accounts(accounts: &[Account]) {
    println!("--- Account List ---");
    for (i, acc) in accounts.iter().enumerate() {
        println!("{}. {} balance: {:.2} yuan", i + 1, acc.owner, acc.balance);
    }
}

fn main() {
    let mut alice = Account { owner: String::from("Alice"), balance: 1000.0 };
    let mut bob = Account { owner: String::from("Bob"), balance: 500.0 };

    println!("{}", check_balance(&alice));
    println!("{}", check_balance(&bob));

    deposit(&mut alice, 200.0);
    println!("{}", check_balance(&alice));

    transfer(&mut alice, &mut bob, 300.0)?;

    let accounts = [&alice, &bob];
    show_accounts(&accounts);

    let total: f64 = accounts.iter().map(|a| a.balance).sum();
    println!("Total Deposits: {:.2} yuan", total);
}

Output:

TEXT 📖 Display only
Alice balance: 1000.00 yuan
Bob balance: 500.00 yuan
Deposit 200.00 Yuan Successful
Alice balance: 1200.00 yuan
Transfer 300.00 yuan: Alice -> Bob
--- Account List ---
1. Alice balance: 900.00 yuan
2. Bob balance: 800.00 yuan
Total Deposits: 1700.00 yuan

This example demonstrates the practical application of the three reference types: &T (read-only query), &mut T (modification operation), and &[T] (read-only traversal). transfer requires two &mut T references simultaneously, which is safe in Rust because they point to different data.


❓ FAQ

Q What is the difference between a reference and a pointer?
A A reference is a pointer that is guaranteed to be valid.
Q Can &T and &mut T coexist?
A No.
Q What is NLL (Non-Lexical Lifetime)?
A NLL allows references to expire after their last use, rather than waiting until the end of the block.
Q When should you use a reference instead of passing by ownership?
A Use a reference when you just need to "take a look" at the data, and use ownership when you need to "own" or "hold onto" the data for the long term.
Q Can a reference be stored in a struct?
A Yes, but you need to specify a lifetime parameter.

📖 Summary


📝 Exercises

  1. Difficulty ⭐: Write a function fn print_message(msg: &String) that prints the string passed to it. In main, create String and then call this function to verify that the original variable remains accessible after the call.
  2. Difficulty ⭐⭐: Write a program to create a mut String. First, create two immutable references and print them; then, create a mutable reference, modify its contents, and print the modified result.
  3. Difficulty ⭐⭐⭐: Try writing a function that returns a reference (such as fn get_ref() -> &String), and observe the compiler’s error messages. Then modify it to return ownership of String, and understand why Rust prohibits returning references to local variables.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏