Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the compiler treat those two equivalent(?) lines differently?

Tags:

rust

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.

like image 213
Pierre-Antoine Avatar asked Sep 13 '19 08:09

Pierre-Antoine


Video Answer


1 Answers

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

like image 74
Grégory OBANOS Avatar answered Oct 07 '22 21:10

Grégory OBANOS