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
selfas 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: Sizedas we did int the definition ofStackLike::new
```rs
pub trait StackLike<T: Clone + PartialEq> {
fn new() -> Self
where
Self: Sized;
...
}
```