Help me see how these variables could be uninitialized

I've reduced my problem down to a minimal case on the Playground:

extern crate rand;

fn main() {
    let a: bool;
    let b: bool;
    
    let (dx, dy) = (rand::random::<i32>(), rand::random::<i32>());
    
    if dx < 0 && dy < 0 {
        a = true;
        b = true;
    } else if dx < 0 && dy >= 0 {
        a = true;
        b = false;
    } else if dx >= 0 && dy < 0 {
        a = false;
        b = true
    } else if dx >= 0 && dy >= 0 {
        a = false;
        b = false;
    }
    
    let test = (a, b);
    println!("{:?}", test);
}

I get use of possibly uninitialized variable errors for a and b, but I'm having trouble seeing a case in which those could remain uninitialized. What am I missing?

The compiler doesn't look at all the conditions and see that there is no possible other condition that could happen. You've covered all possibilities, but, the compiler isn't "smart" enough to know that. All it knows it that you have a series of "if" branches all of which could in theory evaluate to false in which case you would never set a and b with values. When you have exhaustive conditions like this, always make the last one an "else" instead of an "if". In other words, once you've eliminated everything else you care about, whatever is left should just be handled with "else", then, the compiler knows that no matter what a and b will be set.

3 Likes

Thanks for the quick response! I was worried that would be the case... not super convenient in the real code, but not a big deal.

You could also add else { unreachable!() }. LLVM's optimizer should be able to tell that this block is truly unreachable and just remove it.

5 Likes

This is the same in every language I'm aware of that cares whether or not you've initialized your variables, including C# and Java.

You could potentially express the same logic as an exhaustive match on a value of type (bool, bool), in which case any value initialized in all four branches would be considered initialized after the match-expression.

match (dx < 0, dy < 0) {
    (true, true) => {
        /* ... */
    }
    (true, false) => {
        /* ... */
    }
    (false, true) => {
        /* ... */
    }
    (false, false) => {
        /* ... */
    }
}
7 Likes

Lovely! This maintains my original logic (I only switched to if-else after this became a problem) and avoids compiler-pleasing clutter.

If that's what you need?

let ( a, b ) = ( dx < 0, dy < 0 );

Done.

I may have abstracted the initial code too much. This is a hopefully better abstraction of where I've ended up:

extern crate rand;

fn main() {
    let big_struct_proxy1: &str;
    let big_struct_proxy2: &str;

    // these are image width and height differences, simulated here
    let (dx, dy) = (rand::random::<i32>(), rand::random::<i32>());

    match (dx < 0, dy < 0) {
        (true, true) =>
        // image is too wide and too tall
        {
            big_struct_proxy1 = "Gets Initialized Appropriately";
            big_struct_proxy2 = "Gets Initialized Appropriately";
        }

        (true, false) =>
        // image is too wide
        {
            big_struct_proxy1 = "Gets Initialized Appropriately";
            big_struct_proxy2 = "Gets Initialized Appropriately";
        }
        (false, true) =>
        // image is too tall
        {
            big_struct_proxy1 = "Gets Initialized Appropriately";
            big_struct_proxy2 = "Gets Initialized Appropriately";
        }
        (false, false) =>
        // image is contained
        {
            big_struct_proxy1 = "Gets Initialized Appropriately";
            big_struct_proxy2 = "Gets Initialized Appropriately";
        }
    }

    let setup_for_next_step = (big_struct_proxy1, big_struct_proxy2);
    println!("{:?}", setup_for_next_step);
}

Thanks again, everyone!