How to add trait bound for associate type?

The code as follows:

enum Error {
    // ...
}

trait Test {
    type Err; // ??? Error: From<Self::Err>
    fn test(&self) -> Result<(), Self::Err>;
}

fn run<T: Test>(t: &T) -> Result<(), Error> {
    t.test()?;
    Ok(())
}

fn main() {}

(Playground)

How can I add the trait bound Error: From<Self::Err> for Test::Err to make the code work?

You can place the bound on the use-site rather than the trait itself.

fn run<T: Test>(t: &T) -> Result<(), Error>
where
    Error: From<T::Err>,
{
    t.test()?;
    Ok(())
}

Yes, I konw this. And then you should always place the bound for all the functions f<T: Test>(...) which invoke run. It's infectious.

Here, I want to add the bound to the Test trait, so Error: From<Test::Err> is always satisfied and no need to place the bound everywhere.

But I think currently there is no valid syntax to express it.

You can put a where clause on the trait itself, but that appears to still require propagating the constraints onward. Your best choice is probably to require the trait to return a Result<(), Error> directly.

Alternatively, you can explicitly convert the result instead of using ?’s conversion:

trait Test {
    type Err: Into<Error>;
    fn test(&self) -> Result<(), Self::Err>;
}

fn run<T: Test>(t: &T) -> Result<(), Error>
{
    t.test().map_err(|e| e.into())
}

I think rustc can auto infer it, so we do not need to propagate the bound, and can use ? smoothly.

I would’ve thought so, too, but my tests on the playground disagree.

Yes, may be it works in the future.

Currently, I add a helper trait IntoError in nightly edition to solve the problem:

#![feature(specialization)]
#![allow(incomplete_features)]

enum Error {}

trait IntoError: Into<Error> {}

impl<E: IntoError> From<E> for Error {
    #[inline]
    default fn from(e: E) -> Self {
        e.into()
    }
}

trait Test {
    type Err: IntoError;
    fn test(&self) -> Result<(), Self::Err>;
}

fn run<T: Test>(t: &T) -> Result<(), Error> {
    t.test()?;
    Ok(())
}

The blanket From from IntoError will cause From to be implemented twice.

This is similar in how the following doesn’t work

struct Foo<T: Clone>(T);

fn foo<T>(x: Foo<T>) {
    let Foo(y) = x;
    let z = y.clone();
}

without putting a trait constraint on the parameter T in foo as well.

One could expect this to work without the constraint as a parameter x: Foo<T> can only exist if T: Clone is satisfied. But the way Rust traits on datatypes it is essentially the same way that Haskell used to do it (it’s deprecated and considered a misfeature in Haskell), heck, you cannot even do this:

fn bar<T>(x: Foo<T>) -> T {
    let Foo(y) = x;
    y
}

Or this, for that matter:

enum Error {}

trait Test
where Error: From<Self::Err>
{
    type Err;
    fn test(&self) -> Result<(), Self::Err>;
}

fn run<T: Test>(t: &T) -> Result<(), Error> {
    //t.test()?; ⟵ note: commented out!
    Ok(())
}

fn main() {}

Use specialization feature.

When will the Rust team be able to solve the problem?

I don’t know, I have it on my TODO list (or rather my personal Rust feature wishlist) to eventually start an IRLO discussion about this. I wasn’t aware this problem applies to traits, too, which makes it worse IMO.

To elaborate: They have a different Feature, called GADTs, with a very similar syntax that makes things like this actually work with datatypes, like this

data Foo t = Eq t => Foo t

foo :: Foo t -> Bool
foo x =
    case x of
        Foo y -> y == y

(⟶ try on Compiler Explorer)

Rust code for comparison:

struct Foo<T: Eq>(T);

// does not work, needs <T: Eq>
fn foo<T>(x: Foo<T>) -> bool {
    match x {
        Foo(y) => y == y
    }
}

And for traits (in Haskell called “(type) classes”) the problem never existed at all.

data Error

-- the `--` means this is a comment
-- recreating the `From` trait
class From b a where -- corresponds to Rusts `A: From B`
    from :: b -> a
instance From a a where
    from x = x

-- recreating the question mark operator
-- `Either b a` is like `Result<A,B>`
-- `Left` ≙ `Err`, `Right` ≙ `Ok`
(?) :: From b c => Either b a -> Either c a
(?) (Left err) = Left (from err)
(?) (Right x) = Right x

-----------------------------------------------------------

-- like: `trait Foo where Error: From<Self::Err> {`
-- but actually more like a generalized supertrait feature
class From (Err a) Error => Foo a where
    type Err a
    test :: a -> Either (Err a) ()

run :: Foo a => a -> Either Error ()
run t = do
    (test t ?)
    Right ()

(⟶ try on Compiler Explorer)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.