Is it possible to mark a potentially uninitialized

2020-04-18 05:33发布

问题:

Is it possible to explain to the compiler that the v variable is good at the line marked as 1 without using unsafe or code that may call panic!?

#[derive(PartialEq, Debug)]
enum Enum {
    V1,
    V2,
    V3,
}

fn main() {
    let e = Enum::V1;

    let mut v: i32;

    if e == Enum::V1 || e == Enum::V2 {
        v = 17; //some complex, costy expression
    }
    match e {
        Enum::V1 | Enum::V2 => {
            println!("Results: {}", v); //1
        }
        _ => {}
    }
}

The compiler reports:

error[E0381]: use of possibly uninitialized variable: `v`
  --> src/main.rs:18:37
   |
18 |             println!("Results: {}", v); //1
   |                                     ^ use of possibly uninitialized `v`

I have a complex expression to initialize v in my real code instead of 17, the type of v does not implement Default, and I only need v for the Enum::V1 and Enum::V2 cases.

In the real code I have separate branches for Enum::V1 and Enum::V2, and I can move v initialization there.

I want to make my code more clear and I don't want to use potentially bad things like unsafe or Option::unwrap

回答1:

The simple method is to initialize v; it's a single word and the compiler can probably optimize it away if it is unnecessary. In this particular case, you could even move both the declaration and initialization into the inner scope of the match, because it isn't used anywhere else.

The cleaner thing to do is to make the invalid case unrepresentable. Here v only really exists in the cases V1 or V2, so if we join the two we don't have a name for a possibly uninitialized value.

#[derive(PartialEq, Debug)]
enum Enum {
    V1 { v: i32 },
    V2 { v: i32 },
    V3
}

fn main() {
    let mut e = Enum::V1 { v: 17 };

    match e {
        Enum::V1 {v} | Enum::V2 {v} => {
            println!("Results: {}", v);//1
        }
        _ => {}
    }
}

This is how types like Result and Option function.



回答2:

In safe Rust, you cannot. In your code, v is only guaranteed to be initialized under the conditional branch e == Enum::V1 || e == Enum::V2, and yet v was declared in a wider scope. Note that this is not a limitation, but a hint from the compiler that the design of the program should be reconsidered.

In this case, I would delegate the calculation of v to a function and only have v in the necessary case blocks.

fn calculate_v(e: Enum) -> i32 { ... }

let e = Enum::V1;

match e {
    Enum::V1 => {
        let v = calculate_v(e);
        // use v
        println!("Results: {}", v);//1
    }
    Enum::V2 => {
        let v = calculate_v(e);
        println!("Results: {}", v);//1
    }
    _ => {}
}


标签: rust