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

相关推荐
Source.Liu13 小时前
【A11】近期实现架构:面向 EPC 工作流企业 QQ 化的第一阶段
windows·架构·rust
geovindu1 天前
rust: Composite Pattern
开发语言·后端·设计模式·rust·组合模式
喵了几个咪2 天前
RushWind Admin — 契约驱动:203 条路由零手写的工程化拆解
微服务·rust·开源·admin
Ramble_Naylor2 天前
闭包与迭代器:函数式写法
rust
喵了几个咪2 天前
Go 写业务,Rust 扛底盘:一套可落地的混合架构
微服务·架构·golang·rust·多租户·gowind·rushwind
对象存储与RustFS2 天前
升级 RustFS 二进制不停机:一条一条换,留一条退路
后端·rust·开源
感谢地心引力2 天前
来试试我做的音乐播放器吧
ai·rust·音乐·navidrome·music·tarui
蚂蚁背大象2 天前
RocketMQ-Rust 1.0.0 发布:用 Rust 做消息队列,这次有哪些变化?
rust·开源·rocketmq
喵个咪2 天前
RushWind Admin — 用 Rust 写的企业级中后台,开源了
后端·rust·开源
喵个咪2 天前
RushWind Admin — 契约驱动:203 条路由零手写的工程化拆解
后端·rust·开源