After reading through some of these posts, I ended up using the following pattern without any crates. This is not my actual code, but just an example of the pattern I used.
#[derive(Default)]
struct Parsed<'a> {
words: Vec<&'a str>,
}
impl<'a> Parsed<'a> {
pub fn clear(&mut self) {
self.words.clear();
}
}
#[derive(Default)]
struct Loader {
raw_text: String,
parsed: Parsed<'static>
}
fn load_impl<'a> (input: &'a str, words: &mut Vec<&'a str>) -> Result<(), Error> {
// Parse the 'input' and populate 'words'.
}
impl Loader {
pub fn load<'a>(&'a mut self, filepath: &Path) -> Result<&'a Parsed<'a>, Error> {
self.raw_text.clear();
File::open(filepath)
.map_err(|_| Error::CannotReadFile(filepath.to_path_buf()))?
.read_to_string(&mut self.raw_text)
.map_err(|_| Error::CannotReadFile(filepath.to_path_buf()))?;
self.parsed.clear();
let borrowed = unsafe {
std::mem::transmute::<&'a mut Parsed<'static>, &'a mut Parsed<'a>>(&mut self.parsed)
};
load_impl(self.raw_text.trim(), &mut borrowed.words)?;
Ok(borrowed)
}
}
I am thinking this is safe, because until the returned reference to Parsed
is dropped, the borrow checker won't allow the Loader to be borrowed again. I tested this pattern. It works and I am predictably seeing performance improvements because both raw_text
and parsed
are reused. But I rarely write unsafe Rust, and never used std::mem::transmute
before so I am not sure. Is this really safe? Or am I introducing some weird UB? Thanks again!