rustmacros

Rust impl trait for type if it has trait


I want to create a macro that impements a trait A for a type if it implements a trait B.

The macro I implement will be given a list of types (String, i32, ...). Then for each argument I want to generate an implementation of A if possible but I am not sure if it does implement B.

trait Double {
    fn double(self) -> (Self, Self)
}

#[cfg(impls(String: Clone))] // <- If impl clone
impl Double for String {
    fn double(self) -> (Self, Self) {
        (self.clone(), self) // But each argument will be a little different.
    }
}

Full Question

I'm trying to write Axum Style Magic Function Param as a macro (MagicParams), it accepts dynamic parameter

define_context!(MyContext {
    value1: i32,
    value2: u32,
    value3: String,
});
context_as_params!(MyContext, 3);

let ctx = MyContext { ... }
fn handler(txt: &String, int: i32) { ... }
handler.call(&ctx)

I want to be able to pass a Context to a function as either a reference or a clone. References are fully implemented, while clones are partially implemented (needing to satisfy Clone).

&String | i32   | UnclonableStruct
ref     | clone | clone (panic) 

Solution

  • Rust already has a feature that lets you implement a trait on a type, conditional on whether that type (or its generics) implement certain traits. There are two different ways to write it:

    impl<T> Double for T where T: Clone {
        fn double(self) -> (Self, Self) {
            (self.clone(), self)
        }
    }
    
    impl<T: Clone> Double for T {
        fn double(self) -> (Self, Self) {
            (self.clone(), self)
        }
    }
    

    I normally prefer the second version. It's shorter, and that can matter a lot with where clauses because they often end up being repeated a lot.

    However, if you're generating the traits using macros rather than blanket implementations (which might be important if all the implementations are slightly different), you can run into a problem: the current version of Rust errors out if you write a where clause that's always false. You'd expect to be able to implement Double on one specific type like this:

    impl Double for String where String: Clone {
        fn double(self) -> (Self, Self) {
            (self.clone(), self)
        }
    }
    

    and that works if the where clause is true, but if it's false you get an error:

    // error[E0277]: the trait bound `&'static mut str: Clone` is not satisfied
    impl Double for &'static mut str where &'static mut str: Clone {
        fn double(self) -> (Self, Self) {
            (self.clone(), self)
        }
    }
    

    In this situation, what you probably want is for the code to not do anything (and not implement the trait), rather than error. This is a change that's planned to be made to Rust in the future, but it isn't available in the current version because the implementation is buggy. In the meantime, it's possible to work around the problem by adding a useless higher-ranked lifetime for<'a>:

    // not an error (and does not actually implement a trait)
    impl Double for &'static mut str where for<'a> &'static mut str: Clone {
        fn double(self) -> (Self, Self) {
            (self.clone(), self)
        }
    }
    

    Because 'a isn't used at all in the condition, saying that the condition is true for all possible values of 'a is just equivalent to saying that the condition is true. Note that the bug that I linked above happens to the for<'a> version too, so you should probably only do this if you know that the trait solver will work correctly with the actual condition you're using.