Description
Problem
Right now if you want to write a function that transforms an iterator you have to specify the type explicitly:
(1.)
struct MyTransformer<Iter> { ... }
impl<T, Iter: Iterator<T>> Iterator<T> for MyTransformer<Iter> { ... }
fn my_transform<T, Iter: Iterator<T>>(iter: Iter) -> MyTransformer<Iter> { ... }
If you want to hide the type of the iterator, you can use a trait object:
(2.)
fn my_transform<T, Iter: Iterator<T>>(iter: Iter) -> ~Iterator<T> { ... }
but this has a performance cost: heap allocation and indirection.
One solution is return type inference:
(3.)
fn my_transform<T, Iter: Iterator<T>>(iter: Iter) -> _ { ... }
This is what C++14 adopts. It has several drawbacks:
- It breaks encapsulation. The signature of the function (the interface) now depends on the body of the function (the implementation).
- If you're a client of this function and want to know what you can do with the return type, you either have to rely on external documentation (comments) or look at the implementation.
- In Rust's case, it significantly complicates the type checking algorithm.
Rust's policy of no type inference at the API level is a sound one, in my opinion.
The same problem will recur when we add unboxed closures.
Proposed solution
Allow writing:
(4.)
fn my_transform<T, Iter: Iterator<T>>(iter: Iter) -> Iterator<T> { ... }
This syntax, a trait as the return type of a function, indicates a limited form of return type inference. Clients of the function may only use the return type according to the trait. The return type of the function must infer to a type that implements the trait.
This is sort of like an existential, but not quite, which is why I think thinking of it as return type inference is more accurate. If it were a true existential, the function could have several branches and return a different type, each implementing Iterator
, from each branch. This is OK with trait objects, but clearly not representable in an unboxed form. Similarly, from a caller's perspective, if it were a true existential, two calls to the function could not be assumed to return the same type. But here it's quite reasonable to assume that they do.
In the interest of least surprise, the return type of such a function with an inferred return type should be assumed to be equal only to itself:
trait IsThing { }
struct Thing;
impl IsThing for Thing { }
fn some_thing() -> IsThing { Thing }
fn some_other_thing() -> IsThing { Thing }
let mut my_thing = some_thing();
// we expect this to work
my_thing = some_thing();
// we expect this *not* to work
my_thing = some_other_thing();
// because it would be surprising if the compiler "knew" that
// `some_thing` and `some_other_thing` return the same type
// when this isn't present in their signatures
In other words, when typechecking the rest of the program, for each function with such an inferred return type, the compiler would conjure a new anonymous type which it would pretend is the return type of the function, knowing only that it implements the trait. The true identity of this type (and the impl on it) would be determined when typechecking the function itself. In this way it should be possible to typecheck the body of the function and the rest of the program separately. (Edit: I think we would have to ban using this feature on trait methods though.)
This solution seems superior to the other three (explicit return type, trait object, or unlimited return type inference), especially if you also consider wanting to return a lambda when we have unboxed closures.