Is this "possibly-uninitialized" compiler error a false alarm? [rustc 1.51.0]

Viewed 311

I was hit with the possibly-uninitialized variable error, although I'm convinced that this should never be the case.
(The rustc --version is rustc 1.51.0 (2fd73fabe 2021-03-23))

fn example(discriminant: bool) {
    let value;
    if discriminant {
        value = 0;
    }

    // more code [...]

    if discriminant {
        println!("{}", value);
    }
}

Here's the error message:

    error[E0381]: borrow of possibly-uninitialized variable: `value`
      --> src/raytracing/intersect.rs:8:24
       |
    10 |         println!("{}", value);
       |                        ^^^^^ use of possibly-uninitialized `value`

Reasoning

I did expect the example to compile (even though seperating the if-blocks as shown isn't "good style" IMHO):

  • As discriminant is immutable, I expect the second if block to never execute without the first.
  • Therefore, the variable should always be defined at the time of borrow in println!().

Possible mitigation

The error can be 'silenced' by initializing value with a temporary value.
I strongly disagree with this approach because

  • a temporary value could be wrong in a mathematical sense due to it being impossible to compute in other code paths.
  • Also, this could hide the fact that the value isn't properly defined during development, limiting the compiler's abilities to signal errors.

Question

Can someone clarify if this case is intended behaviour or maybe a compiler bug?

1 Answers

You're not the first person to see this behavior, and generally I would not consider it a bug. In your case, the condition is very simple and obvious, and it's easy for you and me to reason about this condition. However, in general, the condition does not need to be obvious and, for example, the second case could have additional conditions that are hard to reason about, even though the code is correct.

Moreover, the kind of analysis you want the compiler to do (determine which code is reachable based on conditions) is usually only done when the compiler is optimizing. Therefore, even if the compiler had support for this kind of analysis, it probably wouldn't work in debug mode. It also doesn't work in all cases even in the best optimizing compilers and static analysis tools.

If you have a philosophical objection to initializing a dummy value in this case, you can use an Option:

fn example(discriminant: bool) {
    let value = if discriminant {
        Some(0)
    } else {
        None
    };

    // more code [...]

    if discriminant {
        println!("{}", value.unwrap());
    }
}

In this case, your value is always initialized, and in the second portion, you're asserting that it contains a suitable non-None value. If you're in release mode, the compiler may be able to determine that your code is correct and optimize it to omit the Option (or it may not).

Related