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 版现已稳定! 大的新版本也是一种用于选择可能带来向后兼容性风险的更改的机制。有关如何实现此目标的详细信息以及迁移的详细说明,请参阅版本指南

版本指南里包含了每个更改的详细信息,这里有一份简要总结:

迁移到 2024

指南包含了所有新功能的迁移说明,以及将现有项目迁移到新版本的一般说明。 在许多情况下,cargo fix 可以自动完成必要的更改。你甚至可能会发现,2024 版不需要对你的代码进行任何更改!

请注意,通过 cargo fix 进行的自动修复非常保守,以避免更改代码的语义。在许多情况下,你可能希望保持代码不变并使用 Rust 2024 的新语义;例如,继续使用 expr 宏匹配器,并忽略条件表达式的转换,因为你希望使用新的 2024 版 drop 顺序语义。cargo fix 的结果不应被视为建议,而只是保持行为的保守转换。

async 闭包

Rust 现在支持异步闭包,如 async || {},它在调用时返回 future。这类似于 async fn,它也可以从本地环境中捕获值,就像普通闭包和函数之间的区别一样。标准库 prelude 中还提供了 3 个类似的 trait:AsyncFnAsyncFnMutAsyncFnOnce

在某些情况下,你可以通过普通闭包和异步块来近似实现这一点,例如 || 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,以及当前的参考以获取更多详细信息。

元组的 FromIteratorExtend

早期版本的 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