Summary
Remove the need for explicit associated type definitions when implementing a trait where it can be inferred from a local trait method.
Motivation
When implementing a trait with generics or associated types, you often have to repeat types multiple times. For example:
impl Iterator for Foo {
type Item = usize;
fn next(&mut self) -> Option<usize> { ... }
}
Or a more complex one:
impl<S, R> Service<R> for Timeout<S>
where
S: Service<R>
{
type Response = S::Response;
type Error = E;
type Future = ResponseFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
fn call(&mut self, request: R) -> Self::Future { ... }
}
Having to write out associated type definitions makes implementing a trait feel much heavier than writing an inherent method. This proposal introduces an inference source for these types, local trait methods.
impl Iterator for Foo {
fn next(&mut self) -> Option<usize> { ... } // `Item = usize` is inferred
}
Guide-level explanation
An associated type can only be inferred if a concrete type is used in it's place in a trait method. This means that Iterator::Item
can be inferred from the return type of next
:
impl Iterator for Foo {
fn next(&mut self) -> Option<usize> { ... } // `Item = usize`
}
However, IntoIterator::Item
will not be:
impl IntoIterator for Foo {
type Item = usize; // Still required
fn into_iter(self) -> FooIntoIter { ... } // `Iter = FooIntoIter`
}
Although it could be inferred from <Self::Iter as Iterator>::Item
, this would significantly affect reasoning about the code as you would have to jump to a second trait implementation to find the type.
Drawbacks
Associated type inference removes some context about the trait from implementations. You will not be able to look at a FromStr
implementation and see directly what the Error
type is. Instead, you will have to look at the return type of the from_str
method. A lot of this could be mitigated by having rustdoc retain full associated type definitions.
Alternatives
Type Elision
An alternative approach to removing the need for the type definition completely is using the _
placeholder:
impl Iterator for Foo {
type Item = _;
fn next(&mut self) -> Option<usize> { ... }
}
However, the benefit provided this is much less. You still need the type Item = _
line, yet it doesn't really provide any meaningful information.
Another approach might be to elide the function's parameter/return type:
impl Iterator for Foo {
type Item = usize;
fn next(&mut self) -> Option<_> { ... }
}
But this again doesn't help very much, you can already use the Self::Item
alias instead of writing the type again, and you still need the extra line, type Item = T
.
Inferring Generics
Generic types cannot be inferred in this proposal:
impl From<_> for Foo { // not allowed
fn from(x: usize) -> Foo { ... }
}
The main reason for this is that inferring generics provides little benefit, saving a few characters versus saving an entire line with associated types. Generics also being a direct part of the trait means that scanning code for say, a From<X>
implementation provides immediate context about a type. From<_>
would mean an extra step to look at the parameter of the from
method. With associated types this extra step is already there. Now instead of searching for impl Iterator
and looking at type Item
, you search for impl Iterator
and look at fn next
.
Generic inference is left as a potential future extension in an effort to keep this RFC minimal.
Prior art
I believe Haskell performs similar type inference?