Skip to content
DocsRust LearningintermediateReferences & Borrowing
Chapter 5 of 19·intermediate·7 min read

References & Borrowing

Tham Chiếu & Mượn

References, borrowing rules, and slices

Hover or tap any paragraph to see Vietnamese translation

References and Dereferencing

A reference allows you to refer to a value without taking ownership of it. When you have a reference to a value, that value will not be dropped when the reference goes out of scope. The & syntax creates a reference.

src/main.rs
1fn main() {2    let s1 = String::from("hello");34    // &s1 creates a reference — does NOT take ownership5    let len = calculate_length(&s1);67    // s1 is still valid here because we only lent it8    println!("The length of '{s1}' is {len}.");910    // Dereferencing with * to get the value behind a reference11    let x = 5;12    let y = &x;       // y is a reference to x

The action of creating a reference is called borrowing. Just as in real life, when you borrow something from someone, you give it back when you are done — you do not own it.

The Borrowing Rules

Rust's borrow checker enforces two core rules at compile time to guarantee memory safety. There is zero runtime cost for these checks.

  • At any given time, you can have EITHER one mutable reference (&mut T), OR any number of immutable references (&T) — but not both at the same time.
  • References must always be valid — no dangling references allowed.
Info
Both rules are enforced entirely at compile time. If your code compiles successfully, it is guaranteed to have no data races, dangling pointers, or use-after-free bugs.

Mutable References

Immutable references do not allow modifying the data. To modify data through a reference, use &mut. However, only one mutable reference to a specific piece of data can exist at a time — this is how Rust prevents data races.

src/main.rs
1fn main() {2    let mut s = String::from("hello");34    change(&mut s);56    println!("{s}"); // "hello, world"7}89fn change(some_string: &mut String) {10    some_string.push_str(", world");11}
src/main.rs
1fn main() {2    let mut s = String::from("hello");34    let r1 = &mut s;5    // let r2 = &mut s; // ERROR: cannot borrow `s` as mutable more than once67    println!("{r1}"); // r1 last used here89    // After r1 is last used, a new mutable reference is allowed10    let r2 = &mut s;11    r2.push_str(" world");12    println!("{r2}");13}
Info
The restriction of only one mutable reference at a time prevents data races at compile time. A data race occurs when two or more pointers access the same data simultaneously, at least one is writing, and there is no synchronization mechanism. Rust eliminates this possibility entirely.

Rules for Multiple References

Multiple immutable references can coexist, but you cannot combine immutable and mutable references. The scope of a reference ends at its last point of use, not at the closing curly brace (NLL — Non-Lexical Lifetimes).

src/main.rs
1fn main() {2    let mut s = String::from("hello");34    // Multiple immutable references are fine5    let r1 = &s;6    let r2 = &s;7    println!("{r1} and {r2}");8    // r1 and r2 are no longer used after this point (NLL)910    // Now a mutable reference is allowed11    let r3 = &mut s;12    r3.push_str(" world");13    println!("{r3}");1415    // This combination would be an ERROR:16    // let r1 = &s;17    // let r3 = &mut s;  // ERROR: cannot borrow as mutable18    //                   // because already borrowed as immutable19    // println!("{r1} {r3}"); // if r1 is still in scope here20}

Dangling References

A dangling reference occurs when you have a pointer to memory that has been freed. In C/C++, this is a common and dangerous bug. Rust prevents this at compile time: the compiler ensures data will not go out of scope before a reference to it does.

src/main.rs
1// This would cause a compile ERROR — Rust prevents dangling references:2//3// fn dangle() -> &String {        // returns a reference to a String4//     let s = String::from("hello"); // s is created inside this function5//     &s                          // ERROR: we return a reference to s6// }                               // but s is dropped here — dangling!7//8// error[E0106]: missing lifetime specifier910// The correct solution: return the String itself (transfer ownership)11fn no_dangle() -> String {12    let s = String::from("hello");13    s // ownership is transferred to the caller — no reference needed14}1516fn main() {17    let s = no_dangle();18    println!("{s}");19}
Tip
The Rust compiler rejects any code that creates a dangling reference and provides a clear error message. This is one of Rust's most powerful safety features — it completely eliminates an entire class of dangerous bugs.

String Slices

A string slice is a reference to part of a String. The syntax uses range notation [start..end] where start is inclusive and end is exclusive. The type of a string slice is &str.

src/main.rs
1fn main() {2    let s = String::from("hello world");34    let hello = &s[0..5];  // "hello" — bytes 0 through 45    let world = &s[6..11]; // "world" — bytes 6 through 1067    // Range shorthand8    let hello2 = &s[..5];  // same as [0..5]9    let world2 = &s[6..];  // same as [6..11]10    let whole  = &s[..];   // the entire string as a slice1112    println!("{hello} {world}");
Tip
The &str type is recommended for function parameters when working with strings. It is more flexible than &String because it accepts both references to String values (via automatic coercion) and string literals.

Array and Vector Slices

Similar to string slices, you can create slices of arrays and vectors. The type of an array slice is &[T] where T is the element type. A slice is a reference to a contiguous sequence of memory, consisting of a pointer to the first element and a length.

src/main.rs
1fn main() {2    let a = [1, 2, 3, 4, 5];34    // Array slice: type is &[i32]5    let slice = &a[1..3]; // [2, 3]6    println!("{:?}", slice); // [2, 3]78    println!("Sum of slice: {}", sum(slice));9    println!("Sum of all:   {}", sum(&a)); // coerce [i32; 5] to &[i32]1011    // Vec slice works the same way12    let v = vec![10, 20, 30, 40, 50];

The Slice Type in Summary

Slices are non-owning types that store a reference to a contiguous sequence of elements in a collection. Each slice on the stack has two components: a pointer to the first element and a length. Slices are one of Rust's zero-cost abstractions.

src/main.rs
1fn main() {2    // String types3    let owned: String = String::from("hello world");4    let str_slice: &str = &owned[..5];  // borrows part of the String5    let literal: &str = "hello";        // a string literal is &str67    // Array and Vec slices8    let arr: [i32; 5] = [1, 2, 3, 4, 5];9    let arr_slice: &[i32] = &arr[1..3]; // borrows part of the array1011    let vec: Vec<i32> = vec![1, 2, 3, 4, 5];12    let vec_slice: &[i32] = &vec[..];   // borrows the entire Vec1314    // Slice internals: (pointer, length) — just 2 words on the stack15    println!("str_slice: '{}' len={}", str_slice, str_slice.len());16    println!("literal:   '{}' len={}", literal, literal.len());17    println!("arr_slice: {:?} len={}", arr_slice, arr_slice.len());18    println!("vec_slice: {:?} len={}", vec_slice, vec_slice.len());19}
Info
Slices are zero-cost abstractions: they provide a safe and convenient API with no runtime overhead compared to working directly with raw pointers and lengths. The compiler optimizes slices into simple pointer operations.

Key Takeaways

Điểm Chính

  • References let you use values without taking ownershipTham chiếu cho phép bạn sử dụng giá trị mà không lấy quyền sở hữu
  • You can have many immutable references or one mutable referenceBạn có thể có nhiều tham chiếu bất biến hoặc một tham chiếu có thể thay đổi
  • The borrow checker prevents dangling references at compile timeBorrow checker ngăn tham chiếu treo tại thời điểm biên dịch
  • Slices are references to contiguous sequences in collectionsSlice là tham chiếu đến các chuỗi liên tiếp trong collection

Practice

Test your understanding of this chapter

Quiz

How many mutable references (&mut T) to the same data can exist at the same time?

Có bao nhiêu tham chiếu có thể thay đổi (&mut T) đến cùng một dữ liệu có thể tồn tại cùng lúc?

True or False

You can have multiple immutable references (&T) to the same data at the same time.

Bạn có thể có nhiều tham chiếu bất biến (&T) đến cùng một dữ liệu cùng lúc.

Code Challenge

Borrow without taking ownership

Mượn mà không lấy quyền sở hữu

fn print_length(s: String) {
    println!("length: {}", s.len());
}

fn main() {
    let s = String::from("hello");
    print_length(s);
    println!("{s}"); // s still valid
}
Quiz

What is the type of a string slice in Rust?

Kiểu dữ liệu của một lát cắt chuỗi (string slice) trong Rust là gì?

True or False

Rust prevents dangling references at runtime by using a garbage collector to track live references.

Rust ngăn tham chiếu treo (dangling reference) lúc runtime bằng cách dùng garbage collector để theo dõi tham chiếu còn sống.

Chapter Complete!

Great job! Keep the momentum going.

Your progress0 of 19 chapters read
← → to navigate chapters
Built: 4/8/2026, 12:01:11 PM