systems-rust
Rust done well: ownership and borrowing idioms, error handling with thiserror and anyhow, traits, generics or trait objects, lifetimes, iterators, unsafe with SAFETY proofs and Miri, async with tokio (Send bounds, cancellation, blocking work), shared state and channels, performance habits, clippy and rustfmt, features and MSRV, choosing crates, and cross-compiling. Read when writing or reviewing Rust.
- License 许可
- Apache-2.0
- 所属仓库
- enowdev/enowxcli
- 最近更新
- 2026-10-06
- Stars
- ★ 67
- Forks
- 21
01 前言 Front Matter
- name
- systems-rust
- description
- Rust done well: ownership and borrowing idioms, error handling with thiserror and anyhow, traits, generics or trait objects, lifetimes, iterators, unsafe with SAFETY proofs and Miri, async with tokio (Send bounds, cancellation, blocking work), shared state and channels, performance habits, clippy and rustfmt, features and MSRV, choosing crates, and cross-compiling. Read when writing or reviewing Rust.
02 资源文件 1
- 脚本scripts/validate-force-link.js2.8 KB
03 SKILL.md 正文
Rust, idiomatic and sound
Rust on autopilot calls .clone() until the borrow checker goes quiet,
unwrap()s every Result, returns Box<dyn Error> from a library, wraps
everything in Arc<Mutex<_>>, sleeps a thread inside an async task, and
reaches for unsafe to get past a lifetime error. This is the Rust a
senior reviewer expects: borrowing by default, typed errors, unsafe with
proofs, async that cancels cleanly, and the tooling that keeps it so.
General rules in systems, threads and atomics in systems-concurrency,
a web service in backend-stack-rust.
1. Ownership and API shape
- Parameters borrow:
&strnot&String,&[T]not&Vec<T>,impl AsRef<Path>for paths. TakeStringorVec<T>only when the function keeps it. Return owned values, or a borrow ofselffor a view;Cow<'_, str>when the input usually comes back unchanged. - Clone on purpose: an
Arcor a smallCopyvalue is cheap; cloning aVecto satisfy the borrow checker means the data flow is wrong (split the borrow, restructure the loop, use indices). - Newtypes carry invariants and units:
UserId(u64),Millis(u64),Name(String)with a validatingTryFrom, a private field, noDerefMut.NonZeroU32givesOptiona free niche. - Builders past three or four optional settings;
#[must_use]where a result must not be dropped;#[non_exhaustive]on public types that will grow;Debugalways (by hand when a field is secret).
2. Errors
Libraries: an error enum per crate or module with thiserror 2, variants
a caller can match, sources kept. Binaries: anyhow (or eyre with
color-eyre), with context on every ? that crosses a boundary.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum LoadError {
#[error("reading {path}")]
Io { path: PathBuf, source: std::io::Error },
#[error("header truncated: need {need} bytes, have {have}")]
Truncated { need: usize, have: usize },
#[error("unsupported version {0}")]
Version(u16),
}
// In the binary, with `use anyhow::Context;`
let cfg = load(&path).with_context(|| format!("loading config {}", path.display()))?;
- Messages lowercase, no trailing period, the source's text not repeated:
the chain prints it (
{:#}with anyhow;{:?}adds the backtrace underRUST_BACKTRACE=1). - No
unwrap()outside tests and invariants proven on the line above;expectstates the proof (.expect("regex literal is valid"));lock().expect("state lock poisoned")passes on an earlier panic. - Panics are for bugs, never for input; no
Result<T, String>in a library API;mainreturnsanyhow::Result<()>orExitCode.
3. Traits, generics, trait objects
| Need | Use |
|---|---|
| One concrete type per call site, speed matters | fn run<R: Read>(r: R) or fn run(r: impl Read) |
| Mixed collection, plug-in chosen at run time | Box<dyn Trait>, Arc<dyn Trait + Send + Sync> |
| Hide a long return type | -> impl Iterator<Item = T> + '_ |
| Only your crate may implement it | sealed: pub trait Codec: private::Sealed |
- Generics monomorphise: fast code, bigger binary, slower builds. Keep the
generic layer thin over a non-generic inner function
(
open(p: impl AsRef<Path>)callingopen_inner(p.as_ref())). dynneeds a dyn-compatible trait: no generic methods, noSelfby value, noasync fn(nativeasync fnin traits is for static dispatch; fordyn,async-traitor a boxed future).
4. Lifetimes and iterators
- Trust elision. A struct with a lifetime (
Parser<'a>) is a short-lived view; types that live long own their data.T: 'staticmeans "owns its data", not "lives forever": forspawn, move owned data or anArcin, or borrow withstd::thread::scope. - No self-referential structs: keep the buffer plus
Range<usize>indices (self_cellif that fails, never hand-writtenunsafe). Graphs:Vec<Node>withu32orslotmapkeys, orRcdown,Weakup. - Iterators are lazy and collect once, at the end;
collect::<Result<Vec<_>, _>>()stops at the first error;retain,drainandextendwork in place;zip,chunks_exactandwindowsavoid the bounds checks indexing pays.
5. Unsafe
- Justified by FFI, a measured hot path where the compiler cannot prove a
bound, or a structure std lacks; never by a lifetime error. One
operation per block with a
// SAFETY:proof, a# Safetysection on eachpub unsafe fn, a safe wrapper around it (systems, section 5). - The rules that bite: a
&mut Tis unique for its whole life; a reference to uninitialised memory is UB even unread (MaybeUninit<T>and&raw mutinstead);slice::from_raw_partsneeds a non-null aligned pointer even for length 0;transmutebetween types without a defined layout is UB;static mutreferences are denied in edition 2024.
rustup +nightly component add miri
cargo +nightly miri test
MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test
MIRIFLAGS="-Zmiri-many-seeds" cargo +nightly miri test # other schedules and addresses
cargo +nightly careful test # std with its debug checks
RUSTFLAGS="-Zsanitizer=address" cargo +nightly test -Zbuild-std --target x86_64-unknown-linux-gnu
Miri is slow and does not run foreign C code: small inputs, and
#[cfg_attr(miri, ignore)] on the tests it cannot run.
6. Async with tokio
#[tokio::main](a worker per core) for servers,flavor = "current_thread"for CLIs; one runtime, neverblock_oninside it.tokio::spawnneedsSend + 'static. Astd::sync::MutexGuardorRcalive across.awaitmakes the future!Send: drop it in a block first (clippyawait_holding_lock);tokio::sync::Mutexonly when a lock must be held across an await.- Blocking off the workers: CPU work over about 100 microseconds,
std::fs, blocking C calls and compression go tospawn_blocking(or rayon, answering on aoneshot). Neverstd::thread::sleep. - Cancellation drops the future at its pending
.await; the rest never runs.select!branches must be cancel safe:mpsc::Receiver::recv,TcpListener::acceptandCancellationToken::cancelledare;read_exact,read_lineandwrite_allare not. - A timeout on every external call (
tokio::time::timeout); bounded channels (mpsc::channel(1024)),Semaphoreorbuffer_unordered(n)to cap concurrency.JoinSetaborts its tasks on drop; a droppedJoinHandledetaches, it does not cancel.
Structured shutdown with tokio-util (feature rt):
let token = CancellationToken::new();
let tracker = TaskTracker::new();
let worker_token = token.clone();
tracker.spawn(async move {
loop {
tokio::select! {
() = worker_token.cancelled() => break,
job = rx.recv() => match job {
Some(job) => handle(job).await,
None => break, // every sender is gone
},
}
}
});
tracker.close();
shutdown_signal().await; // Ctrl-C or SIGTERM (`systems-os`)
token.cancel();
if tokio::time::timeout(Duration::from_secs(20), tracker.wait()).await.is_err() {
eprintln!("tasks still running after 20s, exiting anyway");
}
7. Shared state and channels
- Channels first:
std::sync::mpsc::sync_channel(n)orcrossbeam-channelbetween threads; tokio'smpsc,oneshot,watch(latest value) andbroadcastbetween tasks. Arc<Mutex<T>>with short critical sections;RwLockonly when reads dominate and the mutex measurably contends;parking_lotfor locks without poisoning;arc-swapfor read-mostly data replaced whole.- Globals:
static CONFIG: OnceLock<Config>set once at startup;static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\d+$").expect("valid regex"));(once_cellandlazy_staticare no longer needed). rayon (par_iter,par_chunks) for data parallelism.
8. Performance habits
Measure first (systems, section 7). What costs nothing:
- No allocation in hot loops: hoist buffers and
clear()them (capacity stays),with_capacity(n),write!into a reusedStringinstead offormat!per item.SmallVec<[T; N]>for collections usually under N;Box<str>orArc<str>for strings that never grow. - std's SipHash resists hash flooding (keep it for keys an attacker
picks);
rustc-hashorfoldhashfor trusted keys in hot maps. memchr,bytes::Bytes,sort_unstable;#[inline]only on small functions called across crates.
[profile.release]
lto = "fat" # or "thin" when link time hurts; measure both
codegen-units = 1
panic = "abort" # only when nothing catches panics (not FFI libraries)
Profile a [profile.profiling] (inherits = "release", debug = "line-tables-only") with samply record or cargo flamegraph; count
allocations with dhat; benchmark with criterion (harness = false,
inputs through std::hint::black_box). -C target-cpu=native only for
binaries run where they are built.
9. Tooling, lints and crates
[lints.rust]
unsafe_op_in_unsafe_fn = "deny"
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(loom)"] }
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
missing_errors_doc = "allow"
undocumented_unsafe_blocks = "warn"
- Pedantic on, the lints the team rejects allowed in one place
(
[workspace.lints],lints.workspace = truein members); a local exception says why:#[expect(clippy::cast_possible_truncation, reason = "len < 2^16, checked above")]. cargo deny check(advisories, licences, bans, duplicates, sources) with a committeddeny.toml;cargo macheteorcargo +nightly udepsfor unused dependencies;cargo nextest runpluscargo test --doc(nextest skips doctests);cargo semver-checksbefore a library release.- A new crate earns its place: recent releases and answered issues,
reverse dependencies, how much
unsafeand why, its own tree (cargo tree -e normal -i <crate>), MSRV, licence. Defaults:serde,postcard,thiserror,anyhow,clap,tracing,tokio,reqwest,rustls,blake3,rayon,crossbeam-channel,parking_lot,bytes,memchr,winnow,proptest,insta,criterion.
10. Features, MSRV, edition
- Features are additive: enabling one never removes an API or changes
behaviour another crate relies on (Cargo unifies them across the graph).
Optional dependencies via
dep:;cargo hack --feature-powerset checktests the combinations. - MSRV in
rust-version = "1.85", checked in CI with that toolchain; resolver 3 (default in edition 2024) prefers dependency versions that support it. Raise it on purpose, in a minor release, and say so. - Edition 2024 for new crates (
cargo fix --edition). What needs thought:unsafe externblocks,#[unsafe(no_mangle)],std::env::set_varisunsafe, return-positionimpl Traitcaptures every lifetime in scope.
11. Cross-compiling and no_std
rustup target add aarch64-unknown-linux-gnu x86_64-unknown-linux-musl
cargo zigbuild --release --target aarch64-unknown-linux-gnu.2.17 # zig links; glibc 2.17 floor
cross build --release --target aarch64-unknown-linux-gnu # container toolchain; cross test too
cargo xwin build --release --target x86_64-pc-windows-msvc # MSVC target from Linux or macOS
- A bare
cargo build --targetfails on C dependencies (-syscrates,cc) that need the target's C toolchain; these tools bring it. musl is static but its allocator is slow under threads: addmimalloc. no_std:#![cfg_attr(not(feature = "std"), no_std)]with a defaultstdfeature,extern crate alloc;forVecandString,core::error::Error(stable since 1.81),#[panic_handler]in binaries.
Check it
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features # or cargo nextest run, then cargo test --doc
cargo +nightly miri test # unsafe changed
Read the warnings, not only the exit code; every new #[allow] or
#[expect] has a reason. Say which of these could not run here.
Avoid
clone() to quiet the borrow checker; &String and &Vec<T> parameters;
unwrap in library code; Box<dyn Error> or String errors from a
library; lifetimes on long-lived structs; self-referential structs with
unsafe; unsafe without SAFETY; static mut; a std mutex guard across
.await; blocking or sleep on the runtime; unbounded channels; external
calls without timeouts; tasks nobody owns; features that remove things;
target-cpu=native in distributed builds; a crate for ten lines of code.
04 同仓库更多 Skills
demo-skill
Project-scope demo skill for tests.
writing
Writing words people read: the specific thing instead of claims, words to drop, sentences, interface copy (buttons, labels, errors, empty states, confirmations), page copy, notifications and emails, alt text and link text, numbers and dates, words that translate well, documentation and voice. Read before writing a page's copy, an interface's text or a document.
ui
Designing or restyling an interface: direction, layout, type, colour, icons, components, states, responsive behaviour. Read before building a page, a screen or a set of components.
ui-themes
Light and dark themes, and colour that holds in both: tokens with a value per theme, and what each kind of content needs to stay visible (icons, SVG drawings and line art, logos, photos, screenshots, charts, code, maps, borders, shadows, focus, form controls), switching without a flash, and checking both. Read before building a second theme or a theme toggle, and before any drawing, logo or chart on a page that has two themes.
ui-structure
Structure before styling: the blueprint written before any interface code (the screen's job, the regions its kind of page needs, their order and why, the layout skeleton in numbers, the components and their states), the anatomy and order of a homepage, a dashboard, a list, a detail page, settings, a form, sign-in, pricing, docs, an article and a checkout, and how to turn the blueprint into code (tokens, layout primitives, components, sections, page). Read first, before building or rebuilding any page or screen.
ui-stack-vue
Building interfaces in Vue 3 and Nuxt: project layout, script setup with typed defineProps, defineEmits and defineModel, attribute fallthrough, slots and scoped slots, template refs, ref, reactive and computed, watch versus watchEffect, composables and VueUse, provide and inject, Pinia, keyed v-for, transitions, Nuxt 4 (app directory, pages, layouts, useFetch and useAsyncData keys, $fetch in handlers, server routes, runtime config, useSeoMeta, NuxtImg, middleware), VeeValidate forms, Reka UI and Nuxt UI, testing, vue-tsc, and the traps. Read when the project uses Vue or Nuxt.