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).
85 lines
2.3 KiB
Markdown
85 lines
2.3 KiB
Markdown
---
|
|
name: rust-best-practices
|
|
description: >
|
|
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:
|
|
|
|
```toml
|
|
[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
|
|
|
|
```rust
|
|
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
|
|
|
|
```rust
|
|
// 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
|
|
}
|
|
```
|