Used binding is possibly-uninitialized

Hello world. I'm getting the error used binding is possibly-uninitialized. Because the initialization of the variable is done inside condition blocks. But all the cases in which there is no initialization, are also cases where the program display an error message and exits, So there is nothing wrong. But how to tell the compiler that I'm in control and doesn't have to care ?

Demonstrate this with some minimal example code, otherwise it is difficult to give you advice.

2 Likes

You might be using a function to do the “display an error message and exit” action. If you do that, make sure to annotate such a function with a -> ! “this function never returns” return type.

// fails

fn display_error_and_exit() {
    panic!("bye!");
}

fn demo(x: bool) {
    let binding;
    if x {
        binding = 42;
    } else {
        display_error_and_exit();
    }
    let _useage = binding; // error[E0381]: used binding `binding` is possibly-uninitialized
}
// works

fn display_error_and_exit() -> ! {
    panic!("bye!");
}

fn demo(x: bool) {
    let binding;
    if x {
        binding = 42;
    } else {
        display_error_and_exit();
    }
    let _useage = binding;
}

If that’s not the problem here, then please give more information on what the code actually looks like :slight_smile:

1 Like
let slugified: String;

if args.contains("docname") {
    if !args.get("docname").is_a_good_docname() {
        eprint!("Bad Doc name");
        std::process::exit(1);
    }
    slugified = slugify(args.get("docname"));
}
else {
    let docname = ask_to_user("Choose a docname");
    if !docname.is_a_good_docname() {
        eprint!("Bad Doc name");
        std::process::exit(1);
    }
    slugified = slugify(docname);
}

I don't use panic! because according to what I read, it's for bug in my code, not for user errors. Is it right ?

The code you posed clearly initializes slugified in both branches, so where is the error coming from? Could you run cargo check and post the (relevant) full error message?

1 Like
error[E0381]: used binding `dest_parent` is possibly-uninitialized
   --> src/create.rs:220:28
    |
66  |     let dest_parent: PathBuf;
    |         ----------- binding declared here but left uninitialized
...
148 |             dest_parent = temp_parent.canonicalize().unwrap();
    |             ----------- binding initialized here in some conditions
...
220 |     println!("folder: {:#?}", dest_parent);
    |                               ^^^^^^^^^^^ `dest_parent` used here but it is possibly-uninitialized
    |
    = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0381`.
warning: `orixdb` (bin "orixdb") generated 8 warnings
error: could not compile `orixdb` due to previous error; 8 warnings emitted

I see… if it’s possible to just post the relevant code section between lines 66 and 220, I’ll gladly help spotting the reason why rustc doesn’t understand your logic, and suggest workarounds :slight_smile:

I'm going to commit and push so that you will see the full code

1 Like

As you can see, the code sample that I posted earlier was just a very very simplified version of the real code.

I see. The compiler cannot understand the connection between the bool dest_exists and whether dest_parent is initialized. There are two possible workaround.

First option: Combine dest_parent: bool and dest_parent: String into a single dest_parent_or_exists: Option<String>, and use dest_parent_or_exists.is_none() instead of dest_exists. (Feel free to re-name it to something shorter if you like!)

I.e.

dest_exists = true;

becomes

dest_parent_or_exists = None;

and

dest_parent = …;
dest_exists = false;

becomes

dest_parent_or_exists = Some(…);

and

if !dest_exists { println!("folder: {:#?}", dest_parent) };

becomes

if let Some(parent) = &dest_parent_or_exists { println!("folder: {:#?}", parent) };

Second option, if you keep the logic as-is, you could still use an Option for dest_parent: either initialized with None on declaration, and made mutable, or initialized with None in every path; and then unwrap in the !dest_exist path. I find the first approach to be preferrable though :slight_smile: Less potential for panicking logic errors.

2 Likes

Thank you :heart:

I know this question is off-topic, but globally how is the code that I'm writing ? (I'm new to rust)

I don’t have time for much review right now. Regarding the exit usage… in principle, it has the downside that destructors for local variables won’t be run anymore before exiting, on the other hand that might not be necessary. Nonetheless, you can alternatively change the signature of (both the real and all the sub-command) main function to be (…) -> std::process::ExitCode, and return ExitCode::FAILURE or ExitCode::SUCCESS as appropiate.

Typical alternative error handling approaches would/could include that the error printing, too, would be moved up to the top level; to make life easy, it can be useful to use a catch-all error type as offered by the crates anyhow or eyre (I haven’t used eyre myself yet, but heard people say it creates nice looking error messages), then the return type of main would be -> Result<(), anyhow::Error> or -> Result<(), eyre::Report>.

1 Like

Glancing at the code a bit, one small thing I noticed:

if matches.contains_id("folder") {
		let folder = matches.get_one::<String>("folder")
			.unwrap()
		;
		…
} else {
		…
}

which can probably be improved to

if let Some(folder) = matches.get_one::<String>("folder") {
		…
} else {
		…
}
1 Like

Thanks for the review :+1:t3:. I'll correct all that.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.