Inferring the hidden type
When you use impl Trait
in a type alias, the hidden type is inferred based on the code in the enclosing module. We call this enclosing module the defining scope for the impl trait. In our example, the defining scope is the module odd
:
#![allow(unused)] fn main() { mod odd { pub type OddIntegers = impl Iterator<Item = u32>; pub fn odd_integers(mut start: u32, stop: u32) -> OddIntegers { .. } } }
As we type-check the functions within the module odd
(or its submodules), we look at the ways that they use the type OddIntegers
. From this we can derive a set of constraints on what the hidden type has to be. For example, the compiler computes that odd_integers
returns a value StepBy<Range<u32>>
, but the function is declared to return OddIntegers
: therefore we conclude that odd_integers
requires the hidden type to be StepBy<Range<u32>>
.
There can be multiple functions that constrain the type
Of course, modules can have more than one function! It's fine to have multiple functions that constrain the type OddIntegers
, so long as they all ultimately constrain it to be the same thing. It's also possible to constrain OddIntegers
by referencing it in other places. For example, consider the function another_function
here:
#![allow(unused)] fn main() { mod odd { pub type OddIntegers = impl Iterator<Item = u32>; // Requires `OddIntegers` to be `StepBy<Range<u32>>` pub fn odd_integers(mut start: u32, stop: u32) -> OddIntegers {..} // Requires `OddIntegers` to be `StepBy<Range<u32>>` fn another_function() { let x: OddIntegers = (3..5).step_by(2); } } }
another_function
references OddIntegers
as the type of the variable x
-- but the value assigned to x
is also of type StepBy<Range<u32>>
, so everything works.
Each function within the defining scope must specify the same type
These two functions each independently require that OddIntegers
has the same underlying type. What is not ok is to have two functions that require different types:
#![allow(unused)] fn main() { type OddIntegers = impl Iterator<Item = u32>; // ^^^^^^^^^^^^^^^^^^^^^^^^^ :tada:! fn odd_integers(mut start: u32, stop: u32) -> OddIntegers { if (start % 2) == 0 { start += 1; } // Requires `OddIntegers` to be `StepBy<Range<u32>>` (start..stop).step_by(2) } fn another_function() { // Requires `OddIntegers` to be `Filter<...>`. // // ERROR! let x: OddIntegers = (3..5).filter(|x| x % 2 != 0); } }
Each function within the defining scope must specify the complete type
Similarly, it is not ok to have functions that only partially constrain and impl Trait. Consider this example:
#![allow(unused)] fn main() { type SomethingDebug = impl Debug; fn something_debug() -> SomethingDebug { // Constrains SomethingDebug to `Option<_>` // (where `_` represents "some type yet to be inferred") None } }
Here, the something_debug
function constrains SomethingDebug
to be some sort of Option
, but it doesn't say the full type. We only have Option<_>
. That's not good enough. You could change this function to specify what kind of option it is, though, and that would be fine:
#![allow(unused)] fn main() { type SomethingDebug = impl Debug; fn something_debug() -> SomethingDebug { // Constrains SomethingDebug to `Option<()>` None::<()> } }