How to Use the Token Pattern for API Safety

The Token pattern in Rust enforces API safety by using a unique, non-constructible type as a function argument to prove that a specific precondition is met, preventing misuse at compile time rather than runtime.

The Token pattern in Rust enforces API safety by using a unique, non-constructible type as a function argument to prove that a specific precondition is met, preventing misuse at compile time rather than runtime. By requiring callers to acquire a token from a trusted source before performing an action, you eliminate entire classes of logic errors where functions are called in the wrong state or order.

Here is a practical example of using a token to ensure a file is only written to after it has been explicitly opened, preventing accidental writes to an uninitialized handle:

use std::fs::File;

// A sealed token type that cannot be constructed outside this module
struct WriteToken;

impl WriteToken {
    // Only the trusted source can create this token
    fn new() -> Self {
        WriteToken
    }
}

struct SafeFile {
    inner: Option<File>,
}

impl SafeFile {
    fn new() -> Self {
        SafeFile { inner: None }
    }

    // The "trusted source" that validates state before issuing a token
    fn open(&mut self, path: &str) -> Option<WriteToken> {
        match File::create(path) {
            Ok(f) => {
                self.inner = Some(f);
                Some(WriteToken::new())
            }
            Err(_) => None,
        }
    }

    // The API is now safe: the compiler enforces that a token must exist
    // to call this, proving the file was successfully opened.
    fn write(&mut self, _token: WriteToken, data: &[u8]) -> std::io::Result<()> {
        // We can safely unwrap because the token proves self.inner is Some
        self.inner.as_mut().unwrap().write_all(data)
    }
}

fn main() {
    let mut file = SafeFile::new();
    
    // This compiles: we get a token from the trusted source
    if let Some(token) = file.open("data.txt") {
        let _ = file.write(token, b"Hello, safe world!");
    }

    // This fails to compile: we cannot construct WriteToken manually
    // let fake_token = WriteToken; // Error: private field
    // file.write(fake_token, b"Bad data"); 
}

This pattern is particularly effective for state machines or resource management where the order of operations matters. Instead of returning Result and forcing the caller to check for errors manually (which they might ignore), the API simply refuses to compile if the caller hasn't gone through the correct initialization flow. The token acts as a cryptographic proof of state that the compiler can verify.

You can also apply this to command-line tools or configuration systems. For instance, a CLI builder might require a ConfigToken to be passed to the run() method, ensuring that build_config() was called first. If the user tries to call run() without configuring the app, the code won't compile, shifting the error detection from runtime panics to compile-time type errors. This approach keeps your public API surface clean while guaranteeing internal invariants are never violated.