Rust团队很高兴地宣布Rust的新版本,即1.51.0。Rust是一种编程语言,它使每个人都能建立可靠和高效的软件。
如果你已经通过rustup安装了以前的Rust版本,获得Rust 1.51.0就很简单了:
rustup update stable
如果你还没有,你可以从我们网站的相应页面获取rustup,并在GitHub上查看1.51.0的详细发布说明。
1.51.0稳定版中的内容
这个版本是近期对Rust语言和Cargo的最大补充之一,稳定了常量泛型的MVP和Cargo的新特性解析器。让我们直接进入主题吧
常量泛型的MVP
在这个版本之前,Rust允许你让你的类型在生命期或类型上被参数化。例如,如果我们想有一个struct ,它在数组的元素类型上是通用的,我们会写如下:
struct FixedArray<T> {
// ^^^ Type generic definition
list: [T; 32]
// ^ Where we're using it.
}
如果我们再使用FixedArray<u8> ,编译器会做出一个单态的FixedArray ,看起来像:
struct FixedArray<u8> {
list: [u8; 32]
}
这是一个强大的功能,它允许你在没有运行时开销的情况下编写可重复使用的代码。然而,直到这个版本,还不可能轻松地对这些类型的值进行通用。这在数组中最为明显,因为数组在其类型定义中包含了其长度([T; N] ),以前你无法对其进行通用。现在,在1.51.0版本中,你可以编写代码,对任何整数、bool 或char 类型的值进行通用处理。(使用struct 或enum 的值仍然是不稳定的)。
这一变化让我们有了自己的数组结构,它的类型和长度都是通用的。让我们看看一个定义的例子,以及如何使用它:
struct Array<T, const LENGTH: usize> {
// ^^^^^^^^^^^^^^^^^^^ Const generic definition.
list: [T; LENGTH]
// ^^^^^^ We use it here.
}
现在,如果我们再使用Array<u8, 32> ,编译器会做出一个Array 的单态版本,看起来像:
struct Array<u8, 32> {
list: [u8; 32]
}
构造泛型为库设计者在创建新的、强大的编译时安全API时增加了一个重要的新工具。如果你想了解更多关于常量泛型的信息,你也可以查看"常量泛型MVP进入测试阶段 "的博文,了解更多关于该功能及其当前限制的信息。我们迫不及待地想看到你创建的新库和API!
array::IntoIter 稳定化
作为稳定常量泛型的一部分,我们也在稳定一个使用它的新API,std::array::IntoIter 。IntoIter ,允许你在任何数组上创建一个按值迭代器。以前没有一个方便的方法来迭代一个数组的自有值,只有对它们的引用:
fn main() {
let array = [1, 2, 3, 4, 5];
// Previously
for item in array.iter().copied() {
println!("{}", item);
}
// Now
for item in std::array::IntoIter::new(array) {
println!("{}", item);
}
}
请注意,这是作为一个单独的方法添加的,而不是在数组上的.into_iter() ,因为这目前引入了一些断裂;目前.into_iter() 指的是通过引用的分片迭代器。我们正在探索在未来使之更符合人体工程学的方法。
Cargo的新特性解析器
依赖关系管理是一个很难的问题,其中最难的部分是当一个依赖关系被两个不同的包所依赖时,要选择使用哪个版本。这不仅包括它的版本号,还包括该包的哪些功能已经启用或未启用。Cargo的默认行为是,当一个包在依赖关系图中被多次引用时,会合并该包的特性。
例如,假设你有一个名为foo 的依赖关系,具有 A 和 B 的特性,它被包bar 和baz 使用,但bar 依赖于foo+A ,baz 依赖于foo+B 。Cargo将合并这两个特性,并将foo 编译为foo+AB 。这样做的好处是,你只需要编译一次foo ,然后它就可以重复用于bar 和baz 。
然而,这也有一个缺点。如果在构建依赖中启用的功能与你正在构建的目标不兼容怎么办?
在生态系统中,一个常见的例子是许多#![no_std] 板块中包含的可选std 功能,它允许板块在std 时提供额外的功能。现在想象一下,你想在你的#![no_std] 二进制文件中使用#![no_std] 版本的foo ,并在构建时在你的build.rs 中使用foo 。如果你的构建时间依赖foo+std ,你的二进制文件现在也依赖foo+std ,这意味着它将不再编译,因为std 对于你的目标平台是不可用的。
这是 cargo 中长期存在的一个问题,在这个版本中,你的Cargo.toml 中有一个新的resolver 选项,你可以设置resolver="2" 来告诉 cargo 尝试一种新的方法来解决特性。你可以查看RFC 2957对该行为的详细描述,可以总结为以下几点:
- 开发依赖- 当一个软件包作为普通依赖和开发依赖共享时,只有在当前构建包括开发依赖的情况下才会启用开发依赖的特性。
- Host Dependencies- 当一个软件包作为普通依赖关系和构建依赖关系或proc-macro共享时,普通依赖关系的特性将保持与构建依赖关系或proc-macro无关。
- 目标依赖--当一个包在构建图中出现多次,并且其中一个实例是目标特定依赖,那么目标特定依赖的特性只有在目标当前正在构建时才会被启用。
虽然这可能会导致一些板块编译不止一次,但在使用货物功能时,这应该会提供一个更直观的开发体验。如果你想了解更多,你也可以阅读Cargo Book中的"特性解析器 "一节,了解更多信息。我们要感谢cargo团队和所有参与其中的人,感谢他们在设计和实现新的解析器方面所做的辛勤工作!
[package]
resolver = "2"
# Or if you're using a workspace
[workspace]
resolver = "2"
分割调试信息
虽然不经常在版本中强调,但Rust团队一直在努力改善Rust的编译时间,而这个版本标志着Rust在macOS上长期以来的最大改进之一。调试信息将二进制代码映射回你的源代码,这样程序就可以给你提供更多关于运行时出问题的信息。在macOS中,调试信息以前是用一个叫dsymutil 的工具收集到一个单一的.dSYM 文件夹中,这可能需要一些时间,并占用相当多的磁盘空间。
将所有的调试信息收集到这个目录中有助于在运行时找到它,特别是在二进制文件被移动的情况下。然而,它确实有一个缺点,那就是即使你对你的程序做了一个小小的改动,dsymutil ,也需要在整个最终的二进制文件上运行,以产生最终的.dSYM 。这有时会增加很多构建时间,特别是对于大型项目,因为所有的依赖关系总是被重新收集,但这一直是一个必要的步骤,因为没有它,Rust的标准库不知道如何加载macOS上的调试信息。
最近,Rust的回溯转而使用不同的后端,支持加载调试信息,而不需要运行dsymutil ,我们已经稳定地支持跳过dsymutil 。这可以大大加快包含debuginfo的构建速度,并大大减少磁盘空间的使用量。我们还没有进行广泛的基准测试,但已经看到了很多报告,说人们在macOS上使用这种行为时,构建速度快了很多。
你可以在运行rustc 时设置-Csplit-debuginfo=unpacked 标志来启用这个新行为,或者在 Cargo 中设置 split-debuginfo[profile] 选项为unpacked 。"unpacked "选项指示Rustc将.o对象文件留在构建输出目录中,而不是删除它们,并跳过运行dsymutil的步骤。Rust的回溯支持足够聪明,知道如何找到这些.o文件。lldb等工具也知道如何做到这一点。只要你不需要把二进制文件移到不同的位置,同时保留调试信息,这应该是可行的:
[profile.dev]
split-debuginfo = "unpacked"
稳定的API
在这个版本中,总共稳定了18个新的方法,用于各种类型,如slice 和Peekable 。一个值得注意的补充是稳定了ptr::addr_of! 和ptr::addr_of_mut! ,它允许你创建指向无对齐字段的原始指针。以前这是不可能的,因为Rust要求&/&mut 是对齐的并指向初始化的数据,而&addr as *const _ 会导致未定义的行为,因为&addr 需要对齐。这两个宏现在可以让你安全地创建不对齐的指针:
use std::ptr;
#[repr(packed)]
struct Packed {
f1: u8,
f2: u16,
}
let packed = Packed { f1: 1, f2: 2 };
// `&packed.f2` would create an unaligned reference, and thus be Undefined Behavior!
let raw_f2 = ptr::addr_of!(packed.f2);
assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);
以下方法被稳定化:
Arc::decrement_strong_countArc::increment_strong_countOnce::call_once_forcePeekable::next_if_eqPeekable::next_ifSeek::stream_positionarray::IntoIterpanic::panic_anyptr::addr_of!ptr::addr_of_mut!slice::fill_withslice::split_inclusive_mutslice::split_inclusiveslice::strip_prefixslice::strip_suffixstr::split_inclusivesync::OnceStatetask::Wake
其他变化
在Rust 1.51.0版本中还有其他变化:请查看Rust、Cargo和Clippy的变化。
1.51.0的贡献者
许多人共同创造了Rust 1.51.0。没有你们,我们不可能做到这一点。谢谢你们!