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?
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.
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).
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 {}
};
}