Andrei Matveiakin
Andrei Matveiakin

Reputation: 1818

Automatically implement a trait for a type that already satisfies it

TL;DR. Is it possible to automatically implement a trait for an existing type that already has all methods required by the trait?

Long version. Suppose I want to have a generic function that does stack operations on any stack-like type. So I have a trait

pub trait StackLike<T> {
    fn is_empty(&self) -> bool;
    fn pop(&mut self) -> Option<T>;
    fn push(&mut self, value: T);
}

which I can now use like this: pub fn foo(s: &mut dyn StackLike<i32>) { s.push(42); }. So far so good.

There are several existing types that already satisfy my trait, so implementing it is trivial, e.g.

impl<T> StackLike<T> for Vec<T> {
    fn is_empty(&self) -> bool { self.is_empty() }
    fn pop(&mut self) -> Option<T> { self.pop() }
    fn push(&mut self, value: T) { self.push(value) }
}

impl<T, const N: usize> StackLike<T> for SmallVec<[T; N]> {
    fn is_empty(&self) -> bool { self.is_empty() }
    fn pop(&mut self) -> Option<T> { self.pop() }
    fn push(&mut self, value: T) { self.push(value) }
}

This works, but it's a lot of boilerplate. Absolutely nothing interesting happens here: method names, argument types and return type — everything is same.

Question. Is it possible to avoid spelling out all these tautological statements, i.e. do something like the snippet below?

#[implement-trait-automagically]
impl<T> StackLike<T> for Vec<T>;

#[implement-trait-automagically]
impl<T, const N: usize> StackLike<T> for SmallVec<[T; N]>;

Or may be I can mark my trait somehow so that all types that can satisfy it automatically satisfy it (similar to how concepts work in C++)?

Upvotes: 6

Views: 1085

Answers (1)

cdhowie
cdhowie

Reputation: 168958

Rust doesn't have this kind of "duck typing" but you can put the implementation behind a macro to achieve your stated goals of avoiding all of the boilerplate code while keeping the implementations identical and error-free:

pub trait StackLike<T> {
    fn is_empty(&self) -> bool;
    fn pop(&mut self) -> Option<T>;
    fn push(&mut self, value: T);
}

macro_rules! stacklike_impl {
    () => {
        fn is_empty(&self) -> bool { self.is_empty() }
        fn pop(&mut self) -> Option<T> { self.pop() }
        fn push(&mut self, value: T) { self.push(value) }
    }
}

impl<T> StackLike<T> for Vec<T> { stacklike_impl!(); }
impl<T, const N: usize> StackLike<T> for SmallVec<[T; N]> { stacklike_impl!(); }

Upvotes: 12

Related Questions