sumtype crate

In Rust, returning one of several different types from a function depending on its arguments is awkward: even when the types share the same interface, they remain distinct concrete types, so you cannot return them directly from a single function. The usual workaround is Box<dyn Trait>, but it has drawbacks — it allocates on the heap, it is not a zero-cost abstraction, and the boxed type often has to be 'static.
For example:
// Example with Iterator trait
// Example with Read trait
Each function returns one of two different concrete types that implement the same trait (Iterator or Read). Box<dyn Trait> lets them share a return type, but only at the cost of the drawbacks above.
This crate solves the problem by generating a single anonymous sum type for each context annotated with the #[sumtype] attribute. The sumtype!(expr) macro wraps an expression in that sum type, so the otherwise-distinct types all become one type within the same #[sumtype] context — which is what lets a single function return any of them. The sum type is a plain enum, so it needs no heap allocation, and it remains a zero-cost abstraction: when the branch is known at compile time, the compiler can collapse the enum away and leave only the concrete type.
The same functions written with sumtype:
use sumtype;
// Iterator example
// Read example
Here #[sumtype] generates a sum type that can hold any of the concrete types implementing the specified trait, and sumtype! wraps each expression so they share that type within the function. Because the sum type is a plain enum, it avoids heap allocation, and when the branch is known at compile time the compiler optimizes the abstraction away.
Zero-Cost Abstraction vs Box<dyn Trait>
A key advantage of sumtype over Box<dyn Trait> is its zero-cost abstraction.
Benefits of sumtype:
- No heap allocation - Uses stack-allocated enum variants
- Static dispatch - Direct method calls, no vtable lookup
- Compile-time optimization - When conditions are known statically, the compiler can eliminate the enum entirely
- Zero runtime cost - In the best case, compiles down to the concrete type with no abstraction overhead
When the branch is known at compile time, the sumtype version can be far faster than a boxed trait object, because it compiles down to direct use of the concrete type with no abstraction overhead.
Difference from std::any::Any
std::any::Any can also hold a value that is one of many concrete types, but it solves a different problem. Any is about type erasure and recovery: you store a value as Box<dyn Any> (or &dyn Any) and later try to recover its original type with downcast_ref::<T>(). sumtype is about unifying a fixed set of types behind a shared trait, so that you keep calling that trait's methods directly.
sumtype |
dyn Any |
|
|---|---|---|
| Set of types | closed — known per #[sumtype] context |
open — any 'static type |
| Calling the shared trait | direct; the sum type implements it | not possible — you must downcast to a concrete type first |
| Recovering the concrete type | secondary — via Downcast when 'static |
downcast, which can fail at runtime |
| Allocation | none (stack-allocated enum) | usually Box (heap) |
| Dispatch | static, and often optimized away entirely | runtime TypeId comparison |
'static requirement |
only for Downcast; not for the core type |
required (Any: 'static) |
In short, reach for dyn Any when you genuinely need runtime reflection — to stash values of an unknown type and recover them later. Reach for sumtype when the set of types is known and you simply want them to share a trait, without the cost (or the 'static requirement) of Box<dyn Trait> or dyn Any.
Recovering a concrete type
When you do need to recover the original type — dyn Any's use case — sumtype offers the
Downcast trait, implemented
automatically for every generated sum type. Name the target type(s) in the return bound
(+ Downcast<To>), and it works with the ordinary type-less sumtype!(expr) form:
use ;
+ +
let v = make;
assert_eq!; // the active variant
assert_eq!; // wrong type
// Move the value out, or get the sum type back unchanged on a miss:
assert_eq!;
Because downcasting checks the wrapped type at runtime (via TypeId), both the target and the
wrapped types must be 'static (so it is unavailable for sum types that wrap a borrowed value).
downcast_ref/downcast_mut are allocation-free; the owning downcast boxes the value briefly to
move it out. To is a trait parameter, so it is chosen by a type annotation or a
Downcast::<To>::… call (not a method turbofish), which also lets Downcast<To> be used as a
generic bound: fn f<E: Downcast<u32>>(e: E) { … }. (Exposing the named sum type — a -> sumtype!()
return or a field — also works and makes every target type available at once.)
Using #[sumtype] in other contexts
#[sumtype] is not limited to functions. Applied to an expression block, for instance, it lets you initialize one of several trait-implementing types based on a condition and bind the result to a variable:
# use sumtype::sumtype;
# let some_condition = true;
#[sumtype(sumtype::traits::Iterator)]
let mut iter = {
if some_condition {
sumtype!((0..5)) // Wraps the range iterator
} else {
sumtype!(vec![10, 20, 30].into_iter()) // Wraps the vector iterator
}
};
// Now `iter` can be used as a unified iterator type in the rest of the code
for value in iter {
println!("{}", value);
}
Here #[sumtype] is applied to an expression block: each branch is wrapped with sumtype! and bound to iter, which can then be used uniformly in the rest of the code. Note that this form requires nightly Rust and #![feature(proc_macro_hygiene)] — see the tracking issue for procedural macros and "hygiene 2.0".
#[sumtype] can also be applied when defining a trait and when implementing one. Here is an example of each.
Using #[sumtype] with a trait definition:
# use sumtype::sumtype;
#[sumtype(sumtype::traits::Iterator)]
trait MyTrait {
fn get_iterator(&self, flag: bool) -> impl Iterator<Item = i32> {
if flag {
sumtype!((0..5)) // Wraps the range iterator
} else {
sumtype!(vec![10, 20, 30].into_iter()) // Wraps the vector iterator
}
}
}
Here #[sumtype] is applied to a trait definition — useful when a trait's default method body needs to return a sum type.
Using #[sumtype] on a trait and on an implementation of it for a concrete type:
# use sumtype::sumtype;
#[sumtype(sumtype::traits::Iterator)]
trait MyTrait {
fn get_iterator(&self, flag: bool) -> impl Iterator<Item = i32> {
if flag {
sumtype!((0..5)) // Wraps the range iterator
} else {
sumtype!(vec![10, 20, 30].into_iter()) // Wraps the vector iterator
}
}
}
struct StructA;
#[sumtype(sumtype::traits::Iterator)]
impl MyTrait for StructA {
fn get_iterator(&self, _flag: bool) -> impl Iterator<Item = i32> {
sumtype!((0..5)) // Wraps a range iterator
}
}
Here, StructA implements MyTrait, wrapping a range iterator with sumtype!.
Using #[sumtype] with a module definition:
# use sumtype::sumtype;
#[sumtype(sumtype::traits::Iterator)]
mod my_module {
pub struct MyStruct {
iter: sumtype!(),
}
impl MyStruct {
pub fn new(flag: bool) -> Self {
let iter = if flag {
sumtype!(0..5, std::ops::Range<u32>) // Wraps a range iterator
} else {
sumtype!(vec![10, 20, 30].into_iter(), std::vec::IntoIter<u32>) // Wraps a vector iterator
};
MyStruct { iter }
}
pub fn iterate(self) {
for value in self.iter {
println!("{}", value);
}
}
}
}
Supported Traits
The sumtype crate provides built-in support for several common traits:
sumtype::traits::Iterator- For types implementingstd::iter::Iteratorsumtype::traits::Read- For types implementingstd::io::Readsumtype::traits::Clone- For types implementingstd::clone::Clonesumtype::traits::Copy- For types implementingstd::marker::Copysumtype::traits::Hash- For types implementingstd::hash::Hashsumtype::traits::Debug- For types implementingstd::fmt::Debugsumtype::traits::Display- For types implementingstd::fmt::Displaysumtype::traits::Error- For types implementingstd::error::Error
More Examples
Working with different Read implementations
use sumtype;
Working with cloneable types
use sumtype;
Working with Debug and Display traits
use sumtype;
// Usage
let debug_item = get_debuggable;
println!; // "Debug: 42"
let display_item = get_displayable;
println!; // "Display: Static string"
Working with Error types
use sumtype;
use fmt;
// Define custom error types
;
;
// Usage
let error = get_error;
println!; // "Error: IO Error: Failed to read file"
println!; // Prints debug representation
// Can be used where std::error::Error is expected
handle_error;
Custom Traits
You can make your own traits work with sumtype using the #[sumtrait] attribute, which extends support to any trait that satisfies the sumtrait-safety requirements.
use ;
// Define a marker type (required for sumtrait)
;
// Define your custom trait
// Implement the trait for different types
;
;
// Use sumtype with your custom trait
See the #[sumtrait] documentation for the full sumtrait-safety requirements and more advanced patterns for building custom sumtype-compatible traits.