---
title: "Use MainSDK from Rust"
description: "Add the native mainsdk crate to a Rust service, store data and files as one owner, and return problem details from axum."
section: "guides"
status: "Planned for 1.5"
spec: "rust-sdk"
canonical: "https://mainsdk.dev/docs/guides/use-mainsdk-from-rust"
updated: "2026-10-28"
---

# Use MainSDK from Rust

> Add the native mainsdk crate to a Rust service, store data and files as one owner, and return problem details from axum.

Status: Planned for 1.5 · Canonical: https://mainsdk.dev/docs/guides/use-mainsdk-from-rust · Updated: 2026-10-28

You will add the `mainsdk` crate, run it on its local drivers, and wire an axum route. It takes about 15 minutes. The crate is native Rust: it shares no code with the TypeScript SDK, only `main.yaml`, the error codes and the wire formats.

## You need

- Rust 1.85 or later and a `main.yaml` with `schema: 1` (`npx mainsdk init` writes one).
- The crate is pre-release and not on crates.io. Use a path or git dependency from the MainSDK repository until it is published.

## 1. Add the crate

```toml title="Cargo.toml"
[dependencies]
mainsdk = { path = "../mainsdk/packages/rust/mainsdk", features = ["kv", "files", "axum"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```

Each interface sits behind a feature of the same name. `config` and `log` are on by default.

## 2. Call an interface

Calls are builders you `.await` directly. A tenant-scoped call needs a tenant, which travels in a task-local `Context`.

```rust title="src/main.rs"
use mainsdk::prelude::*;

#[tokio::main]
async fn main() -> mainsdk::Result<()> {
    let app = App::load().await?;
    Context::new().tenant("tnt_a").user("u1").scope(async {
        app.kv().set("cart", 3).await?;
        let file = app.files().upload(b"hello".to_vec()).owner("u1").await?;
        app.log().info("uploaded").field("file", &file.id).await;
        Ok::<_, MainError>(())
    }).await
}
```

Spawn with `mainsdk::spawn`, not `tokio::spawn`, so the context follows the task. A call that needs a tenant and has none fails with `MAIN-TENANT-0001`.

## 3. Return problem details from axum

```rust title="src/server.rs"
let router = axum::Router::new().route("/cart", handler).layer(app.layer());
```

The layer reads `traceparent`, sets the context, and renders any `MainError` as `application/problem+json` with its `code`, `fix` and `trace_id`.

## 4. Cancel and time out

Drop the future to cancel a call and release what it held. To stop one on purpose, pass a token with `.cancel(token)`; the call fails with `MAIN-SDK-0001`.

## 5. Check the project

`npx mainsdk check` runs clippy (`MAIN-RUST-0001`), cargo-audit (`MAIN-RUST-0002`) and cargo-deny (`MAIN-RUST-0003`) with the usual gates. A missing tool is a warning, `MAIN-RUST-0004`, never a pass. Run `cargo test` too.

## Next

- [Key-value store](https://mainsdk.dev/docs/interfaces/kv), [Files](https://mainsdk.dev/docs/interfaces/files) and the other first-cut interface pages show a Rust tab.
- Rust agents load the `mainsdk-rust` skill.
