Rust 团队很高兴宣布新版本的 Rust 1.82.0。Rust 是一门编程语言,它赋能所有人构建可靠且高效的软件。
如果您之前使用 rustup 安装了 Rust,可以通过以下命令获取 1.82.0:
$ rustup update stable
如果您尚未安装 rustup,可以从我们网站上的相应页面获取 rustup,并查看 1.82.0 的详细发行说明。
如果您想通过测试未来版本来帮助我们,可以考虑在本地更新到 beta 通道 (rustup default beta) 或 nightly 通道 (rustup default nightly)。请报告您遇到的任何错误!
1.82.0 stable 版本有什么新特性
cargo info
Cargo 现在拥有一个 info 子命令,用于显示注册表中关于包的信息,这满足了一个长期以来的请求,距离提出正好还差一点就十年了!多年来,已经编写了一些类似的第三方扩展,而这个实现是在合并到 Cargo 本身之前作为 cargo-information 开发的。
例如,以下是运行 cargo info cc 可能看到的内容
cc #build-dependencies
A build-time dependency for Cargo build scripts to assist in invoking the native
C compiler to compile native C code into a static archive to be linked into Rust
code.
version: 1.1.23 (latest 1.1.30)
license: MIT OR Apache-2.0
rust-version: 1.63
documentation: https://docs.rs/cc
homepage: https://github.com/rust-lang/cc-rs
repository: https://github.com/rust-lang/cc-rs
crates.io: https://crates.io/crates/cc/1.1.23
features:
jobserver = []
parallel = [dep:libc, dep:jobserver]
note: to see how you depend on cc, run `cargo tree --invert --package cc@1.1.23`
默认情况下,cargo info 描述本地 Cargo.lock 中的包版本(如果存在)。正如您所见,它也会指示是否有更新的版本,而 cargo info cc@1.1.30 将报告该版本的信息。
Apple 目标平台升级
64 位 ARM 上的 macOS 现在是 Tier 1
Rust 针对 64 位 ARM 上的 macOS(M1 系列或更新的 Apple Silicon CPU)的目标平台 aarch64-apple-darwin 现已升级为 Tier 1 目标平台,这表明我们对其正常工作的保证是最高的。正如平台支持页面所述,Rust 仓库中的每一次更改都必须在所有 Tier 1 目标平台上通过完整测试才能合并。该目标平台在 Rust 1.49 中作为 Tier 2 引入,使其可在 rustup 中使用。这一新的里程碑使得 aarch64-apple-darwin 目标平台与 64 位 ARM Linux 以及 X86 macOS、Linux 和 Windows 目标平台处于同等地位。
Mac Catalyst 目标平台现在是 Tier 2
Mac Catalyst 是苹果的一项技术,允许在 Mac 上原生运行 iOS 应用程序。这对于测试 iOS 特定代码特别有用,因为 cargo test --target=aarch64-apple-ios-macabi --target=x86_64-apple-ios-macabi 大多数情况下都能正常工作(与通常的 iOS 目标平台不同,后者需要在原生设备或模拟器上运行之前使用外部工具进行打包)。
这些目标平台现在是 Tier 2,可以使用 rustup target add aarch64-apple-ios-macabi x86_64-apple-ios-macabi 下载,因此现在是更新您的 CI 流水线以测试您的代码是否也能在类 iOS 环境中运行的绝佳时机。
精确捕获 use<..> 语法
Rust 现在在某些 impl Trait 约束中支持 use<..> 语法,以控制捕获哪些泛型生命周期参数。
Rust 中的返回位置 impl Trait (RPIT) 类型会捕获某些泛型参数。捕获泛型参数允许该参数在隐藏类型中使用。这反过来又会影响借用检查。
在 Rust 2021 及更早版本中,除非在不透明类型中句法上提到了生命周期参数,否则裸函数以及固有实现 (inherent impls) 的函数和方法上的不透明类型不会捕获生命周期参数。例如,这是错误的
//@ edition: 2021
error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds
--> src/main.rs:1:30
|
1 | fn f(x: &()) -> impl Sized { x }
| --- ---------- ^
| | |
| | opaque type defined here
| hidden type `&()` captures the anonymous lifetime defined here
|
help: add a `use<...>` bound to explicitly capture `'_`
|
1 | fn f(x: &()) -> impl Sized + use<'_> { x }
| +++++++++
使用新的 use<..> 语法,我们可以按照错误提示进行修复,写作
+ use<'_>
以前,正确修复这类错误需要定义一个虚拟 trait,通常称为 Captures,并按如下方式使用它
+
这被称为“Captures 技巧”,它有点复杂和微妙。现在不再需要它了。
以前还有一种不那么正确但更方便的修复方法,通常称为“outlives 技巧”。编译器甚至以前建议这样做。该技巧看起来是这样的
+ '_
在这种简单情况下,由于RFC 3498 中解释的微妙原因,该技巧与 + use<'_> 完全等价。然而,在实际情况中,这会过度约束返回的不透明类型的边界,导致问题。例如,考虑这段代码,它受到了 Rust 编译器中一个真实案例的启发
;
+ 'cx
我们不能移除 + 'cx,因为该生命周期在隐藏类型中使用,因此必须被捕获。我们也不能添加 'a: 'cx 的约束,因为这些生命周期实际上不相关,并且通常情况下 'a 不会长于 'cx。然而,如果我们改为写作 + use<'cx, 'a>,这将奏效并具有正确的边界。
我们今天稳定化的内容有一些限制。use<..> 语法目前不能出现在 trait 或 trait impls 中(但请注意,在这些地方,作用域内的生命周期参数默认已经被捕获),并且它必须列出作用域内的所有泛型类型和 const 参数。我们希望随着时间的推移解除这些限制。
请注意,在 Rust 2024 中,上面的例子将“直接工作”,无需 use<..> 语法(或任何技巧)。这是因为在新版本中,不透明类型将自动捕获作用域内的所有生命周期参数。这是一个更好的默认设置,我们已经看到了很多关于这如何简化代码的证据。在 Rust 2024 中,use<..> 语法将作为一种重要的退出该默认设置的方式。
有关 use<..> 语法、捕获以及这如何应用于 Rust 2024 的更多详细信息,请参阅版本指南的“RPIT 生命周期捕获规则”章节。有关总体方向的详细信息,请参阅我们最近的博客文章“Rust 2024 中 impl Trait 的变化”。
创建裸指针的原生语法
不安全代码有时必须处理可能悬空、未对齐或不指向有效数据的指针。一个常见的情况是处理 repr(packed) 结构体。在这种情况下,避免创建引用非常重要,因为那会导致未定义行为。这意味着不能使用通常的 & 和 &mut 运算符,因为它们会创建引用——即使该引用立即被转换为裸指针,也为时已晚,无法避免未定义行为。
多年来,宏 std::ptr::addr_of! 和 std::ptr::addr_of_mut! 一直用于此目的。现在是时候为这项操作提供一个适当的原生语法了:addr_of!(expr) 变为 &raw const expr,而 addr_of_mut!(expr) 变为 &raw mut expr。例如
原生语法更清楚地表明这些运算符的操作数表达式被解释为位置表达式。它还避免了在指代创建指针的行为时使用“address-of”这个术语。指针不仅仅是一个地址,因此 Rust 正逐渐弃用“address-of”等术语,这些术语强化了指针和地址之间的错误等价性。
带有 unsafe extern 的安全项
Rust 代码可以使用外部代码中的函数和静态项。这些外部项的类型签名在 extern 块中提供。从历史上看,extern 块中的所有项都是不安全的,但我们无需在 extern 块本身上写入 unsafe。
然而,如果 extern 块中的签名不正确,那么使用该项将导致未定义行为。这是编写 extern 块的人的错,还是使用该项的人的错?
我们决定,编写 extern 块的人有责任确保其中包含的所有签名都是正确的,因此我们现在允许写入 unsafe extern
unsafe extern
这样做的一个好处是,unsafe extern 块中的项可以标记为安全使用。在上面的例子中,我们可以调用 sqrt 或读取 TAU 而无需使用 unsafe。未标记为 safe 或 unsafe 的项保守地假定为 unsafe。
在未来的版本中,我们将通过 lint 鼓励使用 unsafe extern。从 Rust 2024 开始,将强制要求使用 unsafe extern。
更多详情,请参阅RFC 3484 和版本指南的“Unsafe extern blocks”章节。
Unsafe 属性
一些 Rust 属性,例如no_mangle,可以在没有任何 unsafe 块的情况下导致未定义行为。如果这是常规代码,我们将要求它们放在 unsafe {} 块中,但到目前为止属性还没有类似的语法。为了反映这些属性可能会破坏 Rust 安全保证的事实,它们现在被视为“不安全”,应按如下方式编写
旧形式的属性(不带 unsafe)目前仍然接受,但将来可能会被 lint 警告,并且在 Rust 2024 中将成为硬错误。
这影响以下属性
no_manglelink_sectionexport_name
更多详情,请参阅版本指南的“Unsafe 属性”章节。
在模式匹配中省略空类型
现在可以省略按值匹配空类型(也称为不可居留类型)的模式
use Infallible;
这适用于空类型,例如无变体的 enum Void {},或者具有可见空字段且没有 #[non_exhaustive] 属性的结构体和枚举。它与 never type ! 结合使用时也会特别有用,尽管该类型目前仍不稳定。
在某些情况下,仍然必须编写空模式。由于与未初始化值和不安全代码相关的原因,如果通过引用、指针或联合字段访问空类型,则不允许省略模式
为了避免干扰希望支持多个 Rust 版本的 crate,即使可以移除带有空模式的 match 分支,目前也不会将其报告为“不可达代码”警告。
浮点数 NaN 语义和 const
对浮点数(类型为 f32 和 f64)的操作出了名的微妙。其中一个原因是存在 NaN(“非数字”)值,用于表示例如 0.0 / 0.0 的结果。让 NaN 值微妙的地方在于存在不止一个可能的 NaN 值。一个 NaN 值有一个符号(可以用 f.is_sign_positive() 检查)和一个载荷(可以用 f.to_bits() 提取)。然而,NaN 值的符号和载荷都完全被 == 忽略(它总是返回 false)。尽管在不同硬件架构上标准化浮点运算行为取得了巨大成功,但 NaN 何时为正或负以及其确切载荷的细节在不同架构上有所不同。更复杂的是,Rust 及其 LLVM 后端在保证精确数值结果不变的情况下,会对浮点运算应用优化,但这些优化可能会改变产生的 NaN 值。例如,f * 1.0 可能会被优化为仅 f。然而,如果 f 是 NaN,这可能会改变结果的确切位模式!
通过此版本,Rust 对 NaN 值的行为制定了一套标准规则。这套规则不是完全确定的,这意味着像 (0.0 / 0.0).is_sign_positive() 这样的操作结果可能会因硬件架构、优化级别和周围代码而异。旨在完全可移植的代码应避免使用 to_bits,并应使用 f.signum() == 1.0 代替 f.is_sign_positive()。然而,这些规则经过精心选择,仍然允许在 Rust 代码中实现 NaN boxing 等高级数据表示技术。有关确切规则的更多详细信息,请查看我们的文档。
随着 NaN 值的语义确定,此版本还允许在 const fn 中使用浮点运算。由于上述原因,像 (0.0 / 0.0).is_sign_positive() 这样的操作(在 Rust 1.83 中将是 const-stable 的)在编译时执行与运行时执行可能会产生不同的结果。这不是一个 bug,代码不能依赖于 const fn 总是产生完全相同的结果。
常量作为汇编立即数
const 汇编操作数现在提供了一种将整数用作立即数而无需先将其存储到寄存器中的方法。例如,我们手动实现一个到 write 的系统调用
const WRITE_SYSCALL: c_int = 0x01; // syscall 1 is `write`
const STDOUT_HANDLE: c_int = 0x01; // `stdout` has file handle 1
const MSG: &str = "Hello, world!\n";
let written: usize;
// Signature: `ssize_t write(int fd, const void buf[], size_t count)`
unsafe
assert_eq!;
输出
Hello, world!
在上面,像 LEN = const MSG.len() 这样的语句会用一个立即数填充格式说明符 LEN,该立即数取 MSG.len() 的值。这可以在生成的汇编代码中看到(值为 14)
lea rsi, [rip + .L__unnamed_3]
mov rax, 1 # rax holds the syscall number
mov rdi, 1 # rdi is `fd` (first argument)
mov rdx, 14 # rdx is `count` (third argument)
syscall # invoke the syscall
mov rax, rax # save the return value
更多详情请参阅参考文档。
安全地引用 unsafe static
现在允许使用这段代码
static mut STATIC_MUT: Type = new;
extern "C"
在表达式上下文中,STATIC_MUT 和 EXTERN_STATIC 是位置表达式。以前,编译器的安全检查不知道裸引用运算符实际上不影响操作数的位置,将其视为对指针的可能读取或写入。然而,实际上并没有不安全的情况,因为它只是创建了一个指针。
放宽此限制可能会导致一些问题,如果您拒绝 unused_unsafe lint,一些 unsafe 块现在会被报告为未使用,但它们现在仅在旧版本上有用。如果您希望支持多个 Rust 版本,请使用 #[allow(unused_unsafe)] 注解这些 unsafe 块,如下面的示例 diff 所示
static mut STATIC_MUT: Type = Type::new();
fn main() {
+ #[allow(unused_unsafe)]
let static_mut_ptr = unsafe { std::ptr::addr_of_mut!(STATIC_MUT) };
}
Rust 的未来版本有望将此泛化到在此位置上安全的其他表达式,而不仅仅是静态项。
稳定化的 API
std::thread::Builder::spawn_uncheckedstd::str::CharIndices::offsetstd::option::Option::is_none_or[T]::is_sorted[T]::is_sorted_by[T]::is_sorted_by_keyIterator::is_sortedIterator::is_sorted_byIterator::is_sorted_by_keystd::future::Ready::into_innerstd::iter::repeat_nimpl<T: Clone> DoubleEndedIterator for Take<Repeat<T>>impl<T: Clone> ExactSizeIterator for Take<Repeat<T>>impl<T: Clone> ExactSizeIterator for Take<RepeatWith<T>>impl Default for std::collections::binary_heap::Iterimpl Default for std::collections::btree_map::RangeMutimpl Default for std::collections::btree_map::ValuesMutimpl Default for std::collections::vec_deque::Iterimpl Default for std::collections::vec_deque::IterMutRc<T>::new_uninitRc<MaybeUninit<T>>::assume_initRc<[T]>::new_uninit_sliceRc<[MaybeUninit<T>]>::assume_initArc<T>::new_uninitArc<MaybeUninit<T>>::assume_initArc<[T]>::new_uninit_sliceArc<[MaybeUninit<T>]>::assume_initBox<T>::new_uninitBox<MaybeUninit<T>>::assume_initBox<[T]>::new_uninit_sliceBox<[MaybeUninit<T>]>::assume_initcore::arch::x86_64::_bextri_u64core::arch::x86_64::_bextri_u32core::arch::x86::_mm_broadcastsi128_si256core::arch::x86::_mm256_stream_load_si256core::arch::x86::_tzcnt_u16core::arch::x86::_mm_extracti_si64core::arch::x86::_mm_inserti_si64core::arch::x86::_mm_storeu_si16core::arch::x86::_mm_storeu_si32core::arch::x86::_mm_storeu_si64core::arch::x86::_mm_loadu_si16core::arch::x86::_mm_loadu_si32core::arch::wasm32::u8x16_relaxed_swizzlecore::arch::wasm32::i8x16_relaxed_swizzlecore::arch::wasm32::i32x4_relaxed_trunc_f32x4core::arch::wasm32::u32x4_relaxed_trunc_f32x4core::arch::wasm32::i32x4_relaxed_trunc_f64x2_zerocore::arch::wasm32::u32x4_relaxed_trunc_f64x2_zerocore::arch::wasm32::f32x4_relaxed_maddcore::arch::wasm32::f32x4_relaxed_nmaddcore::arch::wasm32::f64x2_relaxed_maddcore::arch::wasm32::f64x2_relaxed_nmaddcore::arch::wasm32::i8x16_relaxed_laneselectcore::arch::wasm32::u8x16_relaxed_laneselectcore::arch::wasm32::i16x8_relaxed_laneselectcore::arch::wasm32::u16x8_relaxed_laneselectcore::arch::wasm32::i32x4_relaxed_laneselectcore::arch::wasm32::u32x4_relaxed_laneselectcore::arch::wasm32::i64x2_relaxed_laneselectcore::arch::wasm32::u64x2_relaxed_laneselectcore::arch::wasm32::f32x4_relaxed_mincore::arch::wasm32::f32x4_relaxed_maxcore::arch::wasm32::f64x2_relaxed_mincore::arch::wasm32::f64x2_relaxed_maxcore::arch::wasm32::i16x8_relaxed_q15mulrcore::arch::wasm32::u16x8_relaxed_q15mulrcore::arch::wasm32::i16x8_relaxed_dot_i8x16_i7x16core::arch::wasm32::u16x8_relaxed_dot_i8x16_i7x16core::arch::wasm32::i32x4_relaxed_dot_i8x16_i7x16_addcore::arch::wasm32::u32x4_relaxed_dot_i8x16_i7x16_add
这些 API 现在在 const 上下文中是稳定的
std::task::Waker::from_rawstd::task::Context::from_wakerstd::task::Context::waker$integer::from_str_radixstd::num::ParseIntError::kind
其他变化
查看 Rust、Cargo 和 Clippy 中的所有变化。
1.82.0 的贡献者
许多人共同努力创建了 Rust 1.82.0。没有大家,我们不可能做到这一点。谢谢!