Rust 新版解读 | 1.86 | Trait upcasting

Rust 1.86 官方 release doc: Announcing Rust 1.86.0 | Rust Blog

通过 rustup 安装的同学可以使用以下命令升级到 1.86 版本:

$ rustup update stable

特征向上转型(Trait upcasting)

本次更新包含了一个期待已久的功能————特征对象的向上转型能力。如果一个特征拥有 supertrait,你可以将指向该特征对象的引用强制转换为指向 supertrait 对象的引用:

#![allow(unused)]
fn main() {
trait Trait: Supertrait {}
trait Supertrait {}

fn upcast(x: &dyn Trait) -> &dyn Supertrait {
    x
}
}

该功能同样适用于其他类型的(智能)指针,例如 Arc<dyn Trait> -> Arc<dyn Supertrait>*const dyn Trait -> *const dyn Supertrait

此前需要通过特征内部定义 upcast 方法(如 fn as_supertrait(&self) -> &dyn Supertrait )来实现,且仅适用于单一引用/指针类型。现在不再需要此类变通方案。

需注意:这意味着特征对象的裸指针具有一个特别的约束:“泄漏”一个指向无效虚表的裸指针到安全代码可能导致未定义行为。当前尚不清楚在严格受控环境下临时创建此类裸指针是否会立即引发未定义行为,因此应避免在任何条件下创建此类指针(Miri 会强制执行此约束)。

特征向上转型在与 Any 特征结合时尤为实用,无需添加特征方法或使用外部crate即可将特征对象向上转型为 dyn Any 来调用 Any 的向下转型方法:

#![allow(unused)]
fn main() {
use std::any::Any;

trait MyAny: Any {}

impl dyn MyAny {
    fn downcast_ref<T>(&self) -> Option<&T> {
        (self as &dyn Any).downcast_ref()
    }
}
}

更多细节请参阅Rust参考文档中关于特征向上转型的内容

HashMap和切片现支持可变索引多个元素

借用检查器原先会阻止通过重复调用 get_mut 方法获取的借用同时使用。为安全地支持获取多个可变引用,标准库现在为切片和 HashMap 提供 get_disjoint_mut 辅助方法,可同时获取多个元素的可变引用。示例取自 slice::get_disjoint_mut API 文档

#![allow(unused)]
fn main() {
let v = &mut [1, 2, 3];
if let Ok([a, b]) = v.get_disjoint_mut([0, 2]) {
    *a = 413;
    *b = 612;
}
assert_eq!(v, &[413, 2, 612]);

if let Ok([a, b]) = v.get_disjoint_mut([0..1, 1..3]) {
    a[0] = 8;
    b[0] = 88;
    b[1] = 888;
}
assert_eq!(v, &[8, 88, 888]);

if let Ok([a, b]) = v.get_disjoint_mut([1..=2, 0..=0]) {
    a[0] = 11;
    a[1] = 111;
    b[0] = 1;
}
assert_eq!(v, &[1, 11, 111]);
}

允许安全函数标记 #[target_feature] 属性

此前仅 unsafe 函数可标记 #[target_feature] 属性,因未启用目标特性时调用此类函数会导致未定义行为。本次更新稳定了 target_feature_11 特性,允许安全函数标记该属性。

标记目标特性的安全函数仅可被同标记函数安全调用,但不可传递给以 Fn* 特征约束的泛型函数,且仅支持在标记函数内部强制转换为函数指针。

在未标记函数内部可通过 unsafe 块调用,但调用者需确保目标特性可用:

#![allow(unused)]
fn main() {
#[target_feature(enable = "avx2")]
fn requires_avx2() {
    // ... 省略
}

#[target_feature(enable = "avx2")]
fn safe_callsite() {
    requires_avx2(); // 安全调用
}

fn unsafe_callsite() {
    if is_x86_feature_detected!("avx2") {
        unsafe { requires_avx2() }; // 需手动验证
    }
}
}

详情参阅 target_features_11 RFC

为保障内存安全添加指针非空调试断言

编译器现会在以下场景插入调试断言:对非零大小类型的读写操作前,以及将指针重新借用为引用时。例如以下代码在启用调试断言时将触发 panic:

#![allow(unused)]
fn main() {
let _x = *std::ptr::null::<u8>();
let _x = &*std::ptr::null::<u8>();
}

自Rust 1.53.0起简单案例已产生警告,新运行时检查可检测任意复杂场景。这些断言仅在调试模式下生效,因此不应该依赖其保障内存安全。且调试模式禁用的依赖项(如标准库)即使被调试模式代码调用也不会触发断言。

默认启用 missing_abi lint 警告

在 extern 块和函数中省略 ABI(如 extern {}extern fn)现在会触发 missing_abi lint 警告。extern 关键字后省略 ABI 始终隐式采用 "C" ABI,现建议显式指定(如 extern "C" {}extern "C" fn)。

参阅显式外部 ABI RFC获取详情。

1.87.0版本目标弃用警告

Tier-2 目标 i586-pc-windows-msvc 将在Rust 1.87.0移除。该目标与更流行的i686-pc-windows-msvc的区别在于不要求SSE2指令支持,所有windows目标(除win7目标外)的最低要求系统版本,即 Windows 10 本身就需要SSE2指令。

当前使用该目标的用户应在1.87.0发布前迁移至i686-pc-windows-msvc。详情参见重大变更提案

Others

其它更新细节,和稳定的 API 列表,参考原Blog