Table of Contents
Introduction
Rust's impl Trait
is a little useful.
impl Trait
gives the ability to hide types in functions. It is best in -> return
position, but is also usable in (argument)
position.
Use it when you know what Trait you want something for, but you don't care what it really is.
Examples
Return impl Trait
Here it is:
use std::fmt::Display;
// First case: compiler wants more
fn <T: Display>does_not_work() -> T {"Whoops!"}
// Second case: compiler satisfied
fn actually_works() -> impl Display {"Not whoops!"}
Without impl Trait, it looks like this:
fn old_explicit_way_that_works() -> String {"Duh"}
fn slow_way_that_works() -> Box<dyn Display> {Box::new("Hello")}
Sometimes, the old way requires you to write a big, awful type that has nothing to do with how it is actually used. This solves that.
Argument impl Trait
The following two things are almost identical:
fn visible_type_parameter<T: Trait>(x: T) {}
fn hidden_type_parameter(x: impl T) {}
It is sometimes helpful to specify T using turbofish_aka_explicit_type::<DesiredType>
. This is not currntly possible with impl T
. Don't overthink this one; I favor type parameters. See commentary for a debate.
Relationship to dyn/dynamic types
Runtime Trait objects specified by &'lifetime dyn Trait
or Box<dyn Trait>
can be converted to compile time types. This is a conversion from dynamic to static typing.
trait Trait {}
struct Type;
impl Trait for Type {}
fn dynamic_type(x: &dyn Trait) {}
fn static_explicit_type(x: &Type) {}
fn static_implicit_hidden_type(x: &impl Trait) {}
fn static_implicit_visible_type<T: Trait>(x: &T) {}
fn main() {
let x = Type;
dynamic_type(&x as &Type);
static_explicit_type(&x);
static_implicit_hidden_type(&x);
// static_implicit_hidden_type::<Type>(&x); doesn't work (yet?)
static_implicit_visible_type(&x);
static_implicit_visible_type::<Type>(&x);
}
This only works when &dyn Trait
refers to a single type; i.e. you can only return one type from one function.
Commentary
Argument position controversy
https://github.com/rust-lang/rfcs/pull/2444
I can't summarize this issue easily. Read for yourself, especially the last post, to get a sense.
Future of impl Trait
Official tracking issue:
https://github.com/rust-lang/rust/issues/34511
It's January 2019. Here's what I see:
- Existential types (aka
type x = impl Trait
but in a way that makes sense) -
impl X + Y
for multiple traits. - Various other things to make it more complete.
- Potential that
::<Turbofish>
will appear a la "synthetic" type parameters. https://github.com/rust-lang/rust/issues/44721#issuecomment-330945507
Where's the type theory?
People have tried to explain it this way (existential? universal?), but take a step back to look at rust. impl Trait
is a single step forward in type inference.
Recognize that I'm not using a powerful set of tools for understanding type systems that does exist. Give this article a read:
https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html
Applied example (return position)
Naive attempt:
use std::fmt::Display;
fn main() {
println!("{}", make_value(0));
println!("{}", make_value(1));
}
fn make_value<T: Display>(index: usize) -> T {
match index {
0 => "Hello, World",
1 => "Hello, World (1)",
_ => panic!(),
}
}
Fails because…
error[E0282]: type annotations needed
--> /home/sam/code/trash/impl-return.rs:4:20
|
4 | println!("{}", make_value(0));
| ^^^^^^^^^^ cannot infer type for `T`
error[E0308]: match arms have incompatible types
--> /home/sam/code/trash/impl-return.rs:9:5
|
9 | / match index {
10 | | 0 => "Hello, World",
| | -------------- match arm with an incompatible type
11 | | 1 => "Hello, World (1)",
12 | | _ => panic!(),
13 | | }
| |_____^ expected type parameter, found reference
|
= note: expected type `T`
found type `&'static str`
error: aborting due to 2 previous errors
Some errors occurred: E0282, E0308.
For more information about an error, try `rustc --explain E0282`.
Okay, so I do this:
use std::fmt::Display;
fn main() {
println!("{}", make_value::<&'static str>(0));
println!("{}", make_value::<&'static str>(1));
}
fn make_value<T: Display>(index: usize) -> T {
match index {
0 => "Hello, World",
1 => "okay",
_ => panic!(),
}
}
Another dead end:
error[E0308]: match arms have incompatible types
--> /home/sam/code/trash/impl-return.rs:9:5
|
9 | / match index {
10 | | 0 => "Hello, World",
| | -------------- match arm with an incompatible type
11 | | 1 => "okay",
12 | | _ => panic!(),
13 | | }
| |_____^ expected type parameter, found reference
|
= note: expected type `T`
found type `&'static str`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
Yet this succeeds:
use std::fmt::Display;
fn make_value(index: usize) -> impl Display {
match index {
0 => "Hello, World",
1 => "Hello, World (1)",
_ => panic!(),
}
}
(Generated using org-mode)
Top comments (2)
Hello, thank you for sharing your knowledge. I’m learning rust, and I see that you were able to return string slices (which are references) from the make_value function. Why did it work without having to annotate lifetime parameters? Does “impl Display” puts them implicitly?
I found this error message that explains things well:
github.com/rust-lang/rust/issues/5...
The lifetime is 'static by default, but writing
Display + '_
tells the compiler to infer a shorter-lasting lifetime.