5
votes

From what I understand, when x implements trait Foo, the following two lines should be equivalent.

x.foo();
Foo::foo(&x);

However, I am facing a problem where the compiler accepts the first one, and rejects the second one, with a rather strange error message.

As usual, this example is available on the playground.

Consider the following two related traits.

pub trait Bar<'a> {
    type BarError: Into<MyError>;
    fn bar(&self) -> Result<(), Self::BarError>;
}

pub trait Foo: for<'a> Bar<'a> {
    type FooError: Into<MyError>;
    fn foo(&self) -> Result<(), Self::FooError>
    where
        for<'a> <Self as Bar<'a>>::BarError: Into<<Self as Foo>::FooError>;
}

This example is a bit complex, but I do need the lifetime parameter on Bar, and I can't have it on Foo. As a consequence:

  • I have to resort on Higher-Rank Trait Bounds (HRTB);
  • I can not rely on Bar::BarError in Foo (there are actually an infinite number of types Bar<'_>::BarError), so Foo must have its own FooError;
  • and so I need the complex trait bound in the foo method to convert BarErrors to FooErrors.

Now, let's implement Bar and Foo for a concrete type, e.g. Vec<i32>.

impl<'a> Bar<'a> for Vec<i32> {
    type BarError = Never;
    fn bar(&self) /* ... */
}

impl Foo for Vec<i32> {
    type FooError = Never;
    fn foo(&self) /* ... */
}

Note that Never is an empty enum, indicating that these implementations never fail. In order to comply with the trait definitions, From<Never> is implemented for MyError.

We can now demonstrate the problem: the following compiles like charm.

let x = vec![1, 2, 3];
let _ = x.foo();

But the following des not.

let x = vec![1, 2, 3];
let _ = Foo::foo(&x);

The error messages says:

error[E0271]: type mismatch resolving `<std::vec::Vec<i32> as Foo>::FooError == MyError`
  --> src/main.rs:49:13
   |
49 |     let _ = Foo::foo(&x);
   |             ^^^^^^^^ expected enum `Never`, found struct `MyError`
   |
   = note: expected type `Never`
              found type `MyError`

The compiler seems to believe that I wrote something like this (NB: this is not correct Rust, but just to give the idea).

let _ = Foo::<FooError=MyError>::foo(&x);

And this does not work because x implements Foo<FooError=Never>.

Why does the compiler adds this additional constraint? Is it a bug? If not, is it possible to write it otherwise so it compiles?

NB: you may wonder why I don't just stick to the first version (x.foo(&x)). In my actual situation, foo is actually named retain, which is also the name of a method in Vec. So I must use the second form, to avoid the ambiguity.

NB2: if I remove the HRTB in the declaration of method foo, both lines compile. But then I can not call Bar::bar in any implementation of Foo::foo, which is not an option for me. And changing foo to something like fn foo<'a>(&'a self) -> Result<(), <Self as Bar<'a>>::BarError) is not an option either, unfortunately.

1
This seems to be a known issue. And check this out: "The only workaround I know of is to remove the lifetime parameter". Ouch.edwardw
@edwardw thanks for the pointer. I'm not sure yet this is the exact same problem, though. The error message is not the same anyway. But they may have the same root cause...Pierre-Antoine

1 Answers

6
votes

From what I understand, when x implements trait Foo, the following two lines should be equivalent.

x.foo();
Foo::foo(&x);

This is true for an inherent method (one that is defined on the type of x itself), but not for a trait method. In your case the equivalent is <Vec<i32> as Foo>::foo(&x);.

Here is a playground link