我怎样才能满足 Rust 编译器的要求,即我的比赛臂的 u8 输入是断言/安全的?

Mic*_*ouw 3 rust

作为一名 Rust 初学者,正在研究 Exercism/Rust 上的第一个问题(https://exercism.org/tracks/rust/exercises/assemble-line),
我想知道是否可以将整数输入限制在一个范围内在编译时
能够拥有一组干净的match expression案例。

以下是我当前的实现production_rate_per_hour

pub fn production_rate_per_hour(mut speed: u8) -> f64 {
    speed = cmp::max(speed, 10);

    let cars_per_hour: u8 = 221;

    match speed {
      0 => 0.0,
      1 ..= 4 => (speed * cars_per_hour) as f64,
      5 ..= 8 => (speed * cars_per_hour) as f64 * 0.9,
      9 | 10 => (speed * cars_per_hour) as f64 * 0.77
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试编写一个方法,该方法接受名为的单个mutable u8参数speed,然后将其限制在范围内,0..=10如下所示:

speed = cmp::max(speed, 10);

然后我想匹配speed所有可能的情况,即0..=10. 但由于这是一个运行时检查,编译器看不到这一点,并告诉我也匹配整数值11和更高值:

Compiling assembly-line v0.1.0 (/Users/michahell/Exercism/rust/assembly-line)
error[E0004]: non-exhaustive patterns: `11_u8..=u8::MAX` not covered
  --> src/lib.rs:12:11
   |
12 |     match speed {
   |           ^^^^^ pattern `11_u8..=u8::MAX` not covered
   |
   = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
   = note: the matched value is of type `u8`
Run Code Online (Sandbox Code Playgroud)

我当然可以通过添加以下案例来解决这个问题:

// notify
_ => println!("11 or higher")
// or crash
_ => panic!("you've crashed the assembly line!");
// or do something like this:
_ => (cmp::max(speed, 10) * cars_per_hour) as f64 * 0.77;
Run Code Online (Sandbox Code Playgroud)

但是,我想知道是否可以在编译时限制输入范围,并具有“干净”的匹配表达式。

这可能吗?如果可以,怎么做?

GMa*_*ckG 7

目前还没有办法在类型系统中表达这一点。

我假设你的意思是最小值而不是最大值。典型的方法是:

pub fn production_rate_per_hour(mut speed: u8) -> f64 {
    speed = cmp::min(speed, 10);

    let cars_per_hour: u8 = 221;

    match speed {
      0 => 0.0,
      1 ..= 4 => (speed * cars_per_hour) as f64,
      5 ..= 8 => (speed * cars_per_hour) as f64 * 0.9,
      9 | 10 => (speed * cars_per_hour) as f64 * 0.77,
      _ => unreachable!(),
    }
}
Run Code Online (Sandbox Code Playgroud)

程序中的错误(例如,意外地将上限提高到 11)将导致恐慌。如果这对性能敏感,您可以使用 unsafe:

_ => unsafe { std::hint::unreachable_unchecked() },
Run Code Online (Sandbox Code Playgroud)

如果您声称该分支无法访问的说法是错误的,您会得到未定义的行为。

请注意,在许多情况下,编译器将能够证明无法访问的分支实际上是无法访问的,并完全忽略它。这对于模算术来说很常见:

例子

pub fn foo(v: u64) -> u8 {
    match v % 8 {
      0 => 0,
      1 ..= 4 => 1,
      5 ..= 7 => 2,
      _ => unreachable!(),
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,经过轻微重构后:

pub fn production_rate_per_hour(speed: u8) -> f64 {
    let speed = speed.min(10);
    let factor = match speed {
      0 => 0.0_f64,
      1 ..= 4 => 1.0,
      5 ..= 8 => 0.9,
      9.. => 0.77,
    };

    let cars_per_hour: u8 = 221;

    factor * (speed * cars_per_hour) as f64
}
Run Code Online (Sandbox Code Playgroud)

不存在需要不可达的情况。代价是你不再需要在比赛中非常明确地了解什么速度值是可以接受的。这种情况还是恐慌更好取决于您的具体情况。

  • @JohnKugelman 好吧,撇开愚蠢的错误不谈:https://rust.godbolt.org/z/Y3xsdnnsK。如果您删除任何特定的匹配臂(例如将其与另一个匹配臂合并),那么恐慌就会被消除,否则它似乎总是存在。经过一番挖掘后,可能会跟进一张 rustc 票证。 (3认同)