Rust, and what survived the port

rust-tool-base is a sibling to the Go toolkit rather than a port, and building it was the clearest way to find out which decisions were design and which were just Go habits wearing a confident face.

21 posts

Start here

Why a sibling rather than a port, and enough Rust to follow the rest without writing it daily.

  1. rust-tool-base: the same idea, in a language that argues backA sibling to go-tool-base rather than a port, filling the gap between excellent Rust crates and a coherent way to assemble them. First Light
  2. Just enough Rust to follow alongA primer on the six Rust concepts you need to follow the rest: ownership and borrowing, traits, enums and match, Result and the question mark. Pioneering
  3. What survives a port, and what doesn'tPorting a Go framework to Rust separates design from idiom. Which decisions survived the move, which were habits, and how to tell them apart. Pioneering

What the language gives you

The places where Rust makes a design enforceable that Go could only ask for politely.

  1. A builder that won't compile if you forget a fieldGo functional options enforce required fields at runtime, if at all. A Rust typestate builder refuses to compile when you forget one. Pioneering
  2. Errors without an error handlerGo funnels errors through a handler. In Rust, diagnostics carry their own code and help and propagate on their own, so the funnel disappears. Pioneering
  3. A framework that contains no unsafeWhy every shipping crate uses forbid(unsafe_code) rather than deny, which any module inside it can quietly override from the inside. Pioneering
  4. Secrets that scrub themselves from RAMStorage answers where a secret lives, not what happens to it in memory. Wrapping secrets so they redact in Debug and zero on drop. Pioneering

What it takes away

No init, no pre-main, and a few promises that turn out to contradict each other.

  1. Registering commands without life before mainRust has no init or pre-main phase, so self-registering commands need another mechanism. Using a distributed slice to get the same shape. Pioneering
  2. forbid means forbid, until linkme needs a wordforbid(unsafe_code) is absolute, which is a problem when a dependency emits a link_section the lint counts as unsafe. Where the line ends up. Pioneering
  3. Two API decisions that quietly contradict each otherA Rust gotcha: non_exhaustive promises an enum can grow, but returning a fixed-size array bakes today count into the type. Both cannot be true. Pioneering
  4. clap's global flag, except in a passthrough subtreeWhy a clap global flag stops working inside a passthrough subtree: the tokens get captured as trailing args before the flag is ever parsed. Pioneering

Building the thing

Config, flags, Git and the scaffolder, done the Rust way.

  1. Reloading config without a restartHot-reloading configuration: watch the file, re-read it, swap it in atomically and notify observers, without restarting a long-running service. Pioneering
  2. Two kinds of feature flagRuntime feature flags decide which commands are reachable on a given run. Cargo features decide what is compiled in at all. Do not confuse them. Pioneering
  3. A flag is not a settingReviewing a scaffolder turned up a command name that quietly conflated two different things. A flag and a setting are not the same object. Pioneering
  4. Pure-Rust Git, no git binaryDoing local Git from Rust with gix rather than shelling out to the git binary or linking libgit2, and why that avoids a lot of cross-compilation pain. Pioneering
  5. The scaffolder that won't hand you code that doesn't compileA Rust scaffolder with an AI codegen path that drafts a real command, then refuses to hand it over until it compiles and passes lint. Pioneering

Shipping it

Publishing a seventeen-crate workspace, and the policy gate that stands between it and the world.

  1. Same config, two answersPublishing a seventeen-crate Rust workspace to crates.io, and why the same release configuration produced two different answers. Pioneering
  2. Three traps release-plz sets for a Rust workspaceThree traps release-plz sets for a Rust workspace, starting with a default tag template that collides the moment you have more than one crate. Pioneering
  3. Waivers with an expiry dateA vulnerability scanner is a one-day yes or no. Running cargo-deny as a standing policy gate instead, with waivers that expire on a date. Pioneering
  4. Process isolation won't save you from the filesystemA flaky self-update test: separate processes still shared a computed cache path and raced on disk. Process isolation is not filesystem isolation. Pioneering

The argument

One opinion, from someone who ships framework code in both languages.

  1. Everyone wants Rust's safety, nobody wants RustRefereeing the memory-safety retrofit argument from someone who ships framework code in both Rust and Go, and likes them for different reasons. Soapbox

Where to next

Everything, newest first →