This repo is an unofficial, experimental attempt to explain the design challenges of coherence and the orphan rules in Rust, and provide clear places where the community can aggregate specific kinds of feedback on this complex issue (inspired by and suggested on this internals thread). We do not expect any prior knowledge of type theory or other programming languages; if you know most of what's in The Book, then everything here should make sense (if not, please open an issue).
Discussion on this repo might lead to PRs to improve compiler diagnostics, the development of third-party crates, the drafting of new RFCs, convincing everyone that the status quo is the least of many evils, or merely providing a convenient link the next time someone asks about these problems. Any of those would be considered a success.
In Rust, "trait coherence" (or simply "coherence") is the property that there is at most one implementation of a trait for any given type.
Any programming language that has a feature like traits or interfaces must either:
- Enforce coherence by simply refusing to compile programs that contain conflicting implementations
- Embrace incoherence and give programmers a way to manually choose an implementation when there's a conflict
Rust chooses to enforce coherence.
We could provide some kind of disambiguation syntax like <Type as Trait using impl from Crate>::method()
if we wanted to support incoherence. But there are deeper problems with incoherence than the need for some funky syntax:
-
The incoherence may occur very far away from the crates with conflicting
impl
s, forcing this choice on a user that doesn't and shouldn't even know that any of these traits, types or crates exist, much less what will happen if they ask for CrateA'simpl
instead of CrateB's. -
The oft-cited "hashtable problem": If the trait method is being invoked by some other crate on your behalf, you can't use the explicit disambiguation syntax because the call isn't in your code. The classic concrete example is
std::collections::HashMap
invokingHash
impl
s on your types.- It's been suggested that we could allow something like
prefer CrateA impl of Trait for Type;
in your code to affect the behavior of code in all crates you depend on. That doesn't actually solve the problem, but only pushes it down one level. As soon as two crates write conflictingprefer
statements, you've got a conflict again.
- It's been suggested that we could allow something like
Rust enforces trait coherence through two sets of rules:
-
The "overlap rules" forbid you from writing two
impl
blocks that "overlap", i.e. apply to some of the same types. For example,impl<T: Debug> Trait for T
overlaps withimpl<T: Display> Trait for T
because some types might implement bothDebug
andDisplay
, so you can't write both.- The current plan is to dramatically relax these rules with a feature called "specialization".
-
The "orphan rules", very roughly speaking, forbid you from writing an
impl
where both the trait and the type are defined in a different crate. This is meant to:-
Prevent "dependency hell" problems where multiple crates write conflicting
impl
s, so nobody can ever use both crates in the same program, and the crate authors don't even realize they've created this footgun until someone tries to combine them. -
Allow crates to add
impl
s for their traits and types without it being considered a breaking change. Without the orphan rules, no crate would ever be allowed to add animpl
in a minor or patch version because that would break any program that contained an overlappingimpl
.
-
The precise statement of the orphan rules is rather technical because of generics like impl Trait<Foo, Bar> for Type<Baz, Quux>
(see "Little Orphan Impls"), and the #[fundamental] attribute, and OIBITs/auto traits, and probably something else I'm forgetting about. Today, I believe the most official and up to date precise statement of the orphan rules is RFC #2451, accepted in October 2018 and implemented in January 2019.
Most Rustaceans stick to the simplification that "either the type or trait must be from the same crate" since it's very easy to remember and is accurate in the most typical cases.
In short: there are a lot of impl
s that people want to write, but they currently cannot write because of these rules.
This is where things get interesting, and where we want your input.
In many cases this desire is an "XY problem", and there's actually a much better solution that doesn't involve anyone writing an orphan impl. I'm hoping this repo can help clarify which use cases are XY problems, which are merely theoretical and which are genuine pain points. I suspect that alone will make it obvious what, if anything, Rust should change.
-
In some cases, the
impl
really does need to be provided byType
's crate orTrait
's crate for reasons unrelated to the orphan rules. This includes moststd
traits likeOrd
andEq
, as well asserde
'sSerialize
andDeserialize
. See the core traits issue for details and feedback. -
In many cases, the "newtype pattern" is theoretically the best solution. This means creating a brand new type
NewType
within your crate that is nothing but a wrapper aroundType
, so you can provide whateverimpl
s you want for it. This trivially and elegantly solves "the hashtable problem" because newtypes are completely separate types, so there's simply no ambiguity about what code is supposed to call what impl, no matter how many other crates create similar newtypes or even newtypes of your newtypes.-
There are many use cases where creating such a newtype is tedious or fragile because several methods and traits must be reimplemented for it by simply delegating to the original type. If you have such a use case, please describe it on the tedious newtypes issue.
-
There are proposals for adding a "delegation" feature to Rust, which should help reduce the tedium of newtyping. Please post on the How much would delegation help? issue whether or not delegation would solve your use case.
-
As far as I know, there are no practical use cases where newtyping is impossible, only cases where it's tedious, fragile, etc. If you believe you know of a counterexample, please post on the Are newtypes always an option? issue.
-
-
In some cases, the desired
impl
probably should exist, and client code should not have to provide it via a newtype, but there's no obvious "home" for theimpl
because the trait and type are defined in separate, unrelated crates.-
If the trait's crate is "clearly more fundamental" than the type's crate, or vice versa, then the "less fundamental" crate is probably the best home for that impl. For example, if I write a
linear-algebra
crate withMatrix
andVector
types, I should probably provide impls fornum-traits
likeMulAdd
andPow
. The one problem with this case (that all users oflinear-algebra
now depend onnum-traits
) will be solved by RFC #1787 "Automatic Features". -
Sometimes neither the type's crate or the trait's crate is a good home. For example,
chrono
types should implementdiesel
traits, so that you can easily pass date/time types to your database code, but it's generally agreed that thoseimpl
s don't belong in either crate. As far as I know, Rust has no good answer for this today. Please post on the official orphans issue if you know of any compelling use cases that fit this category or have any thoughts on how we might solve it.
-
-
In some cases, the use case will likely be solved by specialization. Please post on the How much does specialization help? issue if you know of such a use case.
-
In some complex cases, the desired
impl
doesn't "feel" like an orphan impl, although it technically is one by today's rules. See rust-lang/rfcs#1856 for some old examples and discussions.
That's all the high-level cases I'm aware of. If I missed something, please open an issue.
#1 is a timeline of Rust RFCs, issues and blog posts relevant to the orphan rules, with many links
#9 describes how other languages handle these issues
This repository is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.