Skip to main content
Rust Programming
CHAPTER 11 Beginner

References and Slices

Updated: May 18, 2026
5 min read

# CHAPTER 11

References and Slices

1. Chapter Introduction

In Chapter 10, we learned that passing a String into a function transfers ownership, destroying our access to it in the original function. This is highly inconvenient. What if we just want the function to *look* at the data? In Rust, we use References. This act is called Borrowing. We will also explore Slices, which allow us to reference a specific portion of a collection.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Use the & operator to create a reference.
  • Pass parameters by reference to avoid moving ownership.
  • Create mutable references using &mut.
  • Understand the strict rules of the Borrow Checker.
  • Extract partial data using String Slices (&str).

3. Borrowing (Immutable References)

A Reference allows you to refer to some value without taking ownership of it. You create a reference using the ampersand symbol: &.
rust
123456789101112131415
fn main() {
    let s1 = String::from("Hello");

    // We pass a REFERENCE to s1 using &s1. 
    // Ownership is NOT moved!
    let len = calculate_length(&s1); 

    // Because we only borrowed it, s1 is still perfectly valid here!
    println!("The length of '{}' is {}.", s1, len);
}

// The parameter expects a reference to a String (&String)
fn calculate_length(s: &String) -> usize {
    s.len()
} // s goes out of scope, but because it doesn't own the data, nothing is deleted!

4. Mutable References

Just like variables are immutable by default, references are immutable by default. You cannot modify borrowed data!
rust
123
fn change(s: &String) {
    // s.push_str(", world"); // ERROR! Cannot mutate a borrowed value.
}

To modify borrowed data, you must:

  1. 1. Make the original variable mutable (let mut s).
  1. 2. Pass a mutable reference (&mut s).
  1. 3. Accept a mutable reference in the function signature (s: &mut String).

rust
1234567891011
fn main() {
    let mut s1 = String::from("Hello");

    change_string(&mut s1); // Pass a mutable borrow
    
    println!("{}", s1); // Output: Hello, world!
}

fn change_string(s: &mut String) {
    s.push_str(", world!"); // Modifies the original data directly!
}

5. The Rules of the Borrow Checker (CRITICAL)

Rust allows you to borrow data, but the Borrow Checker strictly enforces two rules to prevent Data Races (where two threads try to modify data simultaneously, causing corruption):

Rule 1: You can have EITHER one mutable reference, OR any number of immutable references at the exact same time. Never both.

rust
1234567
let mut s = String::from("hello");

let r1 = &s; // OK (Immutable)
let r2 = &s; // OK (Immutable)
// let r3 = &mut s; // ERROR! Cannot borrow `s` as mutable because it is also borrowed as immutable!

println!("{} and {}", r1, r2);

Rule 2: References must always be valid (No Dangling Pointers). You cannot return a reference to a variable created inside a function, because that variable is deleted when the function ends!

rust
1234
// fn dangle() -> &String { // ERROR!
//     let s = String::from("hello");
//     &s // s is deleted here, so the reference would point to dead memory!
// }

6. The Slice Type (&str)

A Slice is a special kind of reference. Instead of referencing the entire collection, it references a *contiguous sequence* of elements. Slices do not have ownership.

String slices are written as &str.

rust
123456789
fn main() {
    let s = String::from("Hello World");

    // Create a slice from index 0 to 4 (exclusive of 5)
    let hello = &s[0..5]; 
    let world = &s[6..11];

    println!("{} {}", hello, world);
}

*Note: String literals (e.g., let word = "Rust";) are actually String slices (&str) pointing directly to the compiled binary memory.*

7. Mini Project: First Word Finder

Let's write a function that finds the first word in a sentence and returns a slice of it.
rust
1234567891011121314151617
fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes(); // Convert to bytes to iterate

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' { // If we find a space
            return &s[0..i]; // Return a slice up to the space
        }
    }
    
    &s[..] // If no space is found, return the whole string as a slice
}

fn main() {
    let sentence = String::from("Rust Programming");
    let word = first_word(&sentence);
    println!("First word: {}", word);
}

8. Common Mistakes

  • Mixing & and &mut: Trying to read data via an immutable reference while a mutable reference is actively changing it. The compiler will block this to prevent race conditions.
  • Dangling References: Attempting to return a pointer/reference to a local variable that dies at the end of the function.

9. Best Practices

  • Prefer &str as parameters: If you are writing a function that only needs to read a string, don't use fn myfunc(s: &String). Use fn myfunc(s: &str). This makes your API much more flexible, as it can accept both String references and raw string literals!

10. Exercises

  1. 1. Create a mutable string greeting containing "Good".
  1. 2. Create a function add_morning that accepts a mutable reference to a string and appends " Morning" to it.
  1. 3. Call it in main and print the mutated string.

11. MCQs with Answers

Question 1

What symbol is used to create a reference (borrow) in Rust?

# Answer: c) &.

Q2. When you pass data by reference (&x), does ownership transfer? a) Yes b) No, the original function retains ownership Answer: b) No.

Q3. Are references mutable by default? a) Yes b) No Answer: b) No.

Question 4

What syntax is used to create a mutable reference?

Question 5

According to the Borrow Checker, how many mutable references to a single piece of data can exist at the exact same time in the same scope?

Q6. Can you have one mutable reference AND one immutable reference to the same data at the same time? a) Yes b) No, this violates the borrowing rules Answer: b) No.
Question 7

What is a "Dangling Reference"?

Question 8

What does &s[0..5] create?

Question 9

What data type is a String Literal (e.g., "Hello")?

Question 10

Why does Rust enforce these strict borrowing rules?

12. Interview Questions

  • Q: State the two primary rules of References and Borrowing in Rust.
  • Q: Why does Rust prevent having an immutable reference and a mutable reference to the same data active at the same time? (Answer: Because the immutable reference expects the data to remain constant. If the mutable reference changes it underneath, the immutable reference reads corrupted data—a classic race condition).

13. Summary

Borrowing allows multiple parts of your application to access data without constantly taking ownership or cloning massive blocks of heap memory. By rigidly enforcing the "One Mutable OR Infinite Immutable" rule, the Rust compiler guarantees that your memory is never corrupted by conflicting instructions.

14. Next Chapter Recommendation

Up until now, we've dealt with simple strings and numbers. Real-world applications require modeling complex entities like Users, Products, and Invoices. In Chapter 12: Structs in Rust, we will learn how to create custom data types and attach methods to them.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·