Reputation: 36329
During a lengthy computation, I need to look up some data in a number of different files. I cannot know beforehand how many or which files exactly, but chances are high that each file is used many times (on the order of 100 million times).
In the first version, I opened the file (whose name is an intermediate result of the computation) each time for lookup.
In the second version, I have a HashMap<String, Box<File>>
where I remember already open files and open new ones lazily on demand.
I couldn't manage to handle the mutable stuff that arises from the need to have File
s to be mutable. I got something working, but it looks overly silly:
let path = format!("egtb/{}.egtb", self.signature());
let hentry = hash.get_mut(&self.signature());
let mut file = match hentry {
Some(f) => f,
None => {
let rfile = File::open(&path);
let wtf = Box::new(match rfile {
Err(ioe) => return Err(format!("could not open EGTB file {} ({})", path, ioe)),
Ok(opened) => opened,
});
hash.insert(self.signature(), wtf);
// the following won't work
// wtf
// &wtf
// &mut wtf
// So I came up with the following, but it doesn't feel right, does it?
hash.get_mut(&self.signature()).unwrap()
}
};
Is there a canonical way to get a mut File
from File::open()
or File::create()
? In the manuals, this is always done with:
let mut file = File:open("foo.txt")?;
This means my function would have to return Result<_, io::Error>
and I can't have that.
The problem seems to be that with the hash-lookup Some(f)
gives me a &mut File
but the Ok(f)
from File::open
gives me just a File
, and I don't know how to make a mutable reference from that, so that the match arm's types match. I have no clear idea why the version as above at least compiles, but I'd very much like to learn how to do that without getting the File
from the HashMap
again.
Upvotes: 0
Views: 78
Reputation: 154866
Attempts to use wtf
after it has been inserted into the hashmap fail to compile because the value was moved into the hashmap. Instead, you need to obtain the reference into the value stored in the hashmap. To do so without a second lookup, you can use the entry API:
let path = format!("egtb/{}.egtb", self.signature());
let mut file = match hash.entry(self.signature()) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => {
let rfile = File::open(&path)
.map_err(|_| format!("could not open EGTB file {} ({})", path, ioe))?;
e.insert(Box::new(rfile))
}
};
// `file` is `&mut File`, use it as needed
Note that map_err()
allows you to use ?
even when your function returns a Result
not immediately compatible with the one you have.
Also note that there is no reason to box the File
, a HashMap<String, File>
would work just as nicely.
Upvotes: 2