I recently had a situation where I wanted to use bytemuck::bytes_of to convert a reference to an object to a reference to the underlying bytes, but it requires the type implement the NoUninit trait, and the type came from a crate that doesn't implement the trait even though the type meets the requirements.
Generally, for coherence purposes, the orphan rule is necessary to avoid having multiple implementations of the same trait for the same type, which could lead to ambiguity as to which implementation to use. But, in the case of this trait, there are no items in the implementation (no functions, constants, or associated types), so all implementations must be identical[1]. And semantically, these traits typically represent a property of the underlying type, such that implementing the trait is asserting the property and so it's idempotent to assert the same property multiple times.
I expect we'd probably want a lint to help people avoid accidentally implementing traits on external types when they don't mean to, but I see no reason why Rust couldn't allow something like this:
#[allow(impl_empty_external_trait_for_external_type)]
impl ExternalTrait for ExternalType {}
I think this would complicate negative trait bounds, since you can specify a type and trait and not locally know if the type implements that trait, and also specialization means you might have a concrete type and not know if it implements traits for one specialization or another. But as far as I can think of, nothing presently stable could cause a problem if this change happened.
The orphan rule mechanically prevents multiple implementations, but it also protects a library authorās freedom to change how their type functions. If the type foo::Bar you were using is currently NoUninit-compatible in foo 1.0, well, thatās not normally an exposed property of a type, and so it might not be anymore in foo 1.1. Of course, that might be overly conservativeāfor example, if Barās members are all public and it satisfies all the bytemuck::NoUninit requirements in practiceāso Iām not saying it can never work, just pointing out that the orphan rule is theoretically justified as well as practically.
Auto traits are already an exception to thisāoccasionally a problematic one, if fooās author didnāt realize a client was depending on Bar being Sync in 1.0, for example. Extending this to arbitrary traits would beā¦well, you can see there were previous discussions on it.
Yeah, that is a potential concern, which is why I thought about having a lint you'd have to #[allow] to do the implementation. The error message on writing such an orphaned implementation should probably say something along those lines.
In my particular case that inspired this post, the type came from libc and is guaranteed to not have any uninitialized bytes, but libc doesn't take bytemuck as a dependency to implement its traits (which I think is the right choice for libc), nor does bytemuck take libc as a dependency to implement its traits on libc types (it could, but among other arguments against it, adding a big dependency would likely slow down compile times for dependents). So I'm stuck with a type that can't implement NoUninit for reasons of crate boundaries, but which otherwise could implement the trait, which is exactly when this would be useful.
I've been thinking for a while that there should be some way for crates to say "compile this code only if this other crate exists in the crate graph within this version range", without being an actual dependency ā that would allow that sort of interoperation between crates. (This is better than an optional dependency because if the other crate exists twice in the crate graph due to being imported with two different versions, you would get two copies of the code that requires both crates, in order to support both versions.)