Files
marvin-turnstone/skills/rust-best-practices/SKILL.md
T
marvin 18fdff7d8c Import 13 skills from the old opencode setup into turnstone
skills/ carries the SKILL.md files; scripts/import_skills.py runs
inside the VM via turnstone's own storage layer (SKILL.md parser +
risk scanner) so rows match what the console admin API would write.
Server must be restarted after import (stale WAL snapshot).
2026-09-28 20:24:47 +02:00

2.3 KiB

name, description
name description
rust-best-practices Best-practices for Rust software. Load into a workstream whenever you need to read or write a Rust (.rs) file.

Best practices for programming Rust

  • Run cargo check, cargo clippy, and cargo clippy before comitting to git.
  • Don't ignore warnings from cargo.
  • Don't supress warnings using e.g. #[allow(warning)]
  • Never use unsafe without asking the user for explicit permission.

When setting up a new rust project, enable these lints:

[lints.clippy]
allow_attributes = "warn"
as_ptr_cast_mut = "warn"
as_underscore = "warn"
borrow_as_ptr = "warn"
implicit_clone = "warn"
undocumented_unsafe_blocks = "warn"
unicode_not_nfc = "warn"
unused_async = "deny"
wildcard_dependencies = "deny"

[lints.rust]
absolute_paths_not_starting_with_crate = "deny"
explicit_outlives_requirements = "warn"
macro_use_extern_crate = "deny"
missing_abi = "deny"
non_ascii_idents = "forbid"
rust_2018_idioms = { level = "deny", priority = -1 }
single_use_lifetimes = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"

Newtypes and "Parse, Don't Validate"

Use newtypes to make invalid states unrepresentable. Instead of passing raw strings and validating at every use site, parse once into a validated type.

Pattern

pub struct Email(String);  // private field prevents construction without validation

impl Email {
    pub fn parse(s: &str) -> Result<Self, ParseError> {
        if !s.contains('@') {
            return Err(ParseError::InvalidEmail);
        }
        Ok(Self(s.to_owned()))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

Guidelines

  • Private fields: Force construction through parse() only
  • Parse at boundaries: Validate at system edges (API handlers, CLI args, config loading)
  • Use the type in signatures: fn create_user(email: Email) not fn create_user(email: &str)
  • No re-validation: Once parsed, the type guarantees validity

When to Apply

  • Email addresses, passwords, phone numbers
  • File paths, URLs, identifiers
  • Any domain value with invariants
  • Configuration values requiring validation

Anti-pattern to Avoid

// BAD: validation scattered, easy to forget
fn handler(email: &str) {
    if !validate_email(email) { return Err(...); }
    create_user(email)  // still just &str, could be invalid
}