一些不错的 Rust Unstable 特性
旧的 Rust 特性译文,其中部分已经 Stablized ,部分成为废案。 本文原文发布于2021-07-26,其中部分内容可能已经过时。请以 Rust RFC 为准。 已经稳定的特性见 accepted.rs。 最后一次更新:2024-02-08 术语翻译部分参考了 Rust 语言术语中英文对照表。 如果有原文错误,请联系 Ethan Brierley 且联系我更新翻译。 原文 by Ethan Brierley 这篇文章介绍了一些尚不稳定的 Rust 编译器特性。我将会简单叙述这些特性,并不会深入太多细节。 Rust 有三个发布版本: stable,beta,nightly。 Nightly 编译器每天都会发布,而且只有它允许你启用 Unstable Rust 特性。 这篇文章只讨论 Unstable 编译器特性,不讨论此版本的标准库特性。 Unstable Rust 可以让你使用在Stable Rust 中不允许使用的API。为此,编译器与标准库都使用了 Unstable 特性。 使用 Unstable 特性总是伴随着一些风险。它们经常会有一些不期望行为,有时甚至会破坏 Rust 的内存安全保证,导致未定义行为。这些特性可能开发的很好,但也能未开发完善。 使用Unstable特性的 Nightly 编译器,遇到“内部编译器错误”并不少见,这种情况通常称为ICE。它发生于编译过程中,编译器将会panic。这可能是由于数据与查询操作因未完成的特性而被破坏,甚至可能只是因为没做完的特性中打了个 如果你遇到了ICE,检查一下是否已经被反馈,没有的话就把它报告给bug tracker。 Rust 不保证在未来继续支持它的 Unstable 特性。 作为 Rust 开发者,我们享受着优秀的向下兼容性与稳定性, 而启用 Unstable 特性时,Rust不再提供这些保证。 今天工作的程序可能明天就寄了! 我决定学习 Unstable 特性,不是因为我需要用它们去解决实际问题,而是觉得他们很有意思。 对我来说,使用 Unstable 特性,可以让我有趣地,更多的参与语言本身的开发过程。 Unstable 特性的完整列表见Unstable 特性列表。 若要使用 Unstable 特性,首先你需要安装 Nightly 工具链: 临时使用 Nightly 工具链,你可以在运行cargo时加上 例如: 另外,你可以将你的默认编译器改为 Nightly ,这样你就不再需要加上 `+nightly。 切换到 nightly 编译器后,你就可以使用 Unstable 特性。让我们试一试吧! 你会得到如下编译错误: Rust 在 所有 Unstable 特性都需要用 现在,我们看看这些特性。 我把需要启用的特性名称放在每个特性的标题中的 于 Rust 1.59 稳定。 在Rust中,我们经常在绑定到定义时解构某个类型。 我们一般会使用 目前这种写法只得在实例化新的定义时使用。 换句话说,我们可以不使用 于 Rust 1.65 稳定。 Rust 有一个不那么广为人知的特性, 标记代码块的语法,和生命周期很相似。 现在也可以用同样的方式标记 我们可以把标签放在 这个特性不等价于goto。 它没有 goto 那样的破坏性影响,他只是往后继续执行,从一个代码块中退出。 版本引导用这个例子解释问号运算符的工作方式: 使用 接下来重写我们的 我喜欢这个特性。特别是对于较小的表达式,如果不提取成函数,可读性会更好。 目前,指定某个值编译时计算需要定义一个常量。 有了 在这个简单的例子中, 因为编译器优化 constant propagation, 这个特性也允许在const块中使用模式匹配。 如 拓展 于 Rust 1.64 稳定。 目前, 这是一个 stable Rust 函数: 有了 看看这个冗长重复的函数签名: 有了这个特性,我们就可以简单地写成: 这种写法容易理解许多。 这些特性允许你为 泛型类型, 关联类型 以及 const 变量 在更多地方指定默认值。 它们允许你作为开发者创建更好的 API 。 如果一个crate的用户对细节不感兴趣,而它有默认值,则可以忽略细节。 这也让拓展 API 变得容易,无需做出破坏性更新。 这些特性都被标准库使用。 注意 如果每个类型都能简单地实现自动trait ,它们也不会那么有用。 这正是引入 注意 这个特性为 trait 添加了 Rust 不允许定义trait的实现时覆盖此前的实现。 这样编译器就能确定要使用哪个实现——只有一个。 标志为 注:impl_trait_in_binding 临时被移除(2022-07-26),可能是因为它导致了破坏性更新 需要注意的是,使用 目前它们只能被自动实现。 这两个特性让 正如原文作者所说,rust(包括内部实现)正在减少box syntax的使用。 所以这个章节咕了(理直气壮) This makes things a little more ergonomic but I don’t think there is much chance that this feature will ever be stabilised. It seems to have existed forever with no plan for stabilisation but instead a little discussion about removing the feature. It is interesting to note that 目前在闭包中使用异步代码你需要加async块。 于 Rust #93845 移除; 原始 RFC 中,不单独标注的提案被 Rust 拒绝。 详见 Rust #44524 。 使用生命周期标记时,必须事先定义: 使用 这是生命周期在rust 于 Rust 1.58 稳定。 This allows for named arguments to be placed inside of strings inside any macro that depends on 已于 Rust #97254 移除。 理由: 会产生歧义 1 这个特性允许你写 已于 Rust #101728 移除 理由:语法不Rust 用 这个不能编译,因为rust无法推导出 有了 你可以定义没有变体的枚举体, 这种枚举体也存在于标准库中。 你可以在泛型或函数签名中使用该类型,但它不可能被构造。 元类型 Because Loops without a It is then possible to use an empty 译注:原作者的解释和 RFC,Issue 都对不上。 原Issue目标为,不可达的分支可以省略 如析构一个 With the feature We can combine this with destructuring to remove the 这个小节由 @Hinanawi_Tenshi_M 提供翻译,有改动 你可以用 这对微调应用程序非常有用。在这些应用程序中,大小和性能的权衡特别重要。例如使用 web assembly 时。 这个特性让你可以在几乎任何地方标记属性,而不仅仅是顶层项目。例如,你可以在一个闭包上放一个optimize attribute 该特性允许根据编译器版本进行条件编译。 这使得你的 crate 能够使用最新的编译器功能,同时仍然保持对旧编译器的后备支持。 不依赖 这对于不在完整环境中运行的应用非常重要,如嵌入式系统。 嵌入式系统通常没有操作系统,甚至没有动态内存,所以 你现在可以通过 这样不会留下任何东西——你甚至不能使用libc。 这个小节由 @Hinanawi_Tenshi_M 提供翻译,有改动。 已经在 Stable Rust 部分实现。 2 在都柏林 Rust 集会中,关于 Rust的声明式宏非常强大。然而, ——因为代码只是被简单地粘贴回原处。 Macros 2.0 介绍了 编写 Macros 2.0 只需使用关键字 它引入了一种新的格式,Hygiene 。Hygiene 允许宏应用它们定义处的可见性规则,而不是调用处。 生成器(协程)提供了一种特殊的函数,可以在执行过程中暂停,“yield” 中间值给调用者。 生成器允许你使用 生成器中也可以 大约三年前,我尝试编写算法,沿对角线遍历一个无穷的矩阵。我发现用Rust的迭代器编写它非常困难,最终放弃了。 这是我的实现,使用了Rust的生成器(协程)和一些我们刚刚讨论过的特性。 It is understandable if you found the above snippet hard to interpret. It makes use of a number of features that you may have just been introduced to. There is a compelling argument against adding too many new features as they can greatly increase the learning curve. 生成器让一些没有这个特性会难以编写甚至无法编写的实现变为可能。 生成器在标准库中是为了实现 async/await 添加的。 具体的语义在稳定化前很可能被修改,但它很有趣。 我很抱歉,没有介绍其他三个不错的unstable特性:GAT, 内联汇编和特化。 我只是感觉,在这篇文章中,我做不到客观的评价它们,不过将来我可能会尝试。 如果你想了解更多unstable特性,我推荐你看the unstable book,这里会列出绝大部分。 Unstable book会连接到tracking issue,而后者往往会链接到RFC。 组合使用这些来源,你可以很好地了解新特性。 Thank you for reading my first blog post 😃. The best way to support me is by following my Twitter. I am also looking for employment opportunities so please get in touch if you would like to talk about that.注意
关于翻译
Credits
简介
什么是Unstable Rust?
为什么要用 Unstable 特性?
todo!()。启用 Unstable 特性
rustup toolchain install nightly+nightly。<rust-command> +nightly <args>cargo +nightly runrustup default nightlyfn main() {
let my_box = box 5;
}error[E0658]: box expression syntax is experimental; you can call `Box::new` instead
--> src/main.rs:2:18
|
2 | let my_box = box 5;
| ^^^^^
|
= note: see issue #49733 <https://github.com/rust-lang/rust/issues/49733> for more information
= help: add `[()]` to the crate attributes to enablehelp 消息中准确地告诉了我们应该做什么—— 我们需要用 #![feature(box_syntax)] 启用这个特性。[()]
fn main() {
let my_box = box 5;
}#![feature(..)] 启用。 即使你忘记了,编译器通常也会指出要如何做,虽然不总会是。代码块 中,在代码片段中省略,以保持简洁。控制流、模式和块
destructuring_assignmentlet绑定:// 创建两个变量, 一个是 x, 一个是 y
let Point { x, y } = Point::random();destructuring_assignment 将它拓展到赋值。let 完成解构。let (mut x, mut y) = (0, 0);
Point { x, y } = Point::random();从任意块提前返回,
label_break_valueloop可以带值退出。 就像 Rust 中许多其它的结构,在 Rust 中 loop 并不仅仅是语句, 而是表达式。// 保持请求用户输入一个数字,直到他们给出一个有效的数字。
let number: u8 = loop {
if let Ok(n) = input().parse() {
break n;
} else {
println!("Invaid number, Please input a valid number");
}
};label_break_value 把这拓展到任何被标记的块,而不仅仅是 loop。 它的行为,就像是一种提前的 return ,不过适用于任何代码块,而不只是函数体。'block: {
// 这个代码块现在被标记为 "block" 。
}loop。break 后面,从那个代码块提前返回。let number = 'block: {
if s.is_empty() {
break 'block 0; // 从代码块提前返回
}
s.parse().unwrap()
}使用
try_blocks 内联 ? 操作符的功能fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("username.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}? 操作符简化,可以得到等效的代码:fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("username.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}? 可以在函数中提前返回 Err。 try_blocks 提供了适用于任意代码块的相同功能。 使用 try_blocks ,我们可以内联 read_usernames_from_file 函数。try_blocks 和 ? 的关系就像是 label_break_value 和 return 的关系。 try_blocks 的RFC提到了 label_break_value ,作为 try_blocks 一种可能的解糖。read_username_from_file , 我们得到了一个简单的 let 绑定和 try 代码块。let read_username_from_file: Result<String, io::Error> = try {
let mut f = File::open("username.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}inline_constconst PI_APPROX: f64 = 22.0 / 7.0;
fn main() {
let value = func(PI_APPROX);
}inline_const 我们可以用匿名表达式完成同样的事。fn main() {
let value = func(const { 22.0 / 7.0 });
}const 块是不必要的。 但是对于更复杂的常量,用块来表示,可能会更好。match x { 1 + 3 => {} } 会导致语法错误,而 match x { const { 1 + 3 } => {} } 不会。if_let_guardmatch 中的 if 守卫 ,使其允许使用 if let。let_chainsif let 和 while let 表达式不能以 || 或 && 连接, 这个特性添加了支持。Traits
associated_type_boundsfn fizzbuzz() -> impl Iterator<Item = String> {
(1..).map(|val| match (val % 3, val % 5) {
(0, 0) => "FizzBuzz".to_string(),
(0, _) => "Fizz".to_string(),
(_, 0) => "Buzz".to_string(),
(_, _) => val.to_string(),
})
}associated_type_bounds 特性,对于这种情况,我们可以使用一个匿名类型:fn fizzbuzz() -> impl Iterator<Item: Display> { ... }fn flatten_twice<T>(iter: T) -> Flatten<Flatten<T>>
where
T: Iterator,
<T as Iterator>::Item: IntoIterator,
<<T as Iterator>::Item as IntoIterator>::Item: IntoIterator,
{
iter.flatten().flatten()
}fn flatten_twice<T>(iter: T) -> Flatten<Flatten<T>>
where
T: Iterator<Item: IntoIterator<Item: IntoIterator>>,
{
iter.flatten().flatten()
}default_type_parameter_fallback, associated_type_defaults以及const_generics_defaultsconst_generics_defaults 于 Rust 1.59 稳定。negative_impls 和 auto_traitsSend 和 Sync 都是自动 trait。Send trait 定义于标准库:pub unsafe auto trait Send {
// 空的
}auto关键字,它让编译器为任意结构体/枚举体/联合体自动实现 Send trait,(前提是构成这个类型的类型都实现了Send)negative_impls 的原因。negative_impls 允许一个类型不实现某个auto trait。 举个例子,UnsafeCell。不受限制的 UnsafeCell 在线程间共享非常不安全,因此它被标记为 Sync 也不安全。impl<T: ?Sized> !Sync for UnsafeCell<T> {}! ,表示 “不Sync”。marker_trait_attr#[marker] 属性。#[marker] 的 trait 不能在实现中覆盖任何东西。 这样它们就能允许重叠的实现,因为所有的实现都是一样的。type_alias_impl_trait, impl_trait_in_bindings and trait_aliasimpl Trait 让编译器推导具体类型,把它换成实现了Trait的类型。 目前,impl Trait只能在函数参数或返回类型中使用,无法应用于变量绑定。type_alias_impl_trait 时,类型必须是固定的。编译器会推断且应用单一具体的类型。[()]
type Foo = impl AsRef<str>;
fn foo(_: Foo) {}
fn main() {
foo(String::new());
foo("");
}error[E0308]: mismatched types
--> src/main.rs:9:9
|
3 | type Foo = impl AsRef<str>;
| --------------- the expected opaque type
...
9 | foo("");
| --- ^^ expected opaque type, found `&str`
| |
| arguments to this function are incorrect
|
= note: expected opaque type `Foo`
found reference `&'static str`
note: function defined here
--> src/main.rs:5:4
|
5 | fn foo(_: Foo) {}
| ^^^ ------fn_traits and unboxed_closuresFn,FnMut和FnOnce被认为是fn的trait。 它们会被任何函数或者你创建的闭包自动实现,它们允许你给它们传参。fn_trait 则允许为任意类型提供自定义实现。 这就像是操作符重载,但要自定义的是()调用。[()] // 实现带有`extern "rust-call"`的函数
[()]
struct Multiply;
#[()]
const multiply: Multiply = Multiply;
impl FnOnce<(u32, u32)> for Multiply {
type Output = u32;
extern "rust-call" fn call_once(self, a: (u32, u32)) -> Self::Output {
a.0 * a.1
}
}
impl FnOnce<(u32, u32, u32)> for Multiply {
type Output = u32;
extern "rust-call" fn call_once(self, a: (u32, u32, u32)) -> Self::Output {
a.0 * a.1 * a.2
}
}
impl FnOnce<(&str, usize)> for Multiply {
type Output = String;
extern "rust-call" fn call_once(self, a: (&str, usize)) -> Self::Output {
a.0.repeat(a.1)
}
}
fn main() {
assert_eq!(multiply(2, 3), 6);
assert_eq!(multiply(2, 3, 4), 24);
assert_eq!(multiply("hello ", 3), "hello hello hello ");
}语法糖
box_patterns and box_syntaxBox的构造和析构变得更容易。 box关键字将取代Box::new(...),并且允许在模式匹配中解引用Box。struct TrashStack<T> {
head: T,
body: Option<Box<TrashStack<T>>>,
}
impl<T> TrashStack<T> {
pub fn push(self, elem: T) -> Self {
Self {
head: elem,
body: Some(box self),
}
}
pub fn peek(self) -> Option<T> {
if let TrashStack {
body: Some(box TrashStack { head, .. }),
..
} = self
{
Some(head)
} else {
None
}
}
}box_synatx is used heavily in the compiler’s source and a little in the standard library.box does not desugar to Box::new but Box::new is implemented in the standard library with box.impl<T> Box<T> {
...
pub fn new(x: T) -> Self {
box x
}
...
}async_closureapp.at("/").get(|_| async { Ok("Hi") });async_closure 允许你将闭包本身标记为异步的,像异步函数那样写app.at("/").get(async |_| Ok("Hi"));in_band_lifetimesfn select<'data>(data: &'data Data, params: &Params) -> &'data Item;in_band_lifetimes ,生命周期可以不先显式定义。fn select(data: &'data Data, params: &Params) -> &'data Item;1.0.0前的写法。format_args_capturestd::format_args!. That includes print!, format!, write! and many more.let name = "Ferris";
let age = 11;
println!("你好{name},你{age}岁了。");crate_visibility_modifierpub struct Foo(crate ::std::path::Path); crate struct Foo 而不是 pub(crate) struct Foo ,语义不变。Types
type_ascriptionIterator的collect方法举个例子: collect将迭代器转换到集合let word = "hello".chars().collect();
println!("{:?}", word);word的类型。 可以把第一行换成:let word: Vec<char> = "hello".chars().collect();type_ascription就不需要再加上let绑定,我们可以直接:println!("{:?}", "hello".chars().collect(): Vec<char>);: Type 语法可以用在任何一处,提醒编译器“我在这里想要得到这个类型”never_typepub enum Infallible {}() 等价于只有一个变体的枚举。 never_type 引入了一种新的类型,!等价于没有变体的 Infallible。! can never be constructed it can be given special powers. We don’t have to handle the case of ! because we have proven it will never exist.fn main() -> ! {
loop {
println!("Hello, world!");
}
}break “return !” because they don’t ever return.! can be very useful for expressing impossible outcomes in the type system. Take for example the FromStr implementation on this UserName type. This implementation is infallible because its implementation can never fail. This allows us to set the Err variant to type !.struct UserName(String);
impl FromStr for UserName {
type Err = !;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_owned()))
}
}match on the Err variant because ! has no variants.let user_name = match UserName::from_str("ethan") {
Ok(u) => u,
Err(e) => match e {},
};exhaustive_patternsResult<T, !> ,fn safe_unwrap<T>(x: Result<T, !>) -> T {
match x {
Ok(y) => y,
}
}exhaustive_patterns the type system becomes smart enough for us to eliminate the Err branch altogether.let user_name = match UserName::from_str("ethan") {
Ok(u) => u,
};match leaving a beautiful line of code.let Ok(user_name) = UserName::from_str("ethan");attribute
optimize attribute
Cargo.toml 的 opt-level 选项指定你想要怎么优化你的二进制文件。opt-level 指定的是整个 crate 的优化方式,如果你想要分别控制每一个项目的优化方式,你可以使用 optimize_attribute 选项。#[()]
fn fast_but_large() {
...
}
#[()]
fn slow_but_small() {
...
}stmt_expr_attributescfg_version#[(("1.42"))] // 1.42 以上
fn a() {
// ...
}
#[((("1.42")))] // 1.41 以下
fn a() {
// ...
}no_core::std 的 #![no_std] 选项已经存在很久了,#![no_core] 则对应着不依赖 ::core 。std 中的许多功能都无法使用。#![no_core] 表明不使用 libcore。其它
Const Generic
const_generics ,我做过一场演讲。 与其重复那些内容,我更推荐大家去看这个演讲。Macros 2.0
macro_rules! 的一些规则,让我很困惑。macro_rules! 是一个简单的token转换过程,或者说,macro_rules!的一种替代。macrogeneratorsyield关键字返回多个值,每次暂停该函数并返回给调用者。return单个值,不可再恢复。[(
,
,
,
,
)]
use std::{
iter,
ops::{Generator, GeneratorState},
pin::Pin,
};
/// Input
/// [[1, 2, 3]
/// ,[4, 5, 6]
/// ,[7, 8, 9]]
/// Output
/// [1, 2, 4, 3, 5, 7]
fn diagonalize<T>(
mut matrix: impl Iterator<Item: Iterator<Item = T>>,
) -> impl Generator<Yield = T, Return = ()> {
move || {
let mut rows = Vec::new();
(try {
rows.push(matrix.next()?);
for height in 0.. {
for row in 0..height {
if row >= rows.len() {
rows.push(matrix.next()?);
}
yield rows[row].next()?;
}
}
}): Option<()>;
}
}
fn main() {
let matrix = (0..).map(|x| iter::once(x).cycle().enumerate());
let mut diagonals = diagonalize(matrix);
while let GeneratorState::Yielded(value) = Pin::new(&mut diagonals).resume(()) {
dbg!(value);
}
}总结