Rust 新版解读 | 1.85 | Rust 2024 稳定版、async
闭包
Rust 1.85 官方 release doc: Announcing Rust 1.85.0 | Rust Blog
通过 rustup 安装的同学可以使用以下命令升级到 1.85 版本:
$ rustup update stable
Rust 2024
我们很高兴地宣布,Rust 2024 版现已稳定! 大的新版本也是一种用于选择可能带来向后兼容性风险的更改的机制。有关如何实现此目标的详细信息以及迁移的详细说明,请参阅版本指南。
版本指南里包含了每个更改的详细信息,这里有一份简要总结:
- 语言
- RPIT 生命周期捕获规则 — 当
use<..>
不存在时,更改impl Trait
类型对参数的默认捕获。 if let
临时作用域 — 更改if let
表达式的临时作用域。- 尾表达式临时作用域 — 更改块中尾表达式的临时作用域。
- 预留模式匹配的人性化改进 — 禁止某些模式组合以避免混淆并为未来的改进留出空间。
- 不安全的
extern
块 —extern
块现在需要unsafe
关键字。 - 不安全属性 —
export_name
、link_section
和no_mangle
属性现在必须标记为unsafe
。 unsafe_op_in_unsafe_fn
警告 —unsafe_op_in_unsafe_fn
lint 现在默认警告,要求在unsafe
函数中显式使用unsafe {}
块。- 禁止引用
static mut
— 对static mut
项的引用现在会生成默认拒绝的错误。 - Never 类型 fallback 更改 — 更改了 never 类型
!
的强制转换方式,并将never_type_fallback_flowing_into_unsafe
lint 级别更改为“拒绝”。 - 宏片段说明符 —
macro_rules!
宏中的expr
宏片段说明符现在也匹配const
和_
表达式。 - 缺失的宏片段说明符 —
missing_fragment_specifier
lint 现在是一个错误,拒绝没有片段说明符类型的宏元变量。 gen
关键字 — 保留gen
关键字,以便将来添加生成器块。- 保留语法 — 保留
#"foo"#
风格的字符串和##
标记,以便将来更改如何解析受保护的字符串字面量。
- RPIT 生命周期捕获规则 — 当
- 标准库
- prelude 的更改 — 将
Future
和IntoFuture
添加到 prelude 中。 - 为
Box<[T]>
添加IntoIterator
— 更改了迭代器与Box<[T]>
的工作方式。 - 新不安全的函数 —
std::env::set_var
、std::env::remove_var
和std::os::unix::process::CommandExt::before_exec
现在是不安全的函数。
- prelude 的更改 — 将
- Cargo
- Cargo: Rust 版本感知解析器 — 更改默认依赖解析器行为以考虑
rust-version
字段。 - Cargo: 表和键名一致性 — 删除了一些过时的
Cargo.toml
键。 - Cargo: 报错错误使用默认特性开关的继承情况 — 更改了
default-features = false
与继承的工作区依赖项的工作方式。
- Cargo: Rust 版本感知解析器 — 更改默认依赖解析器行为以考虑
- Rustdoc
- Rustdoc 组合测试 — 文档测试现在组合成一个可执行文件,显著提高了性能。
- Rustdoc 嵌套
include!
更改 — 更改了嵌套include!
文件的相对路径行为。
- Rustfmt
- Rustfmt: 样式版本 — 引入了“样式版本”的概念,允许你分开控制格式化版本与 Rust 版本。
- Rustfmt: 格式化修复 — 大量修复了各种情况的格式化问题。
- Rustfmt: 原始标识符排序 — 更改了诸如
r#async
带有r#
标识符的排序方式。 - Rustfmt: 带数字的排序 — 更改了包含整数的标识符的排序方式。
迁移到 2024
指南包含了所有新功能的迁移说明,以及将现有项目迁移到新版本的一般说明。
在许多情况下,cargo fix
可以自动完成必要的更改。你甚至可能会发现,2024 版不需要对你的代码进行任何更改!
请注意,通过 cargo fix
进行的自动修复非常保守,以避免更改代码的语义。在许多情况下,你可能希望保持代码不变并使用 Rust 2024 的新语义;例如,继续使用 expr
宏匹配器,并忽略条件表达式的转换,因为你希望使用新的 2024 版 drop 顺序语义。cargo fix
的结果不应被视为建议,而只是保持行为的保守转换。
async
闭包
Rust 现在支持异步闭包,如 async || {}
,它在调用时返回 future。这类似于 async fn
,它也可以从本地环境中捕获值,就像普通闭包和函数之间的区别一样。标准库 prelude 中还提供了 3 个类似的 trait:AsyncFn
、AsyncFnMut
和 AsyncFnOnce
。
在某些情况下,你可以通过普通闭包和异步块来近似实现这一点,例如 || async {}
。然而,这种内部块返回的 future 无法从闭包捕获中借用,但 async
闭包可以做到这一点:
#![allow(unused)] fn main() { let mut vec: Vec<String> = vec![]; let closure = async || { vec.push(ready(String::from("")).await); }; }
此外,使用 Fn
trait 返回 Future
时,无法正确表达函数签名,但你可以使用 AsyncFn
trait 来编写:
use core::future::Future; async fn f<Fut>(_: impl for<'a> Fn(&'a u8) -> Fut) where Fut: Future<Output = ()>, { todo!() } async fn f2(_: impl for<'a> AsyncFn(&'a u8)) { todo!() } async fn main() { async fn g(_: &u8) { todo!() } f(g).await; //~^ ERROR 类型不匹配 //~| ERROR 一个类型比另一个更通用 f2(g).await; // 没问题! }
因此,async
闭包为这两个问题提供了一流的解决方案!有关更多详细信息,请参阅 RFC 3668 和稳定报告。
从诊断中隐藏 trait 实现
新的 #[diagnostic::do_not_recommend]
属性是给编译器的一个提示,不要将注释的 trait 实现显示为诊断消息的一部分。对于库作者来说,这是一种防止编译器提出可能无益或误导的建议的方式。例如:
pub trait Foo {} pub trait Bar {} impl<T: Foo> Bar for T {} struct MyType; fn main() { let _object: &dyn Bar = &MyType; }
error[E0277]: 未满足 trait 绑定 `MyType: Bar`
--> src/main.rs:9:29
|
9 | let _object: &dyn Bar = &MyType;
| ^^^^ trait `Foo` 未为 `MyType` 实现
|
note: 需要 `MyType` 实现 `Bar`
--> src/main.rs:4:14
|
4 | impl<T: Foo> Bar for T {}
| --- ^^^ ^
| |
| 在此处引入的未满足的 trait 绑定
= note: 需要将 `&MyType` 转换为 `&dyn Bar`
对于某些 API,实现 Foo
并通过该泛型实现间接获得 Bar
可能是有意义的。对于其他 API,可能期望大多数用户直接实现 Bar
,因此 Foo
建议是一个误导。在这种情况下,添加诊断提示将更改错误消息如下:
#![allow(unused)] fn main() { #[diagnostic::do_not_recommend] impl<T: Foo> Bar for T {} }
error[E0277]: 未满足 trait 绑定 `MyType: Bar`
--> src/main.rs:10:29
|
10 | let _object: &dyn Bar = &MyType;
| ^^^^ trait `Bar` 未为 `MyType` 实现
|
= note: 需要将 `&MyType` 转换为 `&dyn Bar`
有关原始动机,请参阅 RFC 2397,以及当前的参考以获取更多详细信息。
元组的 FromIterator
和 Extend
早期版本的 Rust 为 (T, U)
元组对的迭代器实现了便利的 trait,使其行为类似于 Iterator::unzip
,其中 Extend
在 1.56 版中实现,FromIterator
在 1.79 版中实现。这些现在已扩展到更多的元组长度,从单例 (T,)
到 12 个元素长的 (T1, T2, .., T11, T12)
。例如,你现在可以使用 collect()
一次性分发到多个集合中:
use std::collections::{LinkedList, VecDeque}; fn main() { let (squares, cubes, tesseracts): (Vec<_>, VecDeque<_>, LinkedList<_>) = (0i32..10).map(|i| (i * i, i.pow(3), i.pow(4))).collect(); println!("{squares:?}"); println!("{cubes:?}"); println!("{tesseracts:?}"); }
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
[0, 1, 16, 81, 256, 625, 1296, 2401, 4096, 6561]
std::env::home_dir()
的更新
std::env::home_dir()
已被弃用多年,因为如果设置了 HOME
环境变量(这不是 Windows 上的标准配置),它可能会在某些 Windows 配置中给出令人惊讶的结果。我们之前避免更改其行为,因为担心与依赖此非标准配置的代码的兼容性。鉴于该函数已被弃用很长时间,我们现在将其行为更新为错误修复,后续版本将取消对该函数的弃用。
Others
其它更新细节,和稳定的 API 列表,参考原Blog