这篇文章描述了几个不稳定的 Rust 编译器特性。它旨在解释这些特性的基础知识,而不会深入研究太多细节。不仅每夜编译器每天都会出现,而且它还是唯一允许您解锁不稳定的 Rust 功能的编译器。本文讨论不稳定的编译器特性。不稳定的库功能是另一个话题。不稳定的 Rust 可以让你编写在稳定的 Rust 中无法表达的 API。出于这个原因,编译器和标准库中都使用了不稳定的特性。使用不稳定的特性总是伴随着一些风险。它们通常会以意想不到的方式运行,有时甚至会破坏 Rust 的内存安全保证并导致未定义的行为。部分特性可以很好地开发,而其他部分则没有开发。对于使用不稳定功能的夜间编译器,遇到通常称为 ICE 的“内部编译器错误”并不少见。这种情况发生在编译期间,编译器恐慌。这可能是由于数据和查询因不完整的功能而变得畸形或甚至只是打一个待办事项!关于尚未开发的功能的一部分。如果您遇到 ICE,检查问题是否已知,如果不知道,将其报告给错误跟踪器通常会很有帮助。 Rust 不保证它会在未来继续支持其不稳定的特性。作为 Rust 开发人员,我们被出色的向后兼容性和稳定性所宠坏。当启用不稳定的特性时,所有这些保证都会被抛弃。今天有效的可能有效明天大不相同。
我决定研究不稳定的特性并不是因为我需要它们来解决特定的问题。我寻找它们是因为我觉得它们很有趣。对我来说使用不稳定的特性是一种让更多人参与语言本身开发过程的有趣方式.要开始使用不稳定的功能,您需要做的第一件事是通过运行以下命令安装每晚工具链:或者,您可以将默认编译器更改为每晚,这样您就不需要使用 +nightly 修饰符。I'我经常这样做,因为我没有发现夜间编译器太不稳定,即使对于我的项目也能在稳定版上编译得很好。一旦你使用了夜间编译器,你就可以开始使用不稳定的特性了。让我们试一试吧。错误[E0658]:框表达式语法是实验性的;你可以改为调用`Box::new` --> src/main.rs: 2 : 18 | 2 |让 my_box = 框 5 ; | ^^^^^ | = 注意:请参阅问题 # 49733 <https://github.com/rust-lang/rust/issues/49733> 了解更多信息 = 帮助:将 `#![ 功能(box_syntax)]` 添加到 crate 属性以启用 As在 Rust 中经常出现这种情况,帮助消息告诉我们我们需要做什么。我们需要使用 #![feature(box_syntax)] 启用该功能。所有不稳定的特性都需要通过 #![feature(..)] 启用才能使用。如果您忘记了,编译器通常能够为您指明正确的方向,但是,情况并非总是如此。
现在让我们开始讨论一些功能本身。我已经将您需要在代码块中启用的功能的名称放在每个功能的标题中,同时从代码片段中省略它们以保持简洁。在 Rust 中,在将类型绑定到定义时对其进行解构是很常见的。这通常是通过 let 绑定来完成的。 // 创建两个“变量”,一个用于 x,一个用于 y let Point { x, y } = Point::random();传统上,这种模式只有在实例化新定义时才有可能。 destructuring_assignment 将它扩展到在改变值时工作。换句话说,我们可以使用解构而不使用 let。 Rust 鲜为人知的特性之一是循环可以因值而中断。就像 Rust 循环中的许多构造一样,它不仅仅是语句,而是表达式。 // 不断要求用户输入一个数字,直到他们给我们一个有效的 let 数字: u8 = loop { if let Ok(n) = input ().解析(){ 中断 n; } else { println!("无效号码,请输入有效号码"); }}; label_break_value 将此扩展为适用于任何带标签的块,而不仅仅是循环。这作为一种早期返回,适用于任何代码块,而不仅仅是函数体。
我们可以在我们的休息时间贴上相同的标签,以便早日从该区块返回。 let number = 'block: { if s. is_empty () { break 'block 0 ; // 从块中提前返回 } s。解析()。 unwrap()} 这个特性不等同于goto。它没有和goto一样的破坏效果,因为它只是向前走,然后从一个块上中断。 fn read_username_from_file () -> Result<String, io::Error> { let f = File::open(" username.txt ");让 mut f = 匹配 f { Ok(file) => file, Err(e) => return Err(e), };让 mut s = String::new();匹配 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("用户名.txt")?;让 mut s = String::new(); F。 read_to_string (& mut s)?;好的(s)} ?在函数的上下文中使用以在遇到 Err 时提前返回 Err。 try_blocks 解锁相同的功能,但适用于任何代码块而不仅仅是函数。使用 try_blocks 我们可以内联我们的 read_usernames_from_file 函数。 try_blocks 与 ?与 label_break_value 与 return 相关的方式相同。 try_blocks 的 RFC 提到 label_break_value 作为一种对 try_blocks 进行脱糖的潜在方式。
let read_username_from_file: Result<String, io::Error> = try { let mut f = File::open(" username.txt ")?;让 mut s = String::new(); F。 read_to_string (& mut s)?; Ok(s)} 我喜欢这种东西,特别是对于较小的表达式,当不提取到函数中时可以更容易阅读。目前,在编译时获取要评估的值的方法是定义一个常量。对于这个简单的例子,const 块几乎肯定是没有必要的,因为编译器优化,如常量传播,但是,对于更复杂的常量,显式使用该块可能会有所帮助。此功能还允许将这些块用于模式位置。 match x { 1 + 3 => {} } 导致语法错误,而 match x { const { 1 + 3 } => {} } 不会。扩展可以与 match 语句一起使用的 if 守卫,以便能够使用 if let。目前,if let 和 while let 表达式不能用 || 链接起来或 &&。此功能添加了该支持。
fn 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 (), })} 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()} 这些特性允许你在更多的地方分别为泛型类型、关联类型和常量变量指定默认值。这允许您作为开发人员创建更好的 API。如果 crate 用户对细节不感兴趣,并且该项目具有默认值,则可以省略该细节。这也可以更轻松地扩展 API,而无需对您的用户进行重大更改。这两个特性都在标准库中使用。特征发送和同步都是自动特征的例子。请注意 auto 关键字的使用。这告诉编译器为任何结构/枚举/联合自动实现 Send 特征,只要构成所述类型的所有类型也实现 Send。如果只是每个类型总是实现它们,自动特征就不会很有用。这就是negative_impls 的用武之地。
negative_impls 允许类型选择退出实现自动特征。以 UnsafeCell 为例。跨线程共享不受限制的 UnsafeCell 将是非常不安全的,因此它是 Sync 将是非常不安全的。 Rust 不允许定义可能重叠的特性实现。这样编译器将始终知道要使用哪个实现,因为总是只有一个实现。用#[marker] 标记的特征不能覆盖其实现中的任何内容。这样它们就可以有重叠的实现,因为所有的实现都是相同的。 impl Trait 告诉编译器推断一个具体类型来替换它实现 Trait。目前,impl Trait 仅用于函数参数或返回类型的上下文中。 type_alias_impl_trait 和 impl_trait_in_bindings 扩展了 impl trait 可用于分别包含类型别名和 let 绑定的地方。 trait_alias 与 type_alias_impl_trait 有细微的不同。在你使用类型别名的任何地方,类型都必须保持不变。编译器必须推断出在所有这些地方工作的单一具体类型。特质别名更宽容,因为它们在每个地方都可以是不同的类型使用它们的地方。三个特征 Fn、FnMut 和 FnOnce 被称为 fn 特征。它们会自动为您创建的任何函数或闭包实现,并提供向它们传递参数的能力。
自动实现是目前实现这些特征的唯一方法。fn_traits 特性允许对任何类型进行自定义实现。这与运算符重载非常相似,但自定义了 () 的使用。 #![ feature (unboxed_closures)] // 需要用`extern "rust-call"`来实现一个函数#![ feature (fn_traits)] struct Multiply;#[ allow (non_upper_case_globals)] 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 * 一个。 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 * 一个。 1 * 一个。 2 }} impl FnOnce<(& str , usize )> for Multiply { type Output = String; extern "rust-call" fn call_once ( self , a : (& str , usize )) -> Self:: Output { a. 0. 重复 (a. 1 ) }} fn main () { assert_eq!( 乘法 ( 2 , 3 ), 6 ); assert_eq!( 乘法 ( 2 , 3 , 4 ), 24 ); assert_eq!( multiply (" hello ", 3 ), " hello hello hello ");} 请注意,这被用于创建函数重载和可变参数函数的hacky 版本。这两个特性使构建和销毁 Boxes 更容易。 box 关键字替换 Box::new(..) 并允许在模式匹配时取消引用 Boxes。 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 在编译器的源代码中大量使用,在标准库中使用很少。有趣的是,box 并没有对 Box::new 进行脱糖,但是 Box::new 是在标准库中用 box 实现的。
impl <T> Box<T> { ... pub fn new ( x : T) -> Self { box x } ...} 目前要在闭包内异步,你必须使用异步块。 async_closure 允许您将闭包本身标记为异步,就像使用异步函数一样。这允许将命名参数放置在任何依赖于 std::format_args! 的宏内的字符串内。这包括打印!,格式!,写!还有很多。使用此功能,您可以编写 crate struct Foo 而不是 pub(crate) struct Foo 并使其含义完全相同。这使得 pub(crate) 更容易编写,鼓励在不需要完整的 pub 时使用 crate 可见性。这不会编译,因为 Rust 无法推断单词的类型。这可以通过将第一行替换为:
:Type 语法可以在任何地方使用来提示编译器“我现在想要这个类型”。可以定义具有零变体的枚举。这样的枚举在标准库中稳定存在。可以在泛型和函数签名中使用这种类型,但永远不可能构造它。根本没有要构造的变体。单位类型 () 将等效于具有单个变体的枚举。 never_type 引入了一种新类型,!这相当于我们具有零变体的 Infallible 枚举。因为 !永远无法建造它可以被赋予特殊的力量。我们不必处理的情况!因为我们已经证明它永远不会存在。 !对于在类型系统中表达不可能的结果非常有用。以这个 UserName 类型的 FromStr 实现为例。这个实现是绝对可靠的,因为它的实现永远不会失败。这允许我们将 Err 变体设置为类型!。结构体用户名(字符串); impl FromStr for UserName { type Err = !; fn from_str ( s : & str ) -> Result< Self , Self:: Err> { Ok( Self (s.to_owned ())) }}
然后可以在 Err 变体上使用空匹配,因为 !没有变体。有了exhaustive_patterns 特性,类型系统变得足够智能,我们可以完全消除Err 分支。我们可以将其与解构相结合,以移除匹配项,留下一行漂亮的代码。可以使用 opt-level 指定如何使用 Cargo.toml 优化二进制文件。 opt-level 影响整个 crate,而 optimize_attribute 可以控制单个项目的优化。这对于尺寸和性能之间的权衡特别明显的微调应用程序非常有用,例如在使用 Web 组件时。此功能允许您将属性放置在几乎所有地方,而不仅仅是顶级项目。例如,使用此功能,您可以在闭包上放置优化属性。
#[ cfg ( version (" 1.42 "))] // 1.42 及以上 fn a () { // ... }#[ cfg ( not ( version (" 1.42 ")))] // 1.41 及以下 fn a () { // ... } 这允许 crate 使用最新的编译器功能,同时仍然保持对旧编译器的回退支持。使用#![no_std] 暂时可以选择不使用标准库。这对于不在完整环境(如嵌入式系统)中运行的应用程序很重要。嵌入式系统通常没有操作系统甚至是动态内存,因此 std 中的许多功能都不起作用。 #![no_core] 进一步选择退出 libcore。这让你几乎一无所有,你甚至不能使用 libc。这使得实现任何有用的东西变得非常困难。我在 Rust Dublin 的演讲中谈到了 const_generics 的未来。与其重申我在那里所说的话,我鼓励你观看那个演讲。 Rust 的声明性宏非常强大,但是一些关于 macro_rules 的规则!一直让我困惑。一方面,macro_rules!作为一个简单的标记转换。它需要一个标记列表并输出一个新的标记列表,没有比这更聪明的了。公开规则最终是宏被调用的规则。这是显而易见的,因为代码正在简单地粘贴到那个地方。
Macros 2.0 是一个 rfc,描述了对 macro_rules 的替代!只需使用关键字宏就可以使用新的构造。新语法引入的主要改进之一是宏卫生,它允许宏使用它们编写位置而不是调用位置的公开规则。生成器/协程提供了一种特殊的函数,可以在执行过程中暂停以将中间值“产生”给调用者。生成器可以使用 yield 关键字返回多个值,每次暂停函数并返回给调用者。然后生成器可以返回单个值,之后就不能再恢复了。大约三年前,我试图编写一个算法来沿对角线遍历一个无限矩阵。我发现用 Rust 的迭代器很难编写它,最终放弃了。这是使用 Rust 的生成器/协程以及我们已经讨论过的许多其他功能的实现。 #![ 特征 (try_blocks, generators, generator_trait, associated_type_bounds, type_ascription)] 使用 std::{ iter, ops::{Generator, GeneratorState}, pin::Pin,}; /// 输入/// [[1, 2, 3]/// ,[4, 5, 6]/// ,[7, 8, 9]]/// 输出/// [1, 2, 4, 3, 5, 7] fn diagonalize <T>( mut matrix : impl Iterator<Item: Iterator<Item = T>>,) -> impl Generator<Yield = T, Return = ()> { move || { 让 mut 行 = 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 () { 让矩阵 = ( 0 ..)。 map(| x | iter::once(x).cycle().enumerate());让 mut diagonals = diagonalize (matrix);而让 GeneratorState::Yielded(value) = Pin::new(& mut diagonals)。简历 (()) { dbg!(value); }}
如果您发现上述代码段难以解释,这是可以理解的。它利用了您可能刚刚了解的许多功能。有一个令人信服的论据反对添加太多新功能,因为它们会大大增加学习曲线。生成器使得编写没有它们更难甚至不可能编写的实现成为可能。在标准库中添加了生成器来实现 async-await。在任何类型的稳定之前,确切的语义很可能会发生变化,但它们玩起来很有趣。我必须为没有包含三个惊人的不稳定特性而道歉;泛型关联类型、内联汇编和专业化。我只是觉得无法在本文中对这些功能进行公正的评价,但我可能会在将来尝试讨论它们。如果您想阅读有关不稳定功能的更多信息,最好的起点是不稳定的书,其中列出了大多数。不稳定的书然后链接到跟踪问题,然后通常又链接到 RFC。使用这种组合源,然后您可以构建功能周围细节的图片。感谢您阅读我的第一篇博文😃。支持我的最佳方式是关注我的推特。我也在寻找工作机会,所以如果您想谈论这个,请与我们联系。