Reputation: 2481
I am having a Rust compiler problem here. This code was originally written in C and my job is to port it into Rust code. I am not modifying the algorithm or anything here, but the Rust compiler is stricter than the C compiler, and it marks down perfectly valid code.
error[E0381]: borrow of possibly-uninitialized variable: `output_file_handler`
--> src/main.rs:318:9
|
318 | output_file_handler.write(b"Why won't it work?");
| ^^^^^^^^^^^^^^^^^^^ use of possibly-uninitialized `output_file_handler`
error: aborting due to previous error
This program that I'm writing uses the MPI library for Rust, but let's ignore that now because it is not a part of my problem.
The problem is, I have a mutable File
object declared at the top of the main()
function, being uninitialized at the start. Now because I am using MPI, this is a multi-process program. I am simplifying this a bit and removing unessential code to my problem.
my_rank
variable basically holds the "process ID" of the currently executing process, because the same code can be run by multiple processes. The process with my_rank == 0
is only responsible for printing output to a file, none of the other processes do this. In the process with my_rank == 0
, the output_file_handler
is "active", and in the other processes it stays uninitialized, it is simply never used.
Apart from the declaration, all and any accesses of output_file_handler
are in the process with my_rank == 0
, so it is always initialized when it is used. The compiler however, is too dumb to realize that and too strict so it penalizes perfectly good code!
First I have the declaration of the output_file_handler
. Then I have some code that starts up the MPI subsystem and "forks off" a bunch of processes, and assigns to each process it's respective rank. Then after that in my_rank == 0
, the output_file_handler
is initialized. Then later on there is a whole bunch of computationally intensive tasks, including message passing, that is shared for all processes. And then after that is done, the output_file_handler
should write the results of these computations into a file.
I can't move the initialization of the output_file_handler
below these computations into the places where the output is written directly, because if that output file could not be opened, then these intensive computations should not be started at all.
The output_file_handler
has to have scope in the entire main()
function, starting from the top of the function, because if I define it in one of the if my_rank == 0
blocks, then the output_file_handler
will go out of scope and get destroyed at the }
, and I want to use it later on in the future. That is why I have to put the output_file_handler
in the global scope, because there is no other way for me to get the File
object from the initialization, because each if { }
statement is self contained and the only way to get things out of them would be to use the wider scope at the next lower level. And I think that the actual file is closed when the File
object goes out of scope. I do not want that to happen.
Please excuse my lack of knowledge with Rust, but I have very little knowledge as of yet in that programming language, and my assigned job is to port this application from C to Rust. No, I cannot use any other libraries for multi-threading/multi-processing other than MPI.
fn main() {
// Because the mpirun executable itself generates standard error and standard output,
// I need to create a new File where the application's output should be written.
let mut output_file_handler : File;
// Similarly, this is the File where the application's timing information shoudl be written.
let mut timing_file_handler : File;
let universe = mpi::initialize().unwrap();
let world = universe.world();
let comm_size : i32 = world.size();
let my_rank : i32 = world.rank();
/* Some more code here... */
// Open up the output file.
let output_filename : String = "output".to_string();
// The create() static method opens a file in write-only mode.
// If the file already existed, the old content is destroyed. Otherwise, a new file is created.
let create_file_handler = File::create(&output_filename);
// If File::create() succeeds, it returns an instance of Ok() that contains a file handler.
// If File::create() fails, it returns an instance of Err() that contains more information about the kind of error that happened.
output_file_handler = match create_file_handler {
Ok(file) => {
let dummy : i8 = 5;
// Send the success message to all processes.
for i in 0..comm_size {
world.process_at_rank(i).send_with_tag::<i8>(&dummy, 100);
}
file // quantity without a ; at the end, is returned by the match statement
},
Err(error) => {
eprintln!("ERROR: Could not open the file {} for writing output.", output_filename);
process::abort();
}
};
// Make the other ranks either wait for a success message to be sent, or be killed upon failure.
// This is used as a synchronization device.
} else {
// recieve_with_tag::<i8>() returns a tuple `(i8, mpi::point_to_point::Status)`
// recieve_with_tag::<i8>() takes the tag as a parameter.
let __dummy = (world.process_at_rank(0).receive_with_tag::<i8>(100)).0;
}
/* After that is done, there is a lot of very intensive computations performed here.
This code is shared by all the processes. */
// Later, the final output is written to the file.
if my_rank == 0 {
output_file_handler.write(b"Why won't it work?");
}
}
Upvotes: 0
Views: 2056
Reputation: 8075
In your program there is a conditional clause where on leg sets output_file_handler
and one does not. Let uss start from a simplified example:
fn main() {
let mut output_file_handler: File;
if true {
let output_filename: String = "output".to_string();
let create_file_handler = File::create(&output_filename);
output_file_handler = match create_file_handler {
Ok(file) => file,
Err(error) => {
println!(
"ERROR: Could not open the file {} for writing output.",
output_filename
);
process::abort();
}
};
} else {
// output_file_handler is not set
}
output_file_handler.write(b"Why won't it work?");
}
Using a simple Option<File>
will let this look like:
fn main() {
let mut output_file_handler: Option<File>;
if true {
let output_filename: String = "output".to_string();
let create_file_handler = File::create(&output_filename);
output_file_handler = match create_file_handler {
Ok(file) => Some(file),
Err(error) => {
println!(
"ERROR: Could not open the file {} for writing output.",
output_filename
);
process::abort();
}
};
} else {
output_file_handler = None;
}
output_file_handler.unwrap().write(b"Why won't it work?");
}
If you can ensure that the else blocks sets all variables in a way that the later use of output_file_handler
is prevented there might be even a more sophisticated solution without Option
.
Upvotes: 5