While implementing a library, I found that I wanted to implement generic numeric ops for a trait I defined. That would consist of implementing Add, Sub, Mul, Div, etc. for each type that the given trait implements. In theory, I'd like to be able to do something like this:
impl<T: MyTrait> Add for T {...}
However, because of orphan rules, this definition isnt allowed, because Add
isn't a crate local trait, and T
isn't constrained to a crate local type. Instead, I have to wrap T
in a newtype, and implement the trait for that:
impl<T: MyTrait> Add for MyType<T> {...}
Working around this quickly becomes ugly, and involves a lot of indirection, boilerplate, and just overall inconvenience that otherwise shouldn't be necessary; I need to impl Deref
and DerefMut
for the newtype, I need to forward all the trait methods that take ownership of the value manually, and I need to make sure I surround all new instances of the inner type with the newtype when constructing them, among other things. All this extra work just to prevent incoherence seems counterproductive and unnecessary.
To clarify: orphan rules as they currently are exist to prevent incoherence, where multiple crates may create impls between the same trait-type pairs. By requiring that either the implemented trait, or the implementee type are crate local, these rules prevent two implementations on the same trait-type pair ever existing between two disparate crates. As we can see however, that restriction can sometimes prove annoying to work around with regular type and trait usage.
I want to discuss a few ideas I've had as far as alternative solutions to orphan rules, which solve the same problem with less hassle from the developer.
Solution 1: Scoped Trait Impls
This is the most straightforward alternative solution; simply make impls of traits scoped, and require them to be in scope in order to be used. This is the way that rust disambiguates between dissimilar traits with the same name, so why not adopt it to impls of those traits as well?
Pros:
- Completely frees all restrictions on trait implementation
- Allows for the creation of "utility" libraries, which only provide useful impls between existing traits and types that the dev may choose to import
Cons:
- Likely nontrivially increases the length of the module import section, more or less depending on the chosen syntax
- Increases barrier to entry to language use, by making traits require additional work and understanding in order to be used
- Requires a modification of the way the existing language is structured, requiring all code using it be updated to accommodate
Con-solutions:
- The syntax can be adapted to work with existing functionality; by default, import all
impl
s when directly importing a given trait. However, instead, the dev may choose to selectively import specificimpl
s instead of the trait itself, which will leave out other impls defined by said trait. If the dev wants to also create impls for that trait in the same scope, they would need to reference the trait by its fully qualified name.
Solution 2: Allow impl Trait for T
if T
is constrained by at least one crate-local trait
This solution is less rigorous and well considered, as I'm not entirely sure if orphan rules can be violated under it or not. More discussion needs to be done.
Compared to the first solution, this one isn't nearly as dramatic; it solves the specific use case I demonstrated in my example, but it isn't much more freeing otherwise. To be fair, I would consider it a common use case.
Pros:
- Does not require any modification of existing code, can be implemented in-place into existing rust
- Does not significantly increase the complexity of understanding or implementing traits
Cons:
- Covers significantly fewer use cases
- Potentially not rigorous; may allow orphan rules to be violated