RustArena

Step 5 of 7

Shared state: an in-memory store

A real server handles many requests at the same time, on several threads (or async tasks), and they all need the same data: the users, the items, the counters. That's the shared state problem from the concurrency modules, and the solution is the same: wrap the data in a Mutex and share it with an Arc.

use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use std::thread;

#[derive(Default)]
struct Store {
    notes: BTreeMap<u32, String>,
    next_id: u32,
}

#[derive(Default)]
struct AppState {
    store: Mutex<Store>,
}

fn create_note(state: &AppState, text: &str) -> u32 {
    let mut store = state.store.lock().unwrap();
    store.next_id += 1;
    let id = store.next_id;
    store.notes.insert(id, text.to_string());
    id
}

fn count_notes(state: &AppState) -> usize {
    state.store.lock().unwrap().notes.len()
}

fn main() {
    let state = Arc::new(AppState::default());
    let mut clients = Vec::new();
    for client in 0..4 {
        let state = Arc::clone(&state);
        clients.push(thread::spawn(move || {
            for i in 0..25 {
                create_note(&state, &format!("note {i} from client {client}"));
            }
        }));
    }
    for c in clients {
        c.join().unwrap();
    }
    println!("{} notes", count_notes(&state));
    let store = state.store.lock().unwrap();
    println!("ids {} to {}", store.notes.keys().next().unwrap(), store.notes.keys().last().unwrap());
}
100 notes
ids 1 to 100

Piece by piece:

Common mistakes

Your task

Write handle for an inventory API. The types and helpers are ready; the routes are:

Challenge 2

Add replacement: write fn update_item(state: &AppState, id: u32, body: &str) -> Response for PUT /items/{id}. The body is a full item without id ({"name": "big mug", "qty": 2}). Answer 400 {"error":"invalid JSON"} if it doesn't parse, 404 {"error":"item 7 not found"} if there's no such item, otherwise replace its name and qty and answer 200 with the updated item.

Challenge 3

Protect the API with a rate limiter: each client may make limit requests. Write fn check(&self, client: &str) -> Response on Limiter: count the request; while the client's count is within the limit answer 200 with {"remaining":<requests left>}, after that 429 with {"error":"too many requests"}. It must be correct when many threads call it at once.

Previous: Errors become status codes Next: Testing handlers