← Back to quizzesFree quiz

Rust Ownership and Borrowing

Rust’s memory safety guarantees are built on two core concepts: ownership and borrowing . These ideas replace the need for a garbage collector and enable the compiler to prevent many common…

11 questions~6 min
Rust Ownership and Borrowing — Qwi
0 / 11
Score: 0%
1

What happens to a Rust value when its owner goes out of scope?

2

Given `let s = String::from("Hello"); let borrow = &s;`, which statements are true?

3

Why does the following Rust code fail to compile? ```rust fn main() { let s = String::from("Hello"); { let s = String::from("World"); } println!("{s}"); } ```

4

In Rust, which rule prevents data races at compile time?

5

What is the purpose of the `'static` lifetime in Rust?

6

Consider the function `fn function<'l1, 'l2>(s1: &'l1 str, s2: &'l2 str) -> &'l1 str { s1 }`. Which of the following calls is guaranteed to be valid?

7

Why does the following code cause a compile‑time error? ```rust fn main() { let r; { let x = 5; r = &x; } println!("{}", r); } ```

8

When a function takes `&mut String` as a parameter, what restriction applies during the call?

9

What does the Rust compiler do when it encounters a double free scenario like in C?

10

Which of the following best describes an elided lifetime in a function signature with a single input reference?

11

In the context of Rust lifetimes, what does the annotation `Entry<'a, 'b>` indicate about the struct fields?

Understanding Rust Ownership and Borrowing

Rust’s memory safety guarantees are built on two core concepts: ownership and borrowing. These ideas replace the need for a garbage collector and enable the compiler to prevent many common bugs at compile time. In this course we will explore the rules that govern ownership, the lifetimes that track how long references are valid, and the borrowing rules that keep data races out of your programs.

1. The Ownership Model

Every value in Rust has a single owner. When the owner goes out of scope, the value is automatically dropped (its memory is freed). This deterministic cleanup is the foundation of Rust’s safety.

  • Rule 1 – One Owner: A value can have only one owner at a time.
  • Rule 2 – Move Semantics: Assigning a value to a new variable moves ownership, leaving the original variable unusable.
  • Rule 3 – Drop on Scope Exit: When the owning variable leaves its lexical scope, the drop function runs automatically.

Example:

fn main() {
    let s = String::from("Hello"); // s owns the String
    // s goes out of scope at the end of main → memory is freed
}

2. Borrowing: Immutable and Mutable References

Instead of moving ownership, you can borrow a value. Rust distinguishes between:

  • Immutable references (&T): Any number of these may coexist, but they cannot modify the value.
  • Mutable references (&mut T): Only one may exist at a time, and it grants exclusive write access.

These rules are enforced at compile time and are the primary guard against data races.

let s = String::from("Hello");
let r1 = &s; // immutable borrow
let r2 = &s; // another immutable borrow – allowed
// let r3 = &mut s; // ERROR: cannot borrow as mutable while immutable borrows exist

3. Lifetimes – Tracking How Long References Live

A lifetime is a compile‑time construct that describes the scope during which a reference is valid. The most common lifetime is 'static, which means the reference lives for the entire duration of the program.

  • 'static: Used for string literals and global data that never gets dropped.
  • Elided lifetimes: In many simple cases the compiler can infer lifetimes, but explicit annotations become necessary when multiple lifetimes interact.

Consider this function signature:

fn function<'a, 'b>(s1: &'a str, s2: &'b str) -> &'a str { s1 }

The return value is tied to the lifetime of s1. A call is safe only when s1 lives at least as long as the returned reference, regardless of s2’s lifetime.

4. Common Pitfalls Demonstrated by Quiz Questions

4.1 What Happens When an Owner Goes Out of Scope?

When the owner leaves its lexical block, Rust automatically calls drop, freeing the memory. This deterministic cleanup eliminates memory leaks without a garbage collector.

4.2 Borrowing a Value

Given let s = String::from("Hello"); let borrow = &s;, both s and borrow can be used simultaneously because borrow is an immutable reference. The value is not moved; it remains owned by s.

4.3 Shadowing vs. Moving

In nested scopes, a new variable with the same name shadows the outer one but does not affect its ownership. The outer s remains valid after the inner block ends, so println!("{s}") works correctly.

4.4 The Compile‑Time Data‑Race Guard

The rule "you may have either one mutable reference or many immutable references" is the cornerstone of Rust’s guarantee against data races. The compiler enforces this rule at every borrow site.

4.5 The ‘static Lifetime

References with the 'static lifetime are valid for the entire program run. String literals like "Hello" have this lifetime, allowing them to be stored in global constants safely.

4.6 Lifetime Compatibility Example

For the function fn function<'l1, 'l2>(s1: &'l1 str, s2: &'l2 str) -> &'l1 str, a call is guaranteed to be valid when the first argument lives longer than the second. This ensures the returned reference never outlives its source.

4.7 Dangling References

The code snippet that tries to return a reference to a local variable fails because the reference would outlive the variable, creating a dangling pointer. Rust’s borrow checker catches this at compile time.

4.8 Mutable Borrow Restrictions

When a function takes &mut String, no other references (mutable or immutable) to that String may exist for the duration of the borrow. This exclusive access rule prevents simultaneous reads and writes.

5. Practical Guidelines for Writing Safe Rust Code

  • Prefer immutable references: Use &T whenever you don’t need to modify data.
  • Limit the scope of mutable borrows: Keep &mut T as short as possible to reduce contention.
  • Use explicit lifetimes when needed: When functions accept multiple references, annotate lifetimes to make relationships clear.
  • Avoid unnecessary cloning: Cloning duplicates data on the heap; borrowing is usually more efficient.
  • Leverage the compiler: Trust Rust’s error messages—they often point directly to the ownership or lifetime violation.

6. Frequently Asked Questions (FAQ)

Can a value be both owned and borrowed at the same time?

Yes. Ownership remains with the original variable while references (borrows) point to it. The key is that the borrowing rules (no mutable and immutable mix) are respected.

What is the difference between moving and copying?

Types that implement the Copy trait (like integers) are duplicated on assignment, leaving the original usable. Types without Copy (like String) are moved, transferring ownership.

When should I use the 'static lifetime?

Use 'static for data that truly lives for the program’s entire run, such as string literals or resources stored in a global cache.

7. Summary

Rust’s ownership, borrowing, and lifetime system work together to provide memory safety without a runtime garbage collector. By mastering these concepts you can write efficient, race‑free code that the compiler guarantees to be correct.