Blog
News and updates from the Telex project.
-
Rust Deep Dives #1: HashMap's Entry API: Stop Doing Double Lookups
.entry().or_insert() and friends — the clean way to handle "insert if missing, update if present" without double lookups. The most useful HashMap pattern nobody teaches early enough.
Rust Deep Dives #2: Deref Coercion: Why &String Silently Becomes &str
Why &String silently becomes &str, how Deref makes smart pointers transparent, and the invisible conversion chain that runs every time you call a method.
Rust Deep Dives #3: AsRef, Borrow, and ToOwned: The Three Traits Nobody Explains Well
Three traits that all seem to do the same thing — convert between owned and borrowed. Here's what each one actually promises and when your function signature needs which one.
Rust Deep Dives #4: Why Rust Has Six String Types (And When You'll Hit Each One)
&str, String, OsStr, OsString, CStr, CString, Path, PathBuf — they exist because strings aren't as simple as languages pretend. Here's when each one shows up.
Rust Deep Dives #5: Rust's Trait Patterns — When to Be Generic, When to Be Dynamic
When to use impl Trait, when to use dyn Trait, when to use T: Trait — and the higher-level patterns (associated types, extension traits, supertraits, sealed traits) that make trait-based APIs clean.
Rust Deep Dives #6: Serde Beyond #[derive]: The Attributes You'll Actually Use
Derive, rename, skip, flatten, untagged enums, default values, custom serialization — the patterns you actually need when working with JSON, TOML, and any other format serde supports.
Rust Deep Dives #7: Declarative Macros Aren't As Scary As They Look
Declarative macros aren't as scary as they look. The patterns, the fragment types, when to write one instead of a function, and when to stop.
Rust Deep Dives #8: Cargo Features, #[cfg], and Conditional Compilation in Plain English
#[cfg()], Cargo features, optional dependencies, and cfg_attr — the project-level patterns for building Rust code that adapts to different environments and configurations.
-
Rust's Smart Pointer Patterns -- Part 1: The Ownership and Sharing System
A practical guide to Box, Rc, Arc, Weak, Cell, RefCell, Mutex, and RwLock -- when you'd actually reach for each one, how they compose, and the six method pairs that make the system livable.
Rust's Smart Pointer Patterns — Part 2: The Specialists
Cow, OnceCell, OnceLock, LazyLock, Pin, and PhantomData — the specialist wrapper types that solve specific problems the core ownership system doesn't address.
-
40 Rust Patterns That Matter
A field guide to forty patterns that show up constantly in real Rust code. Errors and Options, Ownership and Memory, Iterators, and Composition -- grouped by the kind of problem they solve.
-
Rust's _or Methods (and Five Other Naming Patterns) in Plain English
Six naming conventions -- _or/_or_else, as_/to_/into_, _map, _while, _by/_by_key, copied/cloned -- that cover 80% of the standard library. Once they click, new methods become guessable.
-
Building a Chat Server in Rust #1: Hello, TCP
Accept connections, parse messages, handle errors - three Rust patterns in your first hour. Stage 1 of a 6-part series building a real TCP chat server.
Building a Chat Server in Rust #2: Rooms and Users
Users join rooms, messages broadcast to everyone. Six patterns: RefCell, Rc, Rc<RefCell>, split borrows, index-based design, and Drop/RAII.
Building a Chat Server in Rust #3: Parsing and Performance
A proper wire protocol with zero-copy parsing. Four patterns: lifetime annotations, Cow, custom iterators, and 'static + Clone.
Building a Chat Server in Rust #4: Commands and Plugins
/join, /nick, /kick as a command system. Plugin filters with closures. Builder configuration. Typestate connections.
Building a Chat Server in Rust #5: Going Multi-threaded
Real concurrent connections. Thread per client, Arc<Mutex> for shared state, mpsc channels for message delivery. The server handles multiple users at once.
Building a Chat Server in Rust #6: Going Async
Replace threads with tokio. Async filters with Pin<Box<dyn Future + Send>>. Send/Sync bounds everywhere. The final stage.
-
Rust Patterns That Matter #1: Newtype
UserId(u64) and OrderId(u64) are different types. The compiler catches the mix-up. It compiles to the same code as raw u64. Zero cost.
Rust Patterns That Matter #2: From / Into Conversions
Implement From<T> and get Into<T> for free. This is the pattern behind effortless Rust APIs and seamless error conversion.
Rust Patterns That Matter #3: Error Handling with ? + thiserror + anyhow
thiserror for libraries: structured errors callers can match on. anyhow for applications: convenient errors with context. Know which you're writing.
Rust Patterns That Matter #4: Interior Mutability
You have an immutable reference but need to mutate behind it. The compiler says no. RefCell says yes - at a cost.
Rust Patterns That Matter #5: Shared Ownership
Rust enforces single ownership. But trees, graphs, and UI frameworks need multiple owners. Rc solves this with reference counting.
Rust Patterns That Matter #6: The Combo - Rc<RefCell<T>>
Shared AND mutable? Rc gives you sharing, RefCell gives you mutation. Together they're the workhorse pattern for single-threaded shared mutable state.
Rust Patterns That Matter #7: Split Borrows
A method reads one field and writes another. The compiler blocks it - it sees a borrow of the whole struct. Here's how to help it.
Rust Patterns That Matter #8: Index-Based Design
Self-referential structures and lifetime hell? Store indices into a Vec instead of references. Arenas, ECS, and graphs all use this pattern.
Rust Patterns That Matter #9: Drop and RAII
When a value goes out of scope, Rust runs its Drop implementation. This is RAII - cleanup happens automatically, even when errors occur.
Rust Patterns That Matter #10: Lifetime Annotations
The compiler demands lifetime annotations and elision rules don't cover your case. Here's the mental model: lifetimes describe, they don't control.
Rust Patterns That Matter #11: Cow - Borrow or Own
Should your function take &str or String? With Cow, the answer is "whichever is cheaper at runtime." Borrow if you can, own if you must.
Rust Patterns That Matter #12: Custom Iterators
Implement Iterator for your type and get map, filter, collect, and lazy evaluation for free. The trait that makes Rust's for loops work.
Rust Patterns That Matter #13: 'static + Clone - The Escape Hatch
Everything wants 'static. Your data has a lifetime. The answer: clone it, own it, move on. Profile before you optimise.
Rust Patterns That Matter #14: Enum Dispatch vs Trait Objects
Enums are faster (no vtable), trait objects are extensible. Pick based on whether your set of variants is closed or open.
Rust Patterns That Matter #15: Fn, FnMut, FnOnce
"Expected Fn, found closure that implements FnMut." The three closure traits explained by what they capture and how.
Rust Patterns That Matter #16: Storing and Returning Closures
Closures have anonymous types - you can't name them. Box<dyn Fn()> for storage, impl Fn() for returns. The pragmatic guide.
Rust Patterns That Matter #17: Builder Pattern
Fifteen optional fields and three required ones. Telescoping constructors don't scale. A builder struct with chained methods does.
Rust Patterns That Matter #18: Typestate
Connection: Disconnected -> Connecting -> Connected. Encode the states as types. Invalid transitions don't compile.
Rust Patterns That Matter #19: Arc<Mutex<T>> vs Arc<RwLock<T>>
Rc isn't Send. RefCell isn't Sync. For threads, use Arc for sharing and Mutex (or RwLock) for mutation. Default to Mutex.
Rust Patterns That Matter #20: Channels - Message Passing
"Don't communicate by sharing memory; share memory by communicating." Channels decouple producers and consumers - no locks, no deadlocks.
Rust Patterns That Matter #21: Pin and Boxing Futures
"The trait Unpin is not implemented" - Pin means "won't move in memory." Async needs that. Here's why, and how Box::pin solves it.
Rust Patterns That Matter #22: Send / Sync Bounds in Async
tokio::spawn requires Send. Your future holds a non-Send type across .await. The fix is scoping, restructuring, or spawn_local.
-
Terminals: Why They Evolved the Way They Did
A needs-first history of terminal devices: from physical teletypes to PTYs, signals, SSH, and multiplexers. Each concept motivated by the problem it solved.
-
Designing a TUI Framework in Rust - Part 1
The foundational design decisions behind Telex: State with Rc<RefCell<T>>, View as an enum, keyed hooks, and double-buffered rendering.
Designing a TUI Framework in Rust - Part 2
How Telex evolved: channels for external events, effects, error boundaries, custom widgets, and dirty render skipping.
Telex