I’ve been using cargo scripts for various tasks including in the Azure SDK for Rust. Shell scripts are versatile - and fast - but sometimes you just need the programmatic power and third-party crates that Rust gives you.

#!/usr/bin/env -S cargo +nightly -Zscript
---
[package]
edition = "2024"

[dependencies]
clap = { version = "4.6.1", features = ["derive"] }
---
use clap::Parser;

fn main() {
    let args = Args::parse();
    println!("{}", hello(args.name.as_deref()));
}

#[derive(Parser)]
struct Args {
    name: Option<String>,
}

fn hello(name: Option<&str>) -> String {
    format!("Hello, {}", name.unwrap_or("world"))
}

#[test]
fn test_hello() {
    assert_eq!(hello(None), "Hello, world");
    assert_eq!(hello(Some("people")), "Hello, people");
}

Recently, I prototyped dynamic completion in clap using a cargo script for easily sharing as a gist, though that meant configuring the #[command(name)] as the name of the source file e.g., dynamic.rs. Even though it compiles to dynamic, you run it as dynamic.rs which means shell completion has to match that name.

Cargo scripts have evolved quite a bit since I started using them a couple years ago. cargo fmt works automatically when configured in editors like helix, but rust-analyzer support isn’t complete yet making cargo clippy especially handy. Turns out, you can run clippy, cargo doc, and other commands using --manifest-path.

cargo +nightly clippy -Zscript --manifest-path script.rs

You can even run tests like shown in the example above:

cargo +nightly test -Zscript --manifest-path script.rs