pub trait StackLike<T> {
fn new() -> Self; // minor omission, see notes
fn my_push(&mut self, value: T);
fn my_pop(&mut self) -> Option<T>;
}
This has two implementations
-
/// A stack backed by a vector. pub struct VecStack<T>(Vec<T>);
-
/// A stack that deduplicates consecutive equal elements. pub struct CountedStack<T>(Vec<(usize, T)>);
How to write a data structure that uses both of these in a composable way?
pub struct Foo1 {
bar: VecStack<i32>,
baz: CountedStack<i32>,
}
pub struct Foo2 {
bar: CountedStack<i32>,
baz: CountedStack<i32>,
}
In some languages like Go we could write directly
type Foo3 struct {
bar StackLike[int32]
baz StackLike[int32]
}
We can do this in Rust as follows
pub struct Foo3 {
bar: Box<dyn StackLike<i32>>,
baz: Box<dyn StackLike<i32>>,
}
This last technique is a bit more convoluted but should be more performant that trait objects
pub struct Foo4<S1, S2>
where
S1: StackLike<i32>,
S2: StackLike<i32>,
{
bar: S1,
baz: S2,
}
We can construct this type simply by passing the necessary implementations for StackLike
let mut foo = Foo4::<
VecStack<i32>,
CountedStack<i32>,
>::new();
as we defined the constructor of Foo4
to use the static method defined on the StackTrait
trait
pub fn new() -> Self {
Foo4 {
bar: S1::new(),
baz: S2::new(),
}
}
-
A method on a trait is called static if it is a function that doesn't take
self
as first parameter. The difference is that one can be called directly on the type likeMyStack::new(...)
and is mostly just a function in a special namespace while methods need an instance of that type to be used as inmy_stack.my_push(...)
. -
In Rust there is the concept of object safety for traits, only this kind of traits can be used as
dyn Trait
. For example static methods are not object safe, we can explicitly mark methods we do not want by appendingwhere Self: Sized
as we did int the definition ofStackLike::new
```rs
pub trait StackLike<T: Clone + PartialEq> {
fn new() -> Self
where
Self: Sized;
...
}
```