원클릭으로
rust-backend
Rust coding guidelines for the Windmill backend. MUST use when writing or modifying Rust code in the backend directory.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Rust coding guidelines for the Windmill backend. MUST use when writing or modifying Rust code in the backend directory.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
MUST use when writing Bun/TypeScript scripts.
MUST use when writing Bun Native scripts. The script must start with //native to run on the native worker.
MUST use when writing Deno/TypeScript scripts.
MUST use when writing Python scripts.
MUST use when writing Ansible playbooks.
MUST use when using the CLI, including debugging job failures and inspecting run history via `wmill job`.
| name | rust-backend |
| description | Rust coding guidelines for the Windmill backend. MUST use when writing or modifying Rust code in the backend directory. |
Apply these Windmill-specific patterns when writing Rust code in backend/.
Use Error from windmill_common::error. Return Result<T, Error> or JsonResult<T>:
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.
Never use SELECT * — always list columns explicitly. Critical for backwards compatibility when workers lag behind API version:
// Correct
sqlx::query_as!(Job, "SELECT id, workspace_id, path FROM v2_job WHERE id = $1", id)
// Wrong — breaks when columns are added
sqlx::query_as!(Job, "SELECT * FROM v2_job WHERE id = $1", id)
Use batch operations to avoid N+1:
// Preferred — single query with IN clause
sqlx::query!("SELECT ... WHERE id = ANY($1)", &ids[..]).fetch_all(db).await?
Use transactions for multi-step operations. Parameterize all queries.
Prefer Box<serde_json::value::RawValue> over serde_json::Value when storing/passing JSON without inspection:
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.
#[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,
}
Never block the async runtime. Use spawn_blocking for CPU-intensive work:
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.
pub(crate) instead of pub when possiblewindmill-api/src/ organized by domainwindmill-common/src/Always use rust-analyzer LSP for go-to-definition, find-references, and type info. Do not guess at module paths.
Destructure extractors directly in function signatures:
async fn process_job(
Extension(db): Extension<DB>,
Path((workspace, job_id)): Path<(String, Uuid)>,
Query(pagination): Query<Pagination>,
) -> Result<Json<Job>> { ... }