Search code examples
rustgeneric-programming

How can I implement a function differently depending on if a generic type implements a trait or not?


I'd like to make the implementation of do_something conditional based on if the generic type T implements Debug or not. Is there any way to do something like this?

struct A(i32);

#[derive(Debug)]
struct B(i32);

struct Foo<T> {
    data: T,
    /* more fields */
}

impl<T> Foo<T> {
    fn do_something(&self) {
        /* ... */
        println!("Success!");
    }

    fn do_something(&self)
    where
        T: Debug,
    {
        /* ... */
        println!("Success on {:?}", self.data);
    }
}

fn main() {
    let foo = Foo {
        data: A(3), /* ... */
    };
    foo.do_something(); // should call first implementation, because A
                        // doesn't implement Debug

    let foo = Foo {
        data: B(2), /* ... */
    };
    foo.do_something(); // should call second implementation, because B
                        // does implement Debug
}

I think one way to do this is to create a trait where we have to define do_something(&Self), but I'm not sure. My code snippet is what I will try first.


Solution

  • Here is a solution based on the nightly feature specialization:

    #![feature(specialization)]
    
    use std::fmt::Debug;
    
    struct A(i32);
    
    #[derive(Debug)]
    struct B(i32);
    
    struct Foo<T> {
        data: T,
        /* more fields */
    }
    
    trait Do {
        fn do_something(&self);
    }
    
    impl<T> Do for Foo<T> {
        default fn do_something(&self) {
            /* ... */
            println!("Success!");
        }
    }
    
    impl<T> Do for Foo<T>
    where
        T: Debug,
    {
        fn do_something(&self) {
            /* ... */
            println!("Success on {:?}", self.data);
        }
    }
    
    fn main() {
        let foo = Foo {
            data: A(3), /* ... */
        };
        foo.do_something(); // should call first implementation, because A
                            // doesn't implement Debug
    
        let foo = Foo {
            data: B(2), /* ... */
        };
        foo.do_something(); // should call second implementation, because B
                            // does implement Debug
    }
    

    The first step is to create a trait which defines do_something(&self). Now, we define two impls of this trait for Foo<T>: a generic "parent" impl which is implemented for all T and a specialized "child" impl which is only implemented for the subset where T implements Debug. The child impl may specialize items from the parent impl. These items we want to specialize need to be marked with the default keyword in the parent impl. In your example, we want to specialize do_something.