Implementing a trait for a type that don’t know about each other

I’m pretty sure this has been discussed before, but I couldn’t find an existing RFC.

Let’s say have have a library aa with a trait A that is designed to be implemented by its users, and a library bb with a type B. These two libraries do not “know” about each other. They might be from different authors.

Now, I make project cc where I want to both libraries and have impl A for B. This is impossible, since the language requires trait implementations to be either in the same crate as the trait or in the same crate as the type. (Is that correct?)

The best I can do is go to the author of one of the libraries and ask them to have the impl in their crate with an optional dependency on the other library. This requires a lot more coordination than I feel should be necessary, and becomes tedious if it needs to happen for every pair of unrelated libraries that someone happen to want to use together. Even when the author of all three projects is the same, it’s kinda awkward to have in a general purpose library this unrelated impl.

I don’t really have a proposal here, but is possible to do better than this in current Rust? Or with a change to the language?

1 Like

The restriction you describe is a consequence of what we usually refer to as “the coherence rule.” See e.g. the description on RFC 48

In case it is not obvious, this restriction is to avoid the scenario where both of the library crates dd and ee each independently provide distinct impl aa::A for bb::B, because then if project ff wants to use both of those crates, the impls will conflict.


Also note that the usual workaround for this is to define a trivial wrapper (aka “newtype”) for bb::B within cc (or, in the above description, in each of dd and ee). I.e.:

mod cc {
    pub struct WrapB(pub bb::B);

    impl aa::A for WrapB { ... }
}

With this in place, there is no longer any ambiguity when mixing the crates dd and ee within the project ff: when ff wants the dd impl of the trait, it uses dd::WrapB; when it wants the one from ee, it uses ee::WrapB.

(again, this workaround may not come as any surprise to you, but I want to make sure its documented for other readers).

I seem to recall (but can’t find right now) a proposal for “local trait implementations” so that both dd and ee could have their impl, but neither impl would exist as far as ff is concerned. Would that avoid breaking the coherence rule?

Thanks for the wrapper work around, I hadn’t thought of it. I can see that it works, but unfortunately its ergonomics are pretty low. To me it seems less satisfying than having optional deps to unrelated libraries all over the place.

I had the same idea about making the implementation private to the crate that defines it. I don’t know how much it would complicate other things though.

Alternately if suitable coercion could be defined for the wrapper, it would probably work almost as well.

I believe earlier versions of Rust had explicitly named impls that you had to bring into scope. This probably would have provided the kind of expressiveness that you are asking for here, but it had other downsides.

(Of course, explicitly named and imported impls is certainly not the same as the crate-local impl change that you two are each proposing. But I’m not convinced the ergonomics wouldn’t go bad in some other way due to the impls not being available within the context of ff that is using dd and ee.)

To clarify: crate-local impl could be opted into (with a keyword on the impl maybe?) but wouldn’t be the default.

How would “suitable coercion” be possible?

There are a variety of proposals for trying to provide support for more kinds of coercions. See e.g. https://github.com/rust-lang/rfcs/pull/91

My original idea was simply to replace the current rule “you can only impl Trait for Type if you defined either Trait or Type” with “you can impl Trait for Type always, but it is only exported if you defined and export either Trait or Type”. So default would be public in those cases where it is allowed now.

Suitable coercion would mean implicitly casting base type to a newtype defined around it, when somehow enabled for the newtype, including to call a method that is not defined for the base type, but is defined for the newtype.

I know Scala does something like that. They need it more, because with the underlying Java object model they can't otherwise define new traits (interfaces) for already existing types (classes) at all.

It was just a vague idea; I didn't think about how it would be implemented.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.