Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to mark a potentially uninitialized variable as good without using unsafe or panicking?

Tags:

rust

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

like image 745
user1244932 Avatar asked Sep 15 '25 15:09

user1244932


2 Answers

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.

like image 73
Yann Vernier Avatar answered Sep 18 '25 10:09

Yann Vernier


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
    }
    _ => {}
}
like image 45
E_net4 stands with Ukraine Avatar answered Sep 18 '25 08:09

E_net4 stands with Ukraine