Rustc仅在分配溢出值时发出警告

时间:2016-02-03 16:29:09

标签: rust

我发现我认为这是一种非常奇怪的行为。当变量在运行时溢出时,Rustc会发生混乱;这对我来说很有意义。但是,它只会在编译时分配溢出值时发出警告。那应该是编译时错误吗?否则,两种行为似乎不一致。

我期待编译时错误:

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;

Playground 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

Playground 2

修改

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

1 个答案:

答案 0 :(得分:3)

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

    在你的第一个例子中
  • :你得到一个你忽略的编译时警告,因此编译器推断出你想要包装行为
  • 第二个示例中的
  • :出现运行时错误

Go示例类似于第一个Rust示例(除了Go,按设计,没有警告)。

在Rust中,下溢或溢出会导致计算机科学中的未指定值,可以是! bottom ,这是一个表示控件的特殊值流动分歧,通常意味着堕胎或例外。

此规范允许:

  • 检测调试模式以捕获它们发生的所有溢出
  • 没有检测 1 释放模式(并使用包装算法)

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

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

关于溢出检查的代价:当前的Rust / LLVM情况有助于调试,但尚未真正优化。因此,在此框架中,溢出检查成本。如果情况有所改善,那么有一天,即使在Release中,rustc也可能决定默认激活溢出检查。

Midori(使用与C#类似的语言开发的Microsoft实验操作系统)中,即使在发布版本中也启用了溢出检查:

  

在Midori中,我们默认使用溢出检查进行编译。这与库存C#不同,您必须为此行为显式传递/ checked标志。根据我们的经验,捕获和无意中出现的令人惊讶的溢出数量非常值得给您带来不便和成本。但这确实意味着我们的编译器需要非常善于理解如何消除不必要的编译器。

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

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

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

那么,还有什么成本?

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

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

尽管如此,除非代码是大量数字(例如科学模拟或图形),否则它可能会影响它。