Why doesn't `impl SomeRemoteTrait for <LocalType as OtherRemoteTrait>::Inner` work, even if `Inner` is local?

Hi! I'm not sure if I'm asking in the correct forum, but I ran into a situation which surprised me. (Actually it surprised me twice, because I wasn't expecting it to work even if everything was in the same crate).

While writing a macro to generate some code, I tried a trick to reduce the number of parameters of the macro, and (surprisingly) it seems to work well.

However, if I try to split the code in two crates, I get an error. A minimal example follows:

lib.rs

use sub_crate::{impl_bar_for_inner, Foo};

struct Data;
struct Inner;

impl Foo for Data {
    type Inner = Inner;
}

impl_bar_for_inner!(Data);

sub-crate/lib.rs:

pub trait Foo {
    type Inner: Bar;
}

pub trait Bar {}

#[macro_export]
macro_rules! impl_bar_for_inner {
    ($custom_type:ident) => {
        impl $crate::Bar for <$custom_type as $crate::Foo>::Inner {}
    };
}

When I try to compile this example with two crates, I get the following error:

   Compiling sub-crate v0.1.0 (/project/sub-crate)
   Compiling foreign-trait-local-type-limitation v0.1.0 (/project)
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
  --> src/lib.rs:23:1
   |
23 | impl_bar_for_inner!(Data);
   | ^^^^^^^^^^^^^^^^^^^^^^^^^
   | |
   | impl doesn't use only types from inside the current crate
   | `<Data as Foo>::Inner` is not defined in the current crate
   |
   = note: define and implement a trait or new type instead
   = note: this error originates in the macro `impl_bar_for_inner` (in Nightly builds, run with -Z macro-backtrace
 for more info)

For more information about this error, try `rustc --explain E0117`.
error: could not compile `foreign-trait-local-type-limitation` due to previous error

I understand what the error says, but I'm wondering if this is expected. In this case the types are all local, but there is an indirection to the type using a remote trait. Should this be allowed?

1 Like

Perhaps make orphan rules more permissive of associated types? · Issue #20590 · rust-lang/rust · GitHub (in 2015) says:

  • For now I'm treating associated types just like type parameters in coherence.

Chalk book says:

The Orphan Rules in rustc

Thus, based on the source code, the orphan rules in Rust are as follows:

Given an impl of the form impl<T0…Tn> Trait<P1…Pn> for P0, the impl is allowed if:

  • Trait is local to the current crate
  • Trait is upstream to the current crate and:
    • There is at least one type parameter Pi which, taking fundamental types into account, is local to the current crate
    • Within the type Pi, all type parameters are covered by Pi
      • This only really applies if we allowed fundamental types with multiple type parameters
      • Since we don’t do that yet, we can ignore this for the time being
    • All types Pj such that j < i do not contain T0…Tn at any level of depth (i.e. the types are fully visible — “visible” meaning that the type is a known type and not a type parameter or variable)

So <Data as sub_crate::Foo>::Inner is not fully visible since the associated type is considered to be a type parameter in terms of coherence. Thus your code is not allowed.

Update: You can also consult the Reference, which is newer and shorter.

But the exact coherence behavior with associated types is undocumented. See the reply below.

2 Likes

It works due to the locally defined trait Bar.

1 Like

I'm not sure I understand. The last rule says that "All types Pj such that j < i do not contain T0…Tn at any level of depth", but in my code there are no type parameters for the implementation (i.e., there are no T0...Tn).

Then I think the coherence behavior with associated types is undocumented.

But the code failing turns out to be expected according to the issue.

the orphan check does not look into associated types as it has to run before we are able to do so

And the error msg is reported to enhance.

Update: the passing code is expected too, because the local Bar trait already meets the orphan rule, and then rustc looks into the associated types.

1 Like

Got it! Thank you very much for the help and the references!

Reading the issues, I managed to find a workaround for the macro: I can add a type parameter to the trait that references the local type. Here's the updated sub_crate/lib.rs:

pub trait Foo: Sized {
    type Inner: Bar<Self>;
}

pub trait Bar<T> {}

#[macro_export]
macro_rules! impl_bar_for_inner {
    ($custom_type:ident) => {
        impl $crate::Bar<$custom_type> for <$custom_type as $crate::Foo>::Inner {}
    };
}
2 Likes

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.