Rust 1.45.0 发布

2020年7月16日 · Rust 发布团队

Rust 团队很高兴地宣布 Rust 的新版本 1.45.0 发布。Rust 是一种编程语言,它使每个人都能构建可靠且高效的软件。

如果您之前通过 rustup 安装了 Rust 版本,那么获取 Rust 1.45.0 非常简单,只需执行以下命令:

$ rustup update stable

如果您还没有安装 rustup,您可以从我们网站的相应页面获取 rustup,并查看 GitHub 上1.45.0 的详细发行说明

1.45.0 稳定版中的新功能

Rust 1.45.0 中有两个值得注意的重大更改:修复了整数和浮点数之间转换时长期存在的某些不健全性问题,以及稳定了使一个更流行的 Web 框架能够在稳定的 Rust 上运行所需的最后一个特性。

修复类型转换中的不健全性

Issue 10184 最初是在 2013 年 10 月提出的,早于 Rust 1.0 发布一年半。 您可能知道,rustc 使用 LLVM 作为编译器后端。 当您编写如下代码时

pub fn cast(x: f32) -> u8 {
    x as u8
}

Rust 1.44.0 及更早版本中的 Rust 编译器会生成如下所示的 LLVM-IR

define i8 @_ZN10playground4cast17h1bdf307357423fcfE(float %x) unnamed_addr #0 {
start:
  %0 = fptoui float %x to i8
  ret i8 %0
}

fptoui 实现了类型转换,它是“浮点数到无符号整数”的缩写。

但是这里有一个问题。来自 文档

“fptoui”指令将其浮点操作数转换为最接近的(向零舍入)无符号整数值。如果该值不能放入 ty2 中,则结果是一个毒值。

现在,除非您经常深入研究编译器,否则您可能不明白这意味着什么。它充满了术语,但有一个更简单的解释:如果您将一个很大的浮点数转换为一个很小的整数,您会得到未定义的行为。

这意味着例如,以下代码没有良好定义

fn cast(x: f32) -> u8 {
    x as u8
}

fn main() {
    let f = 300.0;

    let x = cast(f);

    println!("x: {}", x);
}

在我的机器上,Rust 1.44.0 会打印 “x: 0”。但它可以打印任何内容,或者做任何事情:这是未定义的行为。但是 unsafe 关键字没有在此代码块中使用。这就是我们所说的“健全性”错误,也就是说,这是编译器做错事情的错误。我们在我们的问题跟踪器上将这些错误标记为 I-unsound,并非常重视它们。

但是,这个 bug 花了很长时间才解决。原因是,对于正确的处理方式非常不清楚。

最后,决定这样做

  • as 将执行“饱和转换”。
  • 如果您想跳过检查,将添加一个新的 unsafe 转换。

这与数组访问非常相似,例如

  • array[i] 将检查以确保 array 至少有 i + 1 个元素。
  • 您可以使用 unsafe { array.get_unchecked(i) } 来跳过检查。

那么,什么是饱和转换呢?让我们看一个稍加修改的例子

fn cast(x: f32) -> u8 {
    x as u8
}

fn main() {
    let too_big = 300.0;
    let too_small = -100.0;
    let nan = f32::NAN;

    println!("too_big_casted = {}", cast(too_big));
    println!("too_small_casted = {}", cast(too_small));
    println!("not_a_number_casted = {}", cast(nan));
}

这将打印

too_big_casted = 255
too_small_casted = 0
not_a_number_casted = 0

也就是说,太大的数字会变成可能的最大值。太小的数字会产生可能的最小值(即零)。NaN 产生零。

以不安全的方式进行转换的新 API 是

let x: f32 = 1.0;
let y: u8 = unsafe { x.to_int_unchecked() };

但与往常一样,您应该只在最后手段时才使用此方法。就像数组访问一样,编译器通常可以优化掉检查,从而在编译器可以证明时,使安全版本和不安全版本等效。

稳定表达式、模式和语句中的类似函数的程序宏

Rust 1.30.0 中,我们稳定了“项目位置的类似函数的程序宏”。例如,gnome-class crate

Gnome-class 是 Rust 的程序宏。在宏内部,我们定义了一种尽可能像 Rust 的微型语言,它具有扩展功能,可让您定义 GObject 子类、它们的属性、信号、接口实现以及 GObject 的其余功能。目标是不需要您编写任何不安全的代码。

它看起来像这样

gobject_gen! {
    class MyClass: GObject {
        foo: Cell<i32>,
        bar: RefCell<String>,
    }

    impl MyClass {
        virtual fn my_virtual_method(&self, x: i32) {
            ... do something with x ...
        }
    }
}

“在项目位置” 位是一些术语,但基本上这意味着您只能在代码中的某些位置调用 gobject_gen!

Rust 1.45.0 增加了在三个新位置调用程序宏的功能

// imagine we have a procedural macro named "mac"

mac!(); // item position, this was what was stable before

// but these three are new:
fn main() {
  let expr = mac!(); // expression position

  match expr {
      mac!() => {} // pattern position
  }

  mac!(); // statement position
}

能够在更多的地方使用宏很有趣,但还有另一个原因让许多 Rustaceans 为这个特性等待了很久:Rocket。Rocket 于 2016 年 12 月首次发布,是 Rust 的一个流行的 Web 框架,通常被描述为 Rust 生态系统提供的最好的东西之一。 这是其即将发布的版本中的“hello world”示例

#[macro_use] extern crate rocket;

#[get("/<name>/<age>")]
fn hello(name: String, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}

#[launch]
fn rocket() -> rocket::Rocket {
    rocket::ignite().mount("/hello", routes![hello])
}

直到今天,Rocket 仍然依赖于仅限 nightly 版的功能来兑现其灵活性和人体工程学方面的承诺。事实上,正如在 项目主页上看到的那样,当前版本的 Rocket 中上面的相同示例需要使用 proc_macro_hygiene 特性进行编译。但是,正如您可能从该特性的名称中猜到的那样,它今天在稳定版中发布!这个 issue 跟踪了 Rocket 中仅限 nightly 版的功能的历史。现在,它们都已勾选完毕!

下一个版本的 Rocket 仍在开发中,但是发布后,许多人会非常高兴 :)

库更改

在 Rust 1.45.0 中,以下 API 已稳定:

此外,您可以char 与范围一起使用,以迭代码点

for ch in 'a'..='z' {
    print!("{}", ch);
}
println!();
// Prints "abcdefghijklmnopqrstuvwxyz"

有关更改的完整列表,请参阅完整发行说明

其他更改

Rust 1.45.0 版本中还有其他更改:查看 RustCargoClippy 中的更改。

1.45.0 的贡献者

很多人齐心协力创建了 Rust 1.45.0。没有你们所有人,我们不可能做到这一点。谢谢!