掘金 后端 ( ) • 2024-05-04 17:17

highlight: a11y-dark theme: condensed-night-purple

rust-course-banner.jpeg Rust 团队日前发布了 Rust 的新版本 —— 1.78.0 正式版,Rust 是一种强大的编程语言,使开发者能够构建可靠、高效的软件。

注:关于 1.78.0 有关新特性可查看 Rust blog

如果已经安装了以前版本的 Rust,可以通过以下命令升级到 1.78.0 版本: $ rustup update stable

并查看1.78.0 的详细发行说明 。也可以在 > GitHub 上查看1.78.0 的发行日志 。

如果还没有安装,可以从网站上的相应页面获取 rustup 安装(命令: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh )

如果想使用测试未来版本,可以考虑在本地进行更新以使用 beta 版本(rustup default beta) 或 nightly 版本  (rustup default nightly),这两个版本不太稳定,使用过程中可能遇到错误,如果遇到任何错误,可以发送报告

Rust_1.7.8.0.png

1.78.0 稳定版中有什么

秉承了 Rust 团队一贯的风格,1.78.0 版本带来一些渐进性的改进并来带来整体性提升,使得 Rust 语言更加强大和易用,本文主要介绍其中几个功能和改进:

诊断属性(Diagnostic attributes)

Rust 1.78.0 新增支持 #[diagnostic] 属性命名空间,来影响编译器的错误消息。编译器会将这些诊断信息当做非必需使用的提示,即使提供编译器无法识别的诊断信息也不会报错。这些提示对编译器来说是可选的,即便是不同的编译器或 Rust 版本可能无法识别这些提示,开发者依然可以使用它们,相当的灵活。

想象一下,有时候你写代码,编译器会报错,告诉你出了什么问题。有了"诊断属性",你现在可以给这些错误信息加上小提示。这些提示并不是编译器必须要用的,就是说,如果你加上了编译器不认识的提示,那也不会有错。这样很灵活,即便是以后的其他版本或者不同的编译器不支持这个功能,你的代码里面还是可以有这些提示信息的。

此命名空间带来的第一个受支持的属性是 #[diagnostic::on_unimplemented]。这个属性可以放在你定义的 trait上,如果别人在写代码的时候应该用到这个 trait 但是忘了实现,编译器就会显示你自定义的错误信息。就好像是说:“嘿,你需要实现这个 trait,别忘了!” 这样一来,错误信息就更清晰,更容易懂了。示例代码如下:

#[diagnostic::on_unimplemented(  
message = "My Message for `ImportantTrait<{A}>` is not implemented for `{Self}`",  
label = "My Label",  
note = "Note 1",  
note = "Note 2"  
)]  
trait ImportantTrait<A> {}  
  
fn use_my_trait(_: impl ImportantTrait<i32>) {}  
  
fn main() {  
use_my_trait(String::new());  
}

以前,编译器会给出如下内置错误:

error[E0277]: the trait bound `String: ImportantTrait<i32>` is not satisfied  
--> src/main.rs:12:18  
|  
12 | use_my_trait(String::new());  
| ------------ ^^^^^^^^^^^^^ the trait `ImportantTrait<i32>` is not implemented for `String`  
| |  
| required by a bound introduced by this call  

现在使用#[diagnostic::on_unimplemented]时,其自定义消息将显示主错误行信息,并且其自定义标签(label)将放置在源输出上。原始标签会变成帮助(help)信息输出,并且也会写任何自定义注释。

error[E0277]: My Message for `ImportantTrait<i32>` is not implemented for `String`
  --> src/main.rs:12:18
   |
12 |     use_my_trait(String::new());
   |     ------------ ^^^^^^^^^^^^^ My Label
   |     |
   |     required by a bound introduced by this call
   |
   = help: the trait `ImportantTrait<i32>` is not implemented for `String`
   = note: Note 1
   = note: Note 2

对于某个 trait 来说,如果想要提供更友好的提示,而不仅仅是讨论unimplemented本身,那么这种诊断会更有用。例如,这是标准库中的一个示例:

#[diagnostic::on_unimplemented(
    message = "the size for values of type `{Self}` cannot be known at compilation time",
    label = "doesn't have a size known at compile-time"
)]
pub trait Sized {}

想了解更多,请请参阅diagnostic详细文档

断言 unsafe 前置条件(Asserting unsafe preconditions)

Rust 标准库对于不安全函数的前置条件有许多断言,但是历史版本上这些断言只在标准库的 #[cfg(debug_assertions)] 编译版本中启用,以避免影响发布版本的性能。然而,由于标准库通常是在发布模式下编译和分发的,大部分 Rust 开发者实际上根本没有执行过这些检查。

现在,这些断言的条件被延迟到代码生成时,因此会根据用户自己的调试断言设置进行检查,通常在调试和测试版本中默认启用。这个改进有助于开发者在调试和测试期间捕捉到潜在的未定义行为,从而提高 Rust 程序的安全性和可靠性。

例如,slice::from_raw_parts 函数要求一个对齐的非空指针。下面的代码故意使用了一个未对齐的指针,这会导致未定义的行为。虽然以前你可能碰巧遇到它似乎可以 "正常工作" 的情况,但现在调试断言是可以捕获到这种错误,示例代码如下:

fn main() {
    let slice: &[u8] = &[1, 2, 3, 4, 5];
    let ptr = slice.as_ptr();

    // 创建一个从 `ptr` 开始的偏移量,该偏移量总是与 `u16` 的正确对齐方式相差一个单位
    let i = usize::from(ptr as usize & 1 == 0);
    
    // 使用未对齐的指针创建一个 `[u16]` 类型的切片
    let slice16: &[u16] = unsafe { std::slice::from_raw_parts(ptr.add(i).cast::<u16>(), 2) };
    dbg!(slice16);
}

运行上述代码,将会触发一个调试断言失败,因为 slice::from_raw_parts 要求指针必须对齐且非空,切片的总大小不超过 isize::MAX。以下是输出的提示信息:

thread 'main' panicked at 'library/core/src/panicking.rs:220:5: unsafe precondition(s) violated: slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`', note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread caused non-unwinding panic. aborting.

确定性调整(Deterministic realignment)

Rust 标准库包含一些用于处理指针和切片内存对齐的函数,但是这些函数在以前的版本中存在一些限制,在实际使用中并不像文档中承诺的那样可靠,如果开发者严格遵循官方文档,可能会出现实际运行时与预期不一致的问题,现在对这些限制作出调整,能够保证对齐行为。

比如如下几个函数:

  1. pointer::align_offset: 该函数计算将指针调整到特定对齐所需的偏移量。在以前版本,即使对齐方式是有效的,也可能始终返回最大值 (usize::MAX),。该版本解决了这个问题,现在如果对齐方式是有效的,则给出的偏移量始终是正确的,只有在不存在有效对齐时才返回 usize::MAX

  2. slice::align_toslice::align_to_mut: 两个函数都可以将切片转换成对齐的中间切片以及剩余未对齐的头部和尾部切片。在以前版本,这些函数可能会返回不理想的结果。现在这些方法能够保证返回的是可靠的中间部分,不允许返回不太理想的结果。

稳定的 API 列表

以下是该版本中稳定的 API:

  • impl Read for &Stdin
  • 接受多个 std::error::Error 相关实现的非 'static 生命周期
  • 使 impl<Fd: AsFd> 支持 ?Sized
  • impl From<TryReserveError> for io::Error
  • Barrier::new()

兼容性说明

Rust 1.78.0 提高了对 Windows 10 的最低要求,主要针对以下的目标(targets):

  • x86_64-pc-windows-msvc
  • i686-pc-windows-msvc
  • x86_64-pc-windows-gnu
  • i686-pc-windows-gnu
  • x86_64-pc-windows-gnullvm
  • i686-pc-windows-gnullvm

到了 Rust 1.78.0 升级了其捆绑的 LLVM 到版本 18,完成了 x86-32 和 x86-64 target 的 u128/i128 ABI 变更。 当LLVM 版本低于 18 时可能面临着一些约定的调用错误。

其他变化

了解更多有关Rust、Cargo 和 Clippy 中所有变化的详细信息,请参阅完整的发布说明 ,更新的详细列表可查看

0d724ec7e07f47d83a27ab17990480c0.png

官方资料

关于1.78.0 升级的更多信息,可以查阅:

  • 1.78.0 官方博客:https://blog.rust-lang.org/2024/05/02/Rust-1.78.0.html
  • 1.78.0 版本标记:https://github.com/rust-lang/rust/releases/tag/1.78.0
  • 1.78.0 变更日志:https://releases.rs/docs/1.78.0
  • 1.78.0 感谢列表:https://thanks.rust-lang.org/rust/1.78.0
  • 1.78.0 Cargo 更新:https://github.com/rust-lang/cargo/blob/master/CHANGELOG.md#cargo-178-2024-05-02
  • 1.78.0 Clippy 更新:https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md#rust-178