home / skills / windmill-labs / windmill / write-script-rust
This skill guides you in writing Rust scripts with a serializable return type and proper main signature for Windmill automation.
npx playbooks add skill windmill-labs/windmill --skill write-script-rustReview the files below or copy the command above to add this skill to your agents.
---
name: write-script-rust
description: MUST use when writing Rust scripts.
---
## CLI Commands
Place scripts in a folder. After writing, run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Use `wmill resource-type list --schema` to discover available resource types.
# Rust
## Structure
The script must contain a function called `main` with proper return type:
```rust
use anyhow::anyhow;
use serde::Serialize;
#[derive(Serialize, Debug)]
struct ReturnType {
result: String,
count: i32,
}
fn main(param1: String, param2: i32) -> anyhow::Result<ReturnType> {
Ok(ReturnType {
result: param1,
count: param2,
})
}
```
**Important:**
- Arguments should be owned types
- Return type must be serializable (`#[derive(Serialize)]`)
- Return type is `anyhow::Result<T>`
## Dependencies
Packages must be specified with a partial cargo.toml at the beginning of the script:
```rust
//! ```cargo
//! [dependencies]
//! anyhow = "1.0.86"
//! reqwest = { version = "0.11", features = ["json"] }
//! tokio = { version = "1", features = ["full"] }
//! ```
use anyhow::anyhow;
// ... rest of the code
```
**Note:** Serde is already included, no need to add it again.
## Async Functions
If you need to handle async functions (e.g., using tokio), keep the main function sync and create the runtime inside:
```rust
//! ```cargo
//! [dependencies]
//! anyhow = "1.0.86"
//! tokio = { version = "1", features = ["full"] }
//! reqwest = { version = "0.11", features = ["json"] }
//! ```
use anyhow::anyhow;
use serde::Serialize;
#[derive(Serialize, Debug)]
struct Response {
data: String,
}
fn main(url: String) -> anyhow::Result<Response> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let resp = reqwest::get(&url).await?.text().await?;
Ok(Response { data: resp })
})
}
```
This skill provides a clear, opinionated template and checklist for writing Rust scripts that run on the platform. It enforces the required function signature, serialization rules, dependency embedding, and patterns for mixing sync and async code. Use it to produce scripts that deploy cleanly and interoperate with the runtime and resource tooling.
It inspects your script for a main function with owned argument types and a return of anyhow::Result<T> where T implements serde::Serialize. It requires a partial Cargo manifest embedded as a code fence at the top of the file so the runtime knows which crates to compile. For async work, it recommends creating a tokio runtime inside a synchronous main so the script stays compatible with the platform.
What must the main function look like?
main must accept owned arguments and return anyhow::Result<T>, where T derives serde::Serialize.
How do I use async code in a script?
Keep main synchronous and create a tokio runtime inside it, then block_on your async work.
Where do I declare dependencies?
Embed a partial [dependencies] Cargo manifest at the top of the script inside the specified code-fence format; serde is already included.