Occasionally one wants to create effectively inherent methods on foreign types for usage within their crate. When you do this, you have to define both a trait and an impl. All of the contents of the trait are just type signatures for the functions and then the impl contains the same type signatures but with an actual body. For example, say I want to add a method foo()
to Option<MyType>
to automatically create a new MyType
if it's None
and then call the method foo()
on MyType. We have to write that out today as:
trait OptionMyTypeExt {
fn foo(&mut self);
}
impl OptionMyTypeExt for Option<MyType> {
fn foo(&mut self) {
/* body of fn */
}
}
Ideally I'd just like to write impl Option<MyType>
but orphan rules get in the way, so we need the trait to have an item to have in scope for method resolution. But the trait being define separately doesn't actually help us. It duplicates the type signature so if we change it, we need to change it in two places. We never want to write a second implementation.
So, I propose a helper syntax where we just mash the trait and impl together:
trait OptionMyTypeExt impl Option<MyType> {
fn foo(&mut self) {
/* body of foo */
}
}
This example creates the trait item and implementation item just like the original example. But, IMO, it has more benefits than just lowering the amount of duplication. By writing it like this, you give intent to the reader that this trait exists not to be implemented more than once, but just so it can be used. It also gives a single place for doc comments so you don't have to question whether to put them on the trait (where they're honestly more readable today in Rustdoc) away from the impl or next to the impl.
We could also choose to make the trait second class: We could just make it an item that when in scope, provides the methods for the method call operator. So you couldn't refer to it in a new impl or be generic over it. This would also disallow using generics on the trait itself, just the impl.
My only worry is that this would be yet another thing we'd have to teach about Rust and the gain in ergonomics would be marginal compared to that. But on the other hand, having an obvious almost-trivial way of creating inherent methods might open the doors to people using more of them. And if you just do the wrong thing and write the bare impl
, the fix is just adding a keyword and a name. We could update the error index to show that and it'd be a pretty simple tool-assistable fix.
I'm pretty sure I could implement this in the compiler myself, and there are enough technical details that warrant a very detailed RFC, but before I think about that, I'd like to know the opinions of other Rustaceans if this is something I should even think about pursuing.
Before ending this, I'd like to just offer one more example. Itertool's defines a trait Itertool
and has an implementation that is completely empty using default implementations in the trait. Their trait+impl could be combined using this proposal to be:
trait Itertool impl<T: Iterator> T { /*everything currently in the trait */ }