Advanced Traits
Trait Design for Libraries
Designing public traits for a library requires balancing implementor ergonomics (how easy it is to implement the trait for a new type), caller ergonomics (how easy it is to call methods or use dyn Trait), and semver evolution (what changes can be made in minor releases without breaking downstream code).
Checklist of Library Trait Design Rules
| Design Decision | Recommendation | Reason |
|---|---|---|
Struct vs. impl bounds |
Put trait bounds on impl blocks and methods, not on the struct definition (unless required by Drop or associated types) |
Putting struct Cache<T: Clone> forces every struct mentioning Cache<T> to repeat T: Clone even for operations like .len() |
| Core vs. Extension split | Keep required methods minimal in the base trait (Read, Iterator) and provide rich combinators as defaulted methods or an extension trait |
Implementors only write 1 core method (fn next) and inherit dozens of free methods |
Reference & Box forwarding |
Implement your trait for &T, &mut T, and Box<T> (with T: ?Sized) |
Allows passing &mut impl MyTrait or Box<dyn MyTrait> to generic functions taking impl MyTrait by value |
| Dyn compatibility | Add where Self: Sized to constructors and generic helper methods |
Preserves Box<dyn MyTrait> and &dyn MyTrait compatibility |
| Semver safety | Always provide default bodies when adding new methods to an existing unsealed trait, or seal the trait initially | Adding a required method without a default body breaks every downstream impl |
Minimal Core Method + Rich Defaulted Methods
By designing a trait around a single primitive required method (fn write_byte(&mut self, b: u8)) and implementing higher-level operations (fn write_slice, fn write_u32_be) as default methods—which specific types can still override for bulk performance—implementors get maximum leverage from minimal code:
pub trait ByteWriter {
// Single required method:
fn write_byte(&mut self, byte: u8);
// Defaulted bulk method (can be overridden for `memcpy` speed on contiguous buffers!):
fn write_bytes(&mut self, bytes: &[u8]) {
for &b in bytes {
self.write_byte(b);
}
}
// Defaulted big-endian integer helper:
fn write_u32_be(&mut self, value: u32) {
self.write_bytes(&value.to_be_bytes());
}
}
// Forwarding impl so `&mut W` (including `&mut dyn ByteWriter`) satisfies `ByteWriter`:
impl<W: ByteWriter + ?Sized> ByteWriter for &mut W {
fn write_byte(&mut self, byte: u8) {
(**self).write_byte(byte);
}
fn write_bytes(&mut self, bytes: &[u8]) {
(**self).write_bytes(bytes);
}
}
struct ChecksumWriter {
sum: u64,
count: usize,
}
impl ByteWriter for ChecksumWriter {
// Only needs to implement the single required method `write_byte`:
fn write_byte(&mut self, byte: u8) {
self.sum += u64::from(byte);
self.count += 1;
}
}
impl ByteWriter for Vec<u8> {
fn write_byte(&mut self, byte: u8) {
self.push(byte);
}
// Overrides `write_bytes` for contiguous slice extension:
fn write_bytes(&mut self, bytes: &[u8]) {
self.extend_from_slice(bytes);
}
}
fn encode_header(mut out: impl ByteWriter) {
out.write_bytes(b"MAGIC");
out.write_u32_be(0x0102_0304);
}
fn main() {
let mut buf = Vec::new();
let mut checksum = ChecksumWriter { sum: 0, count: 0 };
encode_header(&mut buf);
encode_header(&mut checksum);
println!("buf: {:?}", buf);
println!("checksum: count = {}, sum = {}", checksum.count, checksum.sum);
}
buf: [77, 65, 71, 73, 67, 1, 2, 3, 4]
checksum: count = 9, sum = 368
Semver-Breaking vs. Non-Breaking Trait Changes
| Change to a Public Trait | Breaking Change? | Mitigation |
|---|---|---|
| Adding a new method without a default body | Major breaking change | Provide a default implementation body or seal the trait |
| Adding a new method with a default body | Minor (can cause method-call ambiguity only if a type already has a colliding method name) | Safe in minor releases |
| Adding a new associated type | Major breaking change (associated type defaults are unstable) | Define a companion subtrait (TraitV2: Trait) instead |
| Tightening bounds on a trait or method | Major breaking change | Relaxing bounds is generally minor; tightening is breaking |