Rust Backend

windmill-labs/windmill/.agents/skills/rust-backend

by windmill-labsfb22e5ce368eba9146e0d51b8fdabb52ca9f68f0No licenseListed Oct 9, 2026Updated Oct 9, 2026

Rust coding guidelines for the Windmill backend. MUST use when writing or modifying Rust code in the backend directory.

Instructions onlySoftware Development
AI-generated overview

Rust coding guidelines for the Windmill backend, covering error handling, SQLx, async, serde and Axum patterns.

What it does
Provides Windmill-specific conventions for writing or modifying Rust code under the backend directory. It prescribes error types and result aliases, SQLx query rules such as avoiding SELECT * and using batch operations, JSON and serde handling, async and mutex guidance, module visibility, and Axum handler style. It also documents feature telemetry allowlist behavior and points to rust-analyzer for code navigation.
When to use it
Use when writing or modifying Rust code in the Windmill backend directory. It is intended for contributors who need to follow the project's established patterns for errors, database access, concurrency and API handlers.
Requirements
No scripts are included; it is instructions only. It assumes a Rust project with sqlx, serde, tokio and Axum, and recommends rust-analyzer LSP for navigation.

Windmill Rust Patterns

Apply these Windmill-specific patterns when writing Rust code in backend/.

Error Handling

Use Error from windmill_common::error. Return Result<T, Error> or JsonResult<T>:

rust
use windmill_common::error::{Error, Result};
pub async fn get_job(db: &DB, id: Uuid) -> Result<Job> {    sqlx::query_as!(Job, "SELECT id, workspace_id FROM v2_job WHERE id = $1", id)        .fetch_optional(db)        .await?        .ok_or_else(|| Error::NotFound("job not found".to_string()))?;}

Never panic in library code. Reserve .unwrap() for compile-time guarantees.

SQLx Patterns

Never use SELECT * — always list columns explicitly. Critical for backwards compatibility when workers lag behind API version:

rust
// Correctsqlx::query_as!(Job, "SELECT id, workspace_id, path FROM v2_job WHERE id = $1", id)
// Wrong — breaks when columns are addedsqlx::query_as!(Job, "SELECT * FROM v2_job WHERE id = $1", id)

Use batch operations to avoid N+1:

rust
// Preferred — single query with IN clausesqlx::query!("SELECT ... WHERE id = ANY($1)", &ids[..]).fetch_all(db).await?

Use transactions for multi-step operations. Parameterize all queries.

JSON Handling

Prefer Box<serde_json::value::RawValue> over serde_json::Value when storing/passing JSON without inspection:

rust
pub struct Job {    pub args: Option<Box<serde_json::value::RawValue>>,}

Only use serde_json::Value when you need to inspect or modify the JSON.

Serde Optimizations

rust
#[derive(Serialize, Deserialize)]pub struct Job {    #[serde(skip_serializing_if = "Option::is_none")]    pub parent_job: Option<Uuid>,    #[serde(skip_serializing_if = "Vec::is_empty")]    pub tags: Vec<String>,    #[serde(default)]    pub priority: i32,}

Async & Concurrency

Never block the async runtime. Use spawn_blocking for CPU-intensive work:

rust
let result = tokio::task::spawn_blocking(move || expensive_computation(&data)).await?;

Mutex selection: Prefer std::sync::Mutex (or parking_lot::Mutex) for data protection. Only use tokio::sync::Mutex when holding locks across .await points.

Use tokio::sync::mpsc (bounded) for channels. Avoid std::thread::sleep in async contexts.

Module Structure & Visibility

  • Use pub(crate) instead of pub when possible
  • Place new code in the appropriate crate based on functionality
  • API endpoints go in windmill-api/src/ organized by domain
  • Shared functionality goes in windmill-common/src/

Code Navigation

Always use rust-analyzer LSP for go-to-definition, find-references, and type info. Do not guess at module paths.

Feature Telemetry

FEATURE_USAGE_KINDS in windmill-api-workspaces/src/workspaces.rs is an allowlist: a (feature, kind) pair missing from it is dropped by valid_feature_usage_event with a bare continue — no error, and the route still returns 204. Adding a counter on the frontend without registering it here records nothing. See docs/feature-telemetry.md.

Axum Handlers

Destructure extractors directly in function signatures:

rust
async fn process_job(    Extension(db): Extension<DB>,    Path((workspace, job_id)): Path<(String, Uuid)>,    Query(pagination): Query<Pagination>,) -> Result<Json<Job>> { ... }

Source and attribution

Source:windmill-labs/windmillin.agents/skills/rust-backendat commitfb22e5c

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal