Skip to main content
Lesson 1 of 3Beginner⏱ 15 mins

Ownership & Borrowing in Request Handlers

Understand how Rust's ownership model affects your Actix Web handlers and application state.

0% of path

Learning Objectives

  • Understand why you must clone state
  • Learn about Arc<Mutex<T>>
  • Avoid borrow checker errors in handlers

##Ownership: Rust's superpower

Rust manages memory without a garbage collector and without manual free calls. It does this with three simple rules about ownership. They feel strange at first, but they are exactly why Actix Web servers are fast and safe.

#The three rules

  • Every value has exactly one owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value is dropped (its memory is freed).
code_blocksrust
fn main() {
    let name = String::from("Ada");   // `name` owns the string
    let other = name;                  // ownership MOVES to `other`
    // println!("{name}");             // error: value moved
    println!("{other}");
}

After let other = name;, the variable name is no longer valid. Rust moved the string instead of copying it, so there is never a question about who must free it.

#Borrowing with references

Moving everywhere would be annoying. Instead you can borrow a value with a reference, which lets you use it without taking ownership:

code_blocksrust
fn length(text: &String) -> usize {
    text.len()
}

fn main() {
    let s = String::from("hello");
    let n = length(&s);      // lend `s`, do not give it away
    println!("{s} has {n} bytes");
}

There are two kinds of borrows:

  • &T, a shared reference. You can have many at once, and they are read-only.
  • &mut T, an exclusive reference. You can have only one, and nothing else may read the value at the same time.
code_blocksrust
fn add_exclamation(text: &mut String) {
    text.push('!');
}

fn main() {
    let mut s = String::from("hi");
    add_exclamation(&mut s);
    println!("{s}"); // hi!
}

This "many readers or one writer" rule prevents data races at compile time.

#Copy versus move

Small simple types such as integers, booleans and char implement Copy, so assigning them copies the value. Types that own heap memory, like String and Vec, are moved.

#Why this matters in Actix Web

  • Handlers receive owned values from extractors (web::Json<T>, web::Path<T>), so you can freely use them.
  • Closures given to HttpServer::new often need move because they must own what they capture.
  • Shared state uses web::Data, which is an Arc. Arc gives shared ownership across threads: cloning it only copies a pointer.

When the compiler says "value moved" or "cannot borrow as mutable", it is not being difficult. It is pointing at a real bug that would have crashed other languages at runtime.

Interactive Code Inspector

Select any highlighted line of code below to deeply inspect its mechanics, understand why it exists, and what happens if you change it.

src/main.rs
1use actix_web::{get, web, App, HttpServer, Responder};
2use std::sync::Mutex;
3
4struct AppState {
5 counter: Mutex<i32>,
6}
7
8#[get("/")]
9async fn index(data: web::Data<AppState>) -> impl Responder {
10 let mut counter = data.counter.lock().unwrap();
11 *counter += 1;
12 format!("Visits: {}", counter)
13}
14
15#[actix_web::main]
16async fn main() -> std::io::Result<()> {
17 let state = web::Data::new(AppState {
18 counter: Mutex::new(0),
19 });
20
21 HttpServer::new(move || {
22 App::new()
23 .app_data(state.clone()) // Ownership clone!
24 .service(index)
25 })
26 .bind(("127.0.0.1", 8080))?
27 .run()
28 .await
29}
Line 22
.app_data(state.clone())

What it does

Clones the `web::Data` pointer (which acts like an Arc).

Why use it?

The `move ||` closure takes ownership of its environment. Actix calls this closure multiple times (once per CPU core). If we didn't clone, the first call would consume `state`, and subsequent calls would fail to compile.

How it works underneath

web::Data wraps your state in an Arc. Cloning it just increments a reference count; it doesn't duplicate the memory.

If you remove it

Compiler error: 'use of moved value: `state`'.

"What If?" Experiment

Learning by doing. Modify the code to see what happens.

Goal

Trigger and read a borrow checker error.

Starting Code
let a = String::from("x"); let b = a; println!("{a}");
Suggested Modification

Compile it and read the error message.

Expected Result

The compiler reports "borrow of moved value: a" and suggests cloning.

Explanation: Rust's errors point out exactly which line moved the value, which is the fastest way to learn the rules.

Common Mistakes

Anticipate and avoid these frequent pitfalls when working with this concept.

!

Using a value after moving it

Passing a String by value gives away ownership.

Incorrect Pattern
let s = String::from("hi"); takes_ownership(s); println!("{s}");
Correct Pattern
let s = String::from("hi"); borrows(&s); println!("{s}");

Explanation: Pass a reference (&s) when the function only needs to read the value, so you keep ownership.

!

Two mutable borrows at once

Rust forbids aliasing a mutable reference.

Incorrect Pattern
let a = &mut s; let b = &mut s; a.push('x');
Correct Pattern
{ let a = &mut s; a.push('x'); } let b = &mut s;

Explanation: Finish using the first mutable borrow before creating the second, or restructure so only one place edits the value.

Practical Exercise

Fix the borrow errors

Task

Make the program compile by borrowing instead of moving, then print the string and its length.

Requirements

  • Write a function that takes &str and returns its length
  • Call it without giving away the String
  • Print both the string and its length

Need a hint?

Try solving the exercise in your local environment first. Once you have a solution, or if you get completely stuck, reveal the answer.

Real-World Application

Fast Actix Web servers rely on ownership: request data is moved into handlers without copying, and shared resources like database pools are passed around as cheap Arc clones.

Key Takeaways

  • Each value has one owner and is dropped when the owner goes out of scope.
  • Assigning or passing a String moves it; use & to borrow instead.
  • You can have many &T borrows or exactly one &mut T at a time.
  • web::Data uses Arc for shared ownership across worker threads.

Finished reading?

Mark this lesson as done to track your progress and earn XP.