0x01 什么是 Turbofish
我们运行如下 Rust Snippet:
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);
}
不出意外,Rust 编译器一定会抛出错误
aggresss@traitx tmp % branch:[main]% cargo check
Checking tmp v0.1.0 (/tmp)
error[E0283]: type annotations needed
--> tmp/src/main.rs:3:9
|
3 | let even_numbers = numbers.into_iter().filter(|n| n % 2 == 0).collect();
| ^^^^^^^^^^^^ ------- type must be known at this point
|
= note: cannot satisfy `_: FromIterator<i32>`
note: required by a bound in `collect`
--> /Users/aggresss/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:2050:19
|
2050 | fn collect<B: FromIterator<Self::Item>>(self) -> B
| ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Iterator::collect`
help: consider giving `even_numbers` an explicit type
|
3 | let even_numbers: Vec<_> = numbers.into_iter().filter(|n| n % 2 == 0).collect();
| ++++++++
For more information about this error, try `rustc --explain E0283`.
error: could not compile `tmp` (bin "tmp") due to 1 previous error
上面的错误可以通过两种方式解决
-
第一种方式在 even_numbers 变量声明时声明类型:
let even_numbers: Vec<_> = numbers.into_iter().filter(|n| n % 2 == 0).collect();
-
第二种方式使用 Turbofish 语法,在泛型函数后面加入
::<>
:let even_numbers = numbers.into_iter().filter(|n| n % 2 == 0).collect::<Vec<_>>();
泛型在编译的过程中要被实例化,大多数情况下,具体类型是基于 Hindley-Milner 理论的类型推断,但是在一些特殊情况下,我们需要显示声明类型来帮助编译器排除歧义,这也是 Turbofish 存在的原因。
在使用泛型的过程中,其他语言也会遇到类似的问题,例如 C++ 中使用 ident<T>
,或者 Golang 中使用 ident[T]
,而 Rust 中确使用 ident::<T>
,多了两个冒号(double colon),第一次看到确实觉得有点多余,通过考古在 reddit 上看到 Anna Harren (u/deadstone) 的解释后理解了这样设计的初衷,即可以降低编译器的语法解析难度。
这是一种从语言设计者角度的 Tradeoff,所以语言的使用者多少会有一些疑问或抱怨,在 Rust 社区中确实也会时常听到一些相关的声音,但是当知道了 Turbofish 语法由来的故事后,我已经开始喜欢 ::<>
这个符号。
0x02 Turbofish 的由来
在 2015 年的时候,Anna Harren 第一次提出使用 ::<>
来辅助编译器进行类型判断,同时给它起了一个很有意思的名字 –– TURBOFISH,很快这个名字也被 Rust 官方所采纳,这个符号确实挺像一条加速的鱼,还有人做了一个页面 https://turbo.fish/。不幸的是在 2021 年 Anna Harren 因患癌症离开了这个世界,Rust 1.55.0 的 Release Note 特别提到了她,并将这个版本作为对 Anna Harren 的纪念。
可能开源社区的魅力就在于对多样性的包容,Rust 社区争吵不断,但更容易诞生有趣的事物,毕竟这个世界有那么多有趣的灵魂。
0x03 Furthermore
有一个彩蛋发现,将 Turbofish 符号 ::<>
倒过来, 即 <>::
,Rust 在 Disambiguating Function Calls 中使用 ::
作为 namespace qualifier,同时使用 <>::
作为路径中存在类型混淆时的显示声明,例如:
fn main() {
let s = "Hello, World!";
let string = <&str as Into<String>>::into(&s);
println!("{}", string);
}
这种方法早期被叫做 UFCS (Universal Function Call Syntax),于此同时,使用 Turbofish 也可以反向解决上面的问题:
fn main() {
let s = "Hello, World!";
let string = Into::<String>::into(s);
println!("{}", string);
}
从优雅对称的角度看,我还是支持 Turbofish 仍然是 Turbofish。
0x04 Reference
- https://www.reddit.com/r/rust/comments/3fimgp/comment/ctozkd0/
- https://github.com/rust-lang/rust/blob/master/tests/ui/parser/bastion-of-the-turbofish.rs
- https://foundation.rust-lang.org/news/member-spotlight-turbofish/
- https://turbo.fish/
- https://github.com/jplatte/turbo.fish
- https://blog.rust-lang.org/2021/09/09/Rust-1.55.0.html#dedication
- https://matematikaadit.github.io/posts/rust-turbofish.html
- https://www.reddit.com/r/rust/comments/v4rir6/turbofish_why/
- https://techblog.tonsser.com/posts/what-is-rusts-turbofish
- https://doc.rust-lang.org/reference/expressions/call-expr.html