SuperAPI AI Directory

rust-dev

Coding & development

View repository

Updated 10-15 21:11

About this skill

Rust Development Specification

References: Rust API Guidelines, Rust Async Book, tokio.rs, Axum official examples, Effective Rust

Scope of Application: Backend web services (Axum + Tokio route). CLI/wasm/embedded are not covered by this specification.


Toolchain

# 格式化
cargo fmt --all
# 静态检查,warning 当 error
cargo clippy --all-targets -- -D warnings
# 测试
cargo test --all-features
# CVE 扫描(独立安装)
cargo audit
# 许可证/重复依赖检查(独立安装)
cargo deny check
# 检测未使用依赖
cargo machete

edition: New projects default to edition = "2024", old projects keep 2021 without forced migration.

Version Baseline(hardcoded to avoid repeated decision-making):

  • Tokio 1.x,Axum 0.8,tower-http 0.6
  • serde 1、tracing 0.1、tracing-subscriber 0.3
  • DB:sqlx 0.8(runtime-tokio-rustls)

Naming Conventions

Type Rule Example
Module/File snake_case user_service.rs
Function/Variable snake_case fetch_user, db_pool
Type/Trait/Enum UpperCamelCase UserRepo, AppError
Constant/Static SCREAMING_SNAKE_CASE MAX_RETRY
Generic Single uppercase letter or UpperCamelCase T, Req, Resp
Lifetime Short lowercase 'a, 'ctx

Prohibited:common、util、helper、base and other meaningless module names. Modules are named by responsibility (api、db、service)。


Module Organization

Refer to apple-store-price-rs / org-site-backend for empirically-driven splitting, split files by responsibility,do not pile up lib.rs:

src/
├── main.rs          # 仅 bootstrap:日志初始化、读配置、建路由、启服务
├── config.rs        # 配置加载(env / toml)
├── error.rs         # AppError + IntoResponse impl
├── db.rs            # 数据库连接池/迁移
├── api.rs           # 路由聚合 + handler
├── service/         # 业务逻辑(无副作用层)
│   └── user.rs
└── models.rs        # 领域类型 + serde 派生

Visibility:pub(crate) takes precedence over pub. Only use pub。


Error Handling (Core)

Choose a side by scenario:

Scenario Choice Reason
Library crate thiserror Callers need to match by variant
Binary application top level anyhow Only care about "success/failure + context"
Cross-boundary (handler / public API) Custom AppError + IntoResponse Control the external error shape

Prohibitedin production code:

  • .unwrap() / .expect()(only available in tests and const contexts)
  • panic!()(only for "logically unreachable" cases and with a SAFETY comment)

clippy configuration (in Cargo.toml or clippy.toml):

[lints.clippy]
unwrap_used = "deny"
expect_used = "deny"
panic = "deny"

Use ?for error propagation, use From across types to implement automatic conversion:

#[derive(thiserror::Error, Debug)]
pub enum AppError {
    #[error("db error: {0}")]
    Db(#[from] sqlx::Error),
    #[error("not found")]
    NotFound,
    #[error("bad request: {0}")]
    BadRequest(String),
}

See references/async-axum.md the IntoResponse implementation.


Async Programming Red Lines

1. Blocking calls must be offloaded to spawn_blocking

// ❌ 在 async fn 里直接调用阻塞 API
let data = std::fs::read("big.bin")?;

// ✅
let data = tokio::task::spawn_blocking(|| std::fs::read("big.bin")).await??;

Criteria: CPU-intensive > 10ms,std::fs、std::thread::sleep, synchronous DB driver,reqwest::blocking — all must spawn_blocking。

2. Do not hold .await across std::sync::Mutex

// ❌ 编译能过,运行时死锁/性能崩溃
let g = std_mutex.lock().unwrap();
do_async().await;

// ✅ 用 tokio::sync::Mutex
let g = tokio_mutex.lock().await;
do_async().await;

3. Rc / RefCell cannot cross .await: violates the Send boundary, compilation error, switch to Arc / Mutex。

4. tokio::select! branches must be cancel-safe

  • Cancel-safe ✅:tokio::time::sleep、channel.recv、tokio::io::AsyncRead marked methods
  • Not cancel-safe ❌: self-implemented async fn(may lose data on cancellation)

When unsure, save state to variables outside select! , and branches only read and write local variables.

5. Graceful shutdown: use tokio::signal::ctrl_c() + axum::serve(...).with_graceful_shutdown()。

More pitfalls see references/async-axum.md。


Ownership and Borrowing Minimal Set

Function parameters:

Want Use Do not use
Read-only string &str &String
Read-only slice &[T] &Vec<T>
Path &Path &PathBuf / &str
String parameters may come from multiple sources impl AsRef<str> Multiple overloads

Shared decision tree:

需要跨线程共享?
├── 否 → 用 Box<T> 或值传递
└── 是 → Arc<T>
        ├── 不可变共享 → Arc<T>
        ├── 读多写少 → Arc<RwLock<T>>
        └── 读写均衡 → Arc<Mutex<T>>

Clone is not a bad thing, but inside a loop Clone large objects require vigilance, consider Arc or Cow<'_, T>。


Axum practical key points

type AppState = Arc<InnerState>;

let app = Router::new()
    .nest("/api/users", user::routes())     // 按模块拆 + nest
    .layer(TraceLayer::new_for_http())       // 日志中间件
    .layer(TimeoutLayer::new(Duration::from_secs(30)))
    .with_state(state);
  • State should always be Arc wrapped, do not clone bare structs
  • Handler error type implements IntoResponse, unify the external form (status + json body)
  • Split routes by module fn routes() -> Router<AppState>, avoid main.rs bloat
  • Middleware goes through tower-http: CORS / timeout / rate limiting / logging / static files

See references/async-axum.md。


Dependency management

Version specification:Cargo.toml defaults to caret ("1" equivalent to ^1), do not pin =1.2.3 unless there is a compatibility issue.

Declare features explicitly, do not blindly rely on defaults:

# ✅ 显式开启需要的 feature
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }

Choose rustlsfor TLS, avoid openssl(cross-platform compilation is troublesome).

Dry-run before upgrading:

cargo update --dry-run
# 独立安装
cargo outdated

Cargo.lock Binary projects must commit it, library projects do not commit it.


Testing conventions

// 异步测试用 #[tokio::test]
#[tokio::test]
async fn fetches_user() {
    let svc = test_service().await;
    let user = svc.find(1).await.unwrap();   // 测试代码可以 .unwrap()
    assert_eq!(user.name, "alice");
}

// 表驱动:可选用 rstest crate
#[rstest::rstest]
#[case(1, 2, 3)]
#[case(-1, 1, 0)]
fn adds(#[case] a: i32, #[case] b: i32, #[case] expected: i32) {
    assert_eq!(a + b, expected);
}
  • Unit tests:#[cfg(test)] mod tests { ... } stay close to the code under test
  • Integration tests:tests/ at the top-level directory, each file is its own crate
  • Tests may allow .unwrap()(failure means the test fails, which is exactly expected)

unsafe red lines

Business code should in principle prohibit unsafe。

If it must be used (FFI / extreme performance scenarios):

  1. There must be a // SAFETY: ... comment before the block, explaining the invariants maintained
  2. Shrink the unsafe block to the minimum scope
  3. Prefer to encapsulate it into a safe API exposed externally
  4. Go through code review, mark it separately for review

Performance key points

Pitfall Solution
Inside the loop format! / string concatenation Use String::with_capacity + write!
Frequent Vec reallocation Vec::with_capacity(n)
Inside the loop .clone() large objects Borrow or Arc
Box<dyn Trait> abuse Prefer generics <T: Trait>(static dispatch)
String as key Prefer &str or Cow<'static, str>
Blocking IO inside async spawn_blocking(see the async chapter)

Performance analysis:

cargo build --release
# 火焰图(独立安装)
cargo flamegraph
# criterion 基准测试
cargo bench

Detailed reference

File Content
references/rust-style.md Naming details,From/TryFrom conventions, newtype pattern, iterator preference
references/async-axum.md spawn_blocking boundaries, select! cancellation safety, tower middleware composition, IntoResponse pattern, shutdown signal

📋 This reply follows:rust-dev - [specific section]