C.W.K.
Stream
Lesson 01 of 06 · published

Managed State with State<T>

~13 min · tauri, state, rust, manage

Level 0Web Tourist
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Commands are stateless functions. When they need to remember something together, you give the app a memory: managed state."

Register Once, Inject Everywhere

A command by itself forgets everything between calls. To share data — a config, a database pool, a counter, a cache — you manage it on the Builder with .manage(value). After that, any command can ask for it by declaring a parameter of type tauri::State<T>. Tauri sees that parameter and injects the managed instance automatically. The frontend never passes it; it lives entirely in the core.

One Managed Value Per Type

Managed state is keyed by type. You manage one AppConfig, one Database, one Counter — asking for State<AppConfig> always gives you the same instance. If you need two things of the same underlying type, wrap them in distinct newtypes (e.g. struct ReadPool(Pool) and struct WritePool(Pool)) so the type system can tell them apart. This is a feature: it makes 'which state?' unambiguous.

It's a Shared Reference

What you receive is a shared, read-only handle to the managed value. That's perfect for things you only read (a loaded config). But it means you cannot mutate fields directly through it — the borrow checker won't allow shared mutation. When state needs to change, you reach for interior mutability (a Mutex), which is exactly the next lesson. For now: register read-mostly state and inject it into the commands that need it.

Code

Manage once, inject into commands·rust
use tauri::Manager;

struct AppConfig {
    api_base: String,
    theme: String,
}

// A command receives managed state by declaring a State<T> parameter.
#[tauri::command]
fn get_api_base(config: tauri::State<AppConfig>) -> String {
    config.api_base.clone()
}

pub fn run() {
    tauri::Builder::default()
        // Register the value once; Tauri owns it for the app's lifetime.
        .manage(AppConfig {
            api_base: "https://api.example.com".into(),
            theme: "dark".into(),
        })
        .invoke_handler(tauri::generate_handler![get_api_base])
        .run(tauri::generate_context!())
        .expect("error while running app");
}

External links

Exercise

Add managed state to your app: a struct holding a single read-only string (like an app version or API base). Register it with .manage(), then write a command that reads it via tauri::State<T> and returns it to the frontend. Confirm the value round-trips. You've given your core its first shared memory.
Hint
Two parts: .manage(MyConfig { ... }) in the Builder, and a command parameter config: tauri::State<MyConfig>. If you get 'state not managed', you wrote the command but forgot the .manage() line.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.