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).