为什么 Rust 仅在赋值溢出时发出警告?

问题描述 投票:0回答:1

我发现我认为这是一种非常奇怪的行为。当变量在运行时溢出时,Rust 会出现恐慌;这对我来说很有意义。但是,只有在编译时分配溢出值时,它才会发出警告。这不应该是编译时错误吗?否则,两种行为就会显得不一致。

我预计会出现编译时错误:

fn main() {
    let b: i32 = 3_000_000_000;
    println!("{}", b);
}

产品:

<anon>:2:18: 2:31 warning: literal out of range for i32, #[warn(overflowing_literals)] on by default
<anon>:2     let b: i32 = 3_000_000_000;

游乐场1

这对我来说很有意义:

fn main() {
    let b: i32 = 30_000;
    let c: i32 = 100_000;
    let d = b * c;
    println!("{}", d);
}

产品:

thread '<main>' panicked at 'arithmetic operation overflowed', <anon>:4
playpen: application terminated with error code 101

游乐场2

编辑:

鉴于 FrancisGagné 的评论,以及我发现 Rust 实现了在操作期间检查溢出的运算符,例如 checked_mul,我发现需要自己实现溢出检查。这是有道理的,因为发布版本应该进行优化,并且不断检查溢出可能会变得昂贵。所以我不再看到“不一致”。然而,我仍然感到惊讶的是,分配一个会溢出的值不会导致编译时错误。在

golang
中,它会: Go Playground

rust integer-overflow
1个回答
3
投票

实际上,您的评论与您观察到的行为不一致:

  • 在第一个示例中:您收到编译时警告,您忽略该警告,因此编译器推断您想要包装行为
  • 在第二个示例中:您收到运行时错误

Go 示例与第一个 Rust 示例类似(除了 Go 在设计上没有警告)。


在 Rust 中,下溢或上溢会导致未指定值,在计算机科学中可以是

!
bottom,这是一个表示控制流发散的特殊值,通常意味着中止或异常。

该规范允许:

  • 检测调试模式以在发生溢出时捕获所有溢出
  • 不检测1发布模式(并在那里使用包装算术)

并且两种模式均符合规范。

1 默认情况下不进行检测,如果您选择的话,您可以在繁重的数字代码之外以相对适中的性能成本激活发布中的溢出检查,并使用一个简单的标志。


关于溢出检查的成本:目前Rust/LLVM的情况对调试有帮助,但还没有真正优化。因此,在这个框架中,溢出检查成本。如果情况有所改善,那么有一天,rustc 可能会决定默认激活溢出检查,即使在发布版本中也是如此。

Midori(用类似于C#的语言开发的微软实验性操作系统)中,即使在Release版本中也打开了溢出检查:

在 Midori 中,我们默认启用溢出检查进行编译。这与普通 C# 不同,在 C# 中,您必须显式传递 /checked 标志才能实现此行为。根据我们的经验,发现的意外溢出数量所带来的不便和成本是值得的。但这确实意味着我们的编译器需要真正擅长理解如何消除不必要的编译器。

显然,他们改进了编译器:

  • 它会推理变量的范围,并在可能的情况下静态地消除边界检查和溢出检查
  • 它将尽可能多地聚合检查(对多个可能溢出的操作进行一次检查)

后者只能在发布中完成(你会失去精度),但会减少分支数量。

那么,还剩下什么成本?

妨碍优化的潜在不同算术规则:

  • 在常规算术中,
    64 + x - 128
    可以优化为
    x - 64
    ;激活溢出检查后,编译器可能无法执行此优化
  • 如果编译器没有内置溢出检查向量,向量化也会受到阻碍
  • ...

不过,除非代码大量数字化(例如科学模拟或图形),否则它确实可能会产生影响。

© www.soinside.com 2019 - 2024. All rights reserved.