What is Rust's turbofish
Have you ever heard about the “turbofish”? It is that piece of Rust syntax that looks like ::<SomeType>
. In this post I will describe what it does and how to use it.
First of, if you were to write something like this:
fn main() {
let numbers: Vec<i32> = vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
];
let even_numbers = numbers
.into_iter()
.filter(|n| n % 2 == 0)
.collect();
println!("{:?}", even_numbers);
}
The compiler would yell at you with the following message:
$ cargo check
Checking blog-post v0.1.0 (/Users/davidpdrsn/Desktop/blog-post)
error[E0282]: type annotations needed
--> src/main.rs:6:9
|
6 | let even_numbers = numbers
| ^^^^^^^^^^^^
| |
| cannot infer type
| consider giving `even_numbers` a type
error: aborting due to previous error
For more information about this error, try `rustc --explain E0282`.
error: Could not compile `blog-post`.
To learn more, run the command again with --verbose.
What this message says is that it doesn’t know what type you’re trying to “collect” your iterator into. Is it a Vec
, HashMap
, HashSet
, or something else that implements FromIterator
?
This can be fixed in two different ways. Either by declaring the type of even_numbers
when you declare the variable:
let even_numbers: Vec<i32> = ...
Or by using a turbofish:
let even_numbers = numbers
.into_iter()
.filter(|n| n % 2 == 0)
.collect::<Vec<i32>>();
The ::<Vec<i32>>
part is the turbofish and means “collect this iterator into a Vec<i32>
”.
You can actually replace i32
with _
and let the compiler infer it.
let even_numbers = numbers
.into_iter()
.filter(|n| n % 2 == 0)
.collect::<Vec<_>>();
The compiler is able to do that because it knows the iterator yields i32
s.
With this change our final program looks like this:
fn main() {
let numbers: Vec<i32> = vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
];
let even_numbers = numbers
.into_iter()
.filter(|n| n % 2 == 0)
.collect::<Vec<_>>();
println!("{:?}", even_numbers);
}
Now the compiler is happy:
$ cargo check
Checking blog-post v0.1.0 (/Users/davidpdrsn/Desktop/blog-post)
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
When the turbofish can be used
Lets look at another, similar example:
fn main() {
let s = "Hello, World!";
let string = s.into();
}
Compiling this gives us an error similar to what we had before:
$ cargo check
Checking blog-post v0.1.0 (/Users/davidpdrsn/Desktop/blog-post)
error[E0282]: type annotations needed
--> src/main.rs:3:9
|
3 | let string = s.into();
| ^^^^^^
| |
| cannot infer type
| consider giving `string` a type
error: aborting due to previous error
For more information about this error, try `rustc --explain E0282`.
error: Could not compile `blog-post`.
To learn more, run the command again with --verbose.
We might assume that we fix this by using the turbofish again like so:
fn main() {
let s = "Hello, World!";
let string = s.into::<String>();
}
However that gives us a new error:
$ cargo check
Checking blog-post v0.1.0 (/Users/davidpdrsn/Desktop/blog-post)
error[E0107]: wrong number of type arguments: expected 0, found 1
--> src/main.rs:3:27
|
3 | let string = s.into::<String>();
| ^^^^^^ unexpected type argument
error: aborting due to previous error
For more information about this error, try `rustc --explain E0107`.
error: Could not compile `blog-post`.
To learn more, run the command again with --verbose.
When I was still new to Rust this error confused me a lot. Why does ::<>
work on collect
but not on into
? The answer is in the type signatures of those two functions.
collect
looks like this:
fn collect<B>(self) -> B
And into
looks like this:
fn into(self) -> T
Notice that collect
is written as fn collect<B>
and into
is written as just fn into
.
The fact that collect
has a generic type <B>
is what allows you to use ::<>
. Had into
somehow been written as fn into<B>
then you would have been able to write .into::<String>()
, but since it isn’t, you can’t.
If you encountered a function like fn foo<A, B, C>()
then you would be able to call it like foo::<String, i32, f32>()
.
The turbofish can also be applied to other things such as structs with SomeStruct::<String>::some_method()
. This will work if the struct is defined as struct SomeStruct<T> { ... }
.
You can almost think of the things inside (
and )
to be the “value arguments” to a function and the things inside ::<
and >
to be the “type arguments”.
So to fix our code from above we would have to declare the type when declaring the variable:
fn main() {
let s = "Hello, World!";
let string: String = s.into();
}
And again the compiler is now happy:
$ cargo check
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
We can technically also use the fully qualified trait syntax:
fn main() {
let s = "Hello, World!";
let string = <&str as Into<String>>::into(&s);
}
Or optionally
fn main() {
let s = "Hello, World!";
let string = Into::<String>::into(s);
}
I would personally just use give s
a type check declaring the variable in this case.
I recommend you check out the the book if you want to learn more about how generics work in Rust.
By the way after some digging I found this reddit comment to be the origin on the word “turbofish”.