---
title: Rust
description: The Rust SDK.
---

[`subako-function-sdk`](https://crates.io/crates/subako-function-sdk) turns ordinary Rust functions into tools. A function's name is the tool's name, its doc comment is the description the model reads, and its input and output types, which derive `JsonSchema`, become the tool's schemas.

## Start a package

The [Rust template](https://github.com/subako-ai/subako-function-template-rust) sets up the crate, the toolchain, and the script that builds the bundle:

```bash
rustup target add wasm32-wasip1
cargo install cargo-generate
cargo generate subako-ai/subako-function-template-rust --name orders
```

Or add the SDK to a crate of your own:

```toml Cargo.toml
[dependencies]
schemars = "1"
serde = { version = "1", features = ["derive"] }
subako-function-sdk = "0.2"

[profile.release]
opt-level = "s"
lto = true
```

## Tools

```rust src/main.rs
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use subako_function_sdk::{ToolError, function, package};

#[derive(Deserialize, JsonSchema)]
struct LookupInput {
    /// The order's number, such as 1042.
    order: u32,
}

#[derive(Serialize, JsonSchema)]
struct Order {
    status: String,
    total_cents: u64,
}

/// Look an order up by its number.
#[function]
fn lookup(input: LookupInput) -> Result<Order, ToolError> {
    if input.order == 0 {
        return Err(ToolError::new("order numbers start at 1"));
    }
    Ok(Order { status: "shipped".into(), total_cents: 12_900 })
}

/// Cancel an order that has not shipped.
#[function]
fn cancel(input: LookupInput) -> Result<Order, ToolError> {
    Err(ToolError::new(format!("order {} has already shipped", input.order)))
}

package! {
    name: "orders",
    description: "Looks orders up, and cancels them.",
    functions: [lookup, cancel],
}
```

- Every `#[function]` needs a doc comment: it is the description the model reads, and the build fails without one.
- A function takes one argument that is `Deserialize + JsonSchema`, and returns `Result<O, E>` where `O` is `Serialize + JsonSchema` and `E` converts into `ToolError`.
- Field doc comments become the descriptions of the schema's properties. Write them for the model: they are how it learns what to pass.
- `package!` names the package, lists its functions, and generates `main`.

## Errors

`ToolError::new` makes an error the model reads. It converts from `String`, `&str`, `std::io::Error`, and the SDK's `HttpError`, so `?` hands the model the reason. Input that does not match the schema is answered as an error before your function runs.

Return errors rather than panicking: a panic is a crash, and the model learns only that the function stopped.

## Files

Use `std::fs` on the [volume and assets](/functions/runtime#the-filesystem):

```rust
let csv = std::fs::read_to_string("/volume/tmp/export.csv")?;
std::fs::create_dir_all("/volume/data/reports")?;
std::fs::write("/volume/data/reports/q3.md", summary)?;
let template = std::fs::read_to_string("/package/templates/report.md")?;
```

## HTTP

`http::request` sends a request through Subako, under the grant's network and credentials:

```rust
use subako_function_sdk::http::{self, Request, RequestBody, ResponseBody, ResponseOutput};

/// Fetch a page's text.
#[function]
fn fetch(input: FetchInput) -> Result<Page, ToolError> {
    let response = http::request(&Request::get(input.url))?;
    match response.body {
        ResponseBody::Inline { text } => Ok(Page { status: response.status, text }),
        ResponseBody::File { .. } => Err(ToolError::new("the body was saved to a file")),
    }
}
```

The body comes back inline as text, up to `http::INLINE_MAX_BYTES`. Set the request's `output` to `ResponseOutput::File { path }` to stream a larger or binary body into the volume instead, and send a volume file as the body with `RequestBody::File { path }`.

## Images

Return an `Answer` to show the model an image from the volume as well as the value:

```rust
use subako_function_sdk::{Answer, ToolError, function};

/// Plot a chart and show it.
#[function]
fn plot(input: PlotInput) -> Result<Answer<Plot>, ToolError> {
    let path = "/volume/tmp/chart.png".to_string();
    // ... write the chart to `path` ...
    Ok(Answer::new(Plot { path: path.clone() }).with_image_file(path))
}
```

## Build and test

```bash
cargo test                                         # tools run as plain Rust
cargo build --release --target wasm32-wasip1       # target/wasm32-wasip1/release/orders.wasm
cargo run -- --subako-manifest > subako-package.json
```

Built for your own machine, the crate prints its `subako-package.json` when run with `--subako-manifest`, so the manifest always matches the code. The template's `package.sh` does all three and packs `dist/<name>.tar.gz`, ready to [upload](/functions/quickstart#upload-it).

The API reference is on [docs.rs](https://docs.rs/subako-function-sdk).
