Rust Traits
A trait defines behavior that types can share: a set of methods, and optionally associated types and constants. Traits are how Rust does polymorphism. Generic code says "any T that implements Display", trait objects (dyn Trait) let different types live behind one interface at runtime, and standard traits like Clone, Iterator, From, and Send power most of the language's ergonomics.
Traits resemble interfaces in other languages, but with important differences. Implementations are explicit (unlike Go interfaces), you can implement your traits for types you don't own, and generic trait use compiles down to specialized, zero-cost code.
TL;DR
- Define with
trait Name { fn method(&self) -> T; }; implement withimpl Name for Type { ... }. - Traits can provide default method implementations.
- Trait bounds (
T: Display + Clone, orwhereclauses) constrain generics; calls are statically dispatched (monomorphized). impl Traitin argument or return position is shorthand for "some type implementing this trait".dyn Trait(behindBox,&, orArc) gives dynamic dispatch for heterogeneous collections and plugins.#[derive(...)]auto-implements common traits; the orphan rule requires that you own the trait or the type.
Quick Example
Core Concepts
Defining and Implementing
A trait lists method signatures, optionally with default bodies. Implementations must be written explicitly with impl Trait for Type, and they can override defaults. Methods take &self, &mut self, or self like any other method; associated functions without self (like constructors) are allowed too.
Trait Bounds
Bounds constrain generic parameters to types implementing given traits:
Generic functions are monomorphized: the compiler generates a specialized copy per concrete type, so calls are direct and inlinable, with zero runtime cost. The trade-offs are compile time and binary size.
impl Trait
Returning impl Iterator lets you return complex iterator or closure types without naming them. It's still static dispatch: there's exactly one concrete type behind it.
Trait Objects: dyn Trait
When you need values of different types in one collection, or chosen at runtime, use a trait object behind a pointer: Box<dyn Shape>, &dyn Shape, Arc<dyn Handler + Send + Sync>. Calls go through a vtable (dynamic dispatch). A trait must be dyn-compatible (formerly "object safe") to be used this way. Roughly, its methods can't be generic or return Self.
Associated Types
Traits can declare types that each implementation fills in. Iterator is the canonical example:
An associated type fixes one output type per implementation (a Vec<u8> iterator yields u8). Generic trait parameters (trait From<T>) allow many implementations per type instead.
Derive and Standard Traits
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] generates standard implementations. Crates add their own derives (Serialize, Deserialize from serde; Error from thiserror). Key std traits:
- Conversion:
From/Into,TryFrom/TryInto,AsRef,FromStr. - Formatting:
Display(user-facing),Debug(developer-facing). - Operators:
Add,Index,Deref,PartialEq,Ord. - Lifecycle:
Clone,Copy,Drop,Default. - Concurrency markers:
Send(safe to move between threads) andSync(safe to share references between threads), auto-implemented by the compiler.
The Orphan Rule
You can implement a trait for a type only if your crate defines the trait or the type. You can't implement Display for Vec<T>, because both are foreign. This keeps implementations coherent across the ecosystem. The workaround is the newtype pattern: wrap the foreign type in your own struct (struct Tags(Vec<String>)) and implement on that.
Best Practices
Default to Generics, Use dyn When Needed
Start with generics or impl Trait for performance and type precision. Switch to dyn Trait for heterogeneous collections, plugin registries, or to cut compile times and binary size in large codebases.
Keep Traits Focused
Small traits (one concept each) compose better through bounds (T: Read + Seek) than one giant trait. That's the same principle as interface segregation in SOLID.
Implement Standard Traits for Your Types
Derive Debug on almost everything. Implement Display for user-facing output, From for natural conversions (which also makes ? work for errors), and Default where a sensible default exists. Idiomatic trait implementations make your types feel native.
Use Extension Traits to Add Methods
To add methods to foreign types, define your own trait with those methods and implement it for the foreign type. The orphan rule allows this because you own the trait.
Common Mistakes
Unnecessary Boxing
Trying to Make a Non-Dyn-Compatible Trait a Trait Object
A trait with generic methods or methods returning Self (like Clone) can't be used as dyn Trait. Move generic methods into a separate trait, add where Self: Sized to exclude them from the vtable, or redesign around an enum.
Conflicting Blanket Implementations
impl<T: Display> MyTrait for T covers every Display type, so you can't also write a specific impl MyTrait for String. Plan blanket impls carefully; they're powerful but close off other implementations.
FAQ
How are Rust traits different from Go interfaces?
Go interfaces are satisfied implicitly by any type with matching methods. Rust traits must be implemented explicitly with impl Trait for Type. Rust traits also support default methods, associated types, and static dispatch through generics, while Go interfaces are always dynamically dispatched.
When should I use an enum instead of dyn Trait?
When the set of variants is closed and known to you, an enum is simpler, faster, and exhaustively checkable with match. Use dyn Trait when the set is open, meaning other crates or plugins may add implementations.
What does Send + Sync + 'static mean on a bound?
Send: the value can move to another thread. Sync: references to it can be shared across threads. 'static: it holds no borrowed data that might expire. Together they're what thread pools and async runtimes require for spawned tasks. See Rust async.
What's the newtype pattern?
A tuple struct wrapping one field, struct Meters(f64). It creates a distinct type with zero runtime cost, which is useful for implementing foreign traits on foreign types and for preventing unit mix-ups.
Related Topics
- Rust — The language overview
- Rust Ownership & Borrowing —
Copy,Clone, andDropsemantics - Rust Error Handling —
Error,From, and? - Go Interfaces — Implicit, structural interfaces compared
- Go Generics — Interface constraints on type parameters
- Type Systems — Ad-hoc and parametric polymorphism