I’m about to write a run of posts about building rust-tool-base, and they lean on a handful of Rust ideas that I’d otherwise have to keep stopping to explain. So here they are, up front, in one place. You don’t need to write Rust to follow the series. You need a feel for maybe six concepts, and this is a quick, friendly tour of them. If you already write Rust, skip it with my blessing.
Ownership and borrowing
This is the one everybody mentions, and the one the whole language is built around. Every value in Rust has exactly one owner, and when the owner goes away, the value is cleaned up. No garbage collector deciding when, no manual free. If you want to let another piece of code use a value without handing over ownership, you borrow it: &thing lends it out for reading, &mut thing for writing, and the compiler enforces that you can’t, say, change something while someone else is reading it.
The payoff, and the reason people put up with the up-front fuss, is that an entire family of bug (use-after-free, data races, dangling pointers) becomes a compile error rather than a 3am one. When a post says something “moves” or is “borrowed”, that’s all this is.
Traits are Rust’s interfaces
A trait is a named set of methods a type can promise to provide, exactly like an interface in Go or Java. impl Command for Greet { ... } reads as “the Greet type fulfils the Command contract.”
Two bits of syntax show up a lot. dyn Command means “some value whose concrete type I don’t know, but which implements Command”, decided at runtime. And because the compiler needs a known size, you usually see it wrapped: Box<dyn Command> is “a pointer to some Command, whatever it turns out to be.” Whenever the series talks about a registry of Box<dyn Something>, it just means a list of different types that all satisfy the same trait.
Enums, match, and #[non_exhaustive]
A Rust enum is more than a list of named numbers; it’s a proper “one of these” type, and each variant can carry its own data. You handle one with match, which is like a switch that the compiler forces you to make complete: miss a case and it won’t build.
That completeness is usually a gift, but it’s awkward for a library, because adding a new variant would break everyone’s match. The fix is the attribute #[non_exhaustive]: it tells code outside the library “you must keep a catch-all _ => arm, because I reserve the right to add variants later.” With that in place, growing the enum is a non-breaking change. (One whole post turns on a subtle way to accidentally cancel that promise.)
The type system carries facts, not just shapes
Here’s an idea that surprises people coming from other languages: a Rust type often encodes more than “this is a number” or “this is a list.” The size of a fixed array is part of its type, so [Feature; 11] and [Feature; 12] are genuinely different, incompatible types, not one type holding a different count.
Pushed further, you can make the type track state. A “typestate” builder changes type as you call it, so .build() literally doesn’t exist as a method until every required field has been set, and forgetting one is a compile error rather than a runtime surprise. When a post says the compiler “won’t let you” do something, this is usually how: the mistake was made unrepresentable in the types.
Result and the ? operator
Rust has no exceptions. A function that can fail returns a Result<T, E>: either Ok(value) or Err(problem), and you can’t use the value without acknowledging the error case. Writing that check by hand everywhere would be miserable, so there’s a shorthand: the ? operator. let x = thing()?; means “if this returned an error, return it up to my caller right now; otherwise give me the value.” Errors travel up the call stack as ordinary return values until something handles them, or until they fall out of main.
Crates, the workspace, and features
A crate is Rust’s unit of compilation, roughly “a library or binary.” A workspace is a bundle of crates built together, which is how rust-tool-base is laid out: rtb-app, rtb-cli, rtb-config and so on, each its own crate. And Cargo features are compile-time switches declared in Cargo.toml: turn a feature off and the code it guards, and any dependency it pulled in, is never compiled into your binary at all. Not disabled at runtime; simply absent. That distinction does real work in one of the posts.
That’s the toolkit
Ownership and borrowing, traits and dyn, enums and match and #[non_exhaustive], types that carry facts, Result and ?, and crates with features. Six ideas, and they’re enough to read everything else in this series without tripping over the language itself. Where a post needs a seventh thing, it’ll explain it in passing. Now, on with the actual building.
