Rust Turbofish 的由来

0x01 什么是 Turbofish

我们运行如下 Rust Snippet:

rust 复制代码
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,同时使用 <>:: 作为路径中存在类型混淆时的显示声明,例如:

rust 复制代码
fn main() {
    let s = "Hello, World!";
    let string = <&str as Into<String>>::into(&s);
    println!("{}", string);
}

这种方法早期被叫做 UFCS (Universal Function Call Syntax),于此同时,使用 Turbofish 也可以反向解决上面的问题:

rust 复制代码
fn main() {
    let s = "Hello, World!";
    let string = Into::<String>::into(s);
    println!("{}", string);
}

从优雅对称的角度看,我还是支持 Turbofish 仍然是 Turbofish。

0x04 Reference

相关推荐
Rust语言中文社区7 小时前
【Rust日报】 Danube Messaging - 云原生消息平台
开发语言·后端·rust
Rust研习社11 小时前
构建可扩展 Rust 项目:从模块化到 Workspace 工程化实践
rust
好家伙VCC11 小时前
**发散创新:用 Rust实现数据编织(DataWrangling)的高效流式处理架构**在现
java·开发语言·python·架构·rust
本地化文档1 天前
rustdoc-book-l10n
rust·github·gitcode
Tony Bai1 天前
Rust 看了流泪,AI 看了沉默:扒开 Go 泛型最让你抓狂的“残疾”类型推断
开发语言·人工智能·后端·golang·rust
jump_jump1 天前
RTK:给 AI 编码助手瘦身的 Rust 代理
性能优化·rust·claude
小杍随笔1 天前
【Rust Exercism 练习详解:Anagram + Space Age + Sublist(附完整代码与深度解读)】
开发语言·rust·c#
Rust研习社1 天前
Rust 字符串与切片实战
rust
朝阳5811 天前
局域网聊天工具
javascript·rust
朝阳5811 天前
我做了一个局域网传文件的小工具,记录一下
javascript·rust