Practical Robustness: Going beyond Memory Safety in Rust

2026-07-237 min read

Andy Brinkmeyer's central argument is that Rust's value is broader than preventing memory errors. Its type system can encode rules that otherwise live in documentation, runtime checks, and developer discipline. The compiler can then reject invalid states, duplicate resource use, and out-of-order operations before the program runs.

Brinkmeyer presented these ideas at InfoQ Dev Summit Munich 2025. In the biography published with the presentation, InfoQ identifies him as a senior software engineer at arculus, where he and his team had spent two and a half years building a master-control system entirely in Rust for fleets of autonomous mobile robots. He had previously worked on mission systems for autonomous UAVs. The robotics examples in the talk are therefore production-motivated examples, not claims that Rust alone makes autonomous systems safe. InfoQ published the 46-minute presentation and transcript on July 6, 2026.

Presentation Notes

Enums make valid states explicit

Rust enum variants can carry different data, and a match must cover every possible variant unless it uses a catch-all. Brinkmeyer's robot-state example has three variants:

  • Uninitialized, with no associated data
  • Initialized, which contains a position
  • ExecutingJob, which contains both a position and a job

This representation protects the data in both directions. Code cannot read a position from an uninitialized robot because that variant has no position to expose. Code also cannot construct an initialized state without supplying a position. The same mechanism underpins Option<T>: optionality is represented by None or Some(T), so accessing T requires handling the possibility that it is absent.

The practical lesson is not merely "use enums instead of null." The stronger point is to place state-specific data inside the state that makes it valid. That prevents combinations such as an "uninitialized" status alongside a populated position field.

Ownership can prevent duplicate use

Every Rust value has exactly one owner at a time. Passing a value by value transfers ownership unless the type is copied. Brinkmeyer uses job assignment to show why this matters outside memory management:

  1. A queue owns a job.
  2. Popping the job transfers ownership to a local variable.
  3. Assigning it to the first robot transfers ownership again.
  4. Assigning that same value to a second robot fails to compile because the value has already moved.

This models a domain invariant: one job value cannot accidentally be assigned to two robots. It is not a global singleton restriction; a system can have many jobs, but each individual job has one owner at a time.

Destruction can release physical resources

Rust calls a type's Drop implementation when its owner goes out of scope. Brinkmeyer extends this resource-acquisition-is-initialization pattern from memory and file handles to a physical zone through which only one robot may travel.

A registry grants access by issuing a non-copyable, non-clonable ZoneAccess token. A robot must own the token before entering the zone, and ownership prevents two robots from holding the same token. The registry must still enforce that it creates only one live token for a zone. The token's Drop implementation releases the zone in the registry. If the robot disconnects or is removed from application state, its owned token is dropped and cleanup follows without every exit path having to remember an explicit free call.

This does not prove that the external world matches the software model. It does make the application's access-token lifecycle structurally harder to leak.

Borrowing and lifetimes bind access to authority

Rust allows either one mutable reference or any number of immutable references to a value at a given time. Lifetimes ensure a reference cannot outlive the value from which it was borrowed. Brinkmeyer's Mutex<T> example combines those rules:

  • Creating the mutex moves T into it, preventing unguarded access through the old owner.
  • Locking returns a MutexGuard whose lifetime is tied to the mutex.
  • Access to T, including mutable access, is obtained through the guard, so a reference to T cannot outlive that guard.
  • Dropping the guard unlocks the mutex.

Consequently, safe Rust code cannot retain and use a reference obtained from the guard after releasing the corresponding lock. A convention such as "never use this pointer after unlock" becomes a relationship checked by the compiler.

Method signatures can encode protocols

Brinkmeyer uses a simplified version of Serde's serialization API to show how ownership can encode operation order. A general serializer is consumed to produce a struct serializer. Fields can be serialized repeatedly through borrowed access, but end(self) consumes the struct serializer. Any later attempt to serialize another field fails at compile time because the serializer has moved.

The important design technique is visible in the signatures:

  • Borrow self when an operation may be repeated without ending the protocol.
  • Consume self when an operation invalidates the current state.
  • Return a different type when the operation transitions to a new state.

This replaces a runtime "already finalized" error with an API in which using a finalized value is unrepresentable.

Typestate moves selected state transitions to compile time

An enum is appropriate when state must be inspected and selected at runtime. The typestate pattern is useful when the caller should only see operations valid for a state known at compile time.

Brinkmeyer's Robot<S> is generic over marker types such as Uninit, Init, and ExecutingJob. Methods common to every state, such as reading the robot's name, are implemented for any S. State-specific methods are implemented only for matching concrete types:

  • Robot<Uninit>::init(position) consumes the value and returns Robot<Init>.
  • position() exists for initialized states, but not for Robot<Uninit>.
  • Starting a job consumes an initialized robot and returns a robot in the executing state.

He applies the same pattern to a simulation builder. Marker types track whether position and map have been supplied. build() exists only for RobotSimulationBuilder<PositionSet, MapSet>, so a fully configured builder can return the simulation directly rather than report missing required fields at runtime.

Compile-time guarantees have costs and boundaries

The Q&A supplies qualifications that matter:

  • Ownership and borrowing are compile-time concepts; there is no runtime reflection API for discovering a value's owner.
  • Monomorphization generates concrete code for generic specializations and avoids generic dispatch overhead, but compile times are a real cost. Brinkmeyer points to incremental compilation as the normal development-time mitigation.
  • A typestate builder can require many state combinations. Brinkmeyer suggests code-generating the repetitive implementations with Rust macros when the number becomes impractical to maintain manually.
  • Rust enums reserve enough space for their largest variant, subject to layout and niche optimizations. Changing variants does not inherently require reallocating the enum.
  • Rust's checks reduce classes of developer mistakes; Brinkmeyer does not present them as a substitute for the stricter processes and tooling needed for safety-critical software.

Synthesis

The following are deductions from the presentation rather than claims Brinkmeyer states directly.

First, the best candidate invariants are local and structural: a job has one current owner, access lasts exactly as long as a token, and finalization consumes a protocol object. These map cleanly onto ownership and lifetimes. An invariant that depends on a sensor being accurate, a remote service honoring a lease, or two processes sharing current knowledge still needs runtime coordination and failure handling.

Second, there is an API-design trade-off between enums and typestate. Enums preserve one runtime type and make dynamic state inspection straightforward. Typestate gives stronger method-level guidance and compile-time transition checks, but can multiply concrete types and implementations. The right choice depends on whether state is primarily discovered at runtime or advanced through a controlled API.

Third, Drop is strongest when releasing a local capability is sufficient. For external cleanup that can fail, automatic destruction should not be mistaken for proof that a network request succeeded or physical access actually ended. A robust design may still need reconciliation, retries, or explicit completion reporting around the type-safe local lifecycle.

The broader lesson is to review function signatures as part of system design. Taking ownership, borrowing, and returning a new type are not implementation details in Rust; they define who may act, for how long, and what operations remain legal.


Canonical source: Andy Brinkmeyer, "Practical Robustness: Going beyond Memory Safety in Rust", InfoQ, published July 6, 2026. The canonical page includes the video, transcript, and slide download.