Search code examples
rust

Rust use impl's generic argument for trait's function generic argument


I have a trait

pub trait FromContext {
    fn from_context<T>(context: Context<T>) -> Result<Self>;
}

and I would like to have an implementation of that trait like this

pub struct State<T>(pub T);

impl<T> FromContext for State<T> {
    fn from_context<T>(context: Context<T>) -> Result<Self> {
        Result::Ok(State(context.state))
    }
}

but I get the error

error[E0403]: the name `T` is already used for a generic parameter in this item's generic parameters

I also tried

impl<T1> FromContext for State<T1> {
    fn from_context<T2>(context: Context<T2>) -> Result<Self> {
        Result::Ok(State(context.state))
    }
}

but then I get

error[E0308]: mismatched types

Is there anyway to tell Rust T1 and T2 should be equal?


Solution

  • pub trait FromContext {
        fn from_context<T>(context: Context<T>) -> Result<Self>;
    }
    

    This trait's contract is that from_context can be called with any possible T whatsoever and this will produce a Result<Self>. If instead you want only one T to work, chosen by the implementation, you have two options.

    First, you could use a generic trait. Just move the generic type parameter from the trait method to the trait itself.

    pub trait FromContext<T> {
        fn from_context(context: Context<T>) -> Result<Self>;
    }
    

    Now you can impl<T> FromContext<T> for State<T> and all the types will line up. The benefit of this approach is that you can implement FromContext<T> multiple times on the same type for different Ts.

    (Sidebar: Given this definition, perhaps you can ditch your trait and instead use TryFrom<Context<T>>?)

    The second approach is to use associated types.

    pub trait FromContext {
        type ContextType;
    
        fn from_context(context: Context<Self::ContextType>) -> Result<Self>;
    }
    

    This limits the number of implementations per type to one, but can be beneficial in other ways. In particular, blanket implementations involving generic traits such as FromContext<T> can get tricky because types can implement this trait multiple times, whereas you don't have this problem with a non-generic FromContext.

    Implementing this trait would look like this:

    impl<T> FromContext for State<T> {
        type ContextType = T;
    
        fn from_context<T>(context: Context<T>) -> Result<Self> {
            Result::Ok(State(context.state))
        }
    }