I have a C library linked statically that reads a file and fills global variables with information from the file. I have managed to interface with that library with FFI, so I can get contents of the file through one-shot call to the C library unsafe { read_file(file) }
. Then I can read external static global variables. There is also a C-call unsafe { free_file() }
that frees memory under the global pointers.
I am not competent enough, though, to determine how exactly I should build the Rust application that reads the file through the library and structures the data afterwards (because the output of the C code is just a bunch of globals), thereby I am asking the forum to help figuring out proper program design.
I have found the recommendation to extract external reading routines into a separate module that blocks vision of the FFI globals. I accepted that and also decided that I should have some struct
that absorbs all the globals (copies data from them into proper Rust data structures and then C-calls free_file()
). The overall data structure I have is like this (pseudocode):
struct FileInformation {
pub block1: Block1,
pub block2: struct Block2 {
pub sub1: Block2_Subblock1,
pub sub2: Block2_Subblock2
}
}
where Block
s contain properly (from my pov) structured data.
I assume that the overall constructor should look like this:
impl FileInformation {
fn new(file: Path) -> Self {
unsafe { read_file(file) };
let block1 = Block1::read();
let block2 = Block2::read();
unsafe { free_file() };
Self { block1, block2 }
}
}
where block reading functions know what globals they should access.
The problem is that there is nothing to stop Block1::read()
from accessing the globals, some of which are pointers, when these globals are not valid (either the file hasn't been read yet or the file was already freed). The function Block1::read()
may be called from any context, not only from FileInformation::new()
, because block
variables are public.
This 'reader' function cannot check itself whether the pointers what will be read are valid or invalid, they just will read and cause segfault if the calling context is wrong. How do I enforce both incapsulation and proper context checking (=> error handling) in this case?
The second question. Suppose that Block2
has a field sub2
that perfectly belongs there by its very meaning, but its data supplier is another source, not the file we are reading. The program can live fine with empty sub2
, but with restricted capabilities if sub2
's source is not available. Rust teaches us that this is the place for Option
:
struct FileInformation {
pub block1: Block1,
pub block2: struct Block2 {
pub sub1: Block2_Subblock1,
pub sub2: Option<Block2_Subblock2>
},
pub block3: Block3
}
although if the sub2
source becomes available when the main struct FileInformation
is already constructed, I do not want to re-read everything but rather want to take existing struct and add the missing information. If I do not know, however, when exactly that source will be available, I should either declare an instance of FileInformation
as mut
and carry that mut
around, which I do not want to, or I want to have partial inner mutability on the field FileInformation::block2::sub2
even if the whole FileInformation
is not itself mutable, or something else I am unaware of. What would you consider being "the right version" for this problem?