Temporary files
NamedTempFile
solved our issues
in the previous exercise. But how does it work?
Temporary file directory
Most operating systems provide a temporary file directory.
You can retrieve the path to the temporary directory
using std::env::temp_dir
.
Files created in that directory will be automatically deleted at a later time.
When are temporary files deleted?
When using NamedTempFile
, there are two deletion mechanisms at play:
NamedTempFile
will delete the file when it is dropped.
This is robust in the face of panics (if they don't abort!) and is the main mechanismtempfile
relies on.- If destructor-based deletion fails, the OS will eventually delete the file since it's in the temporary directory.
The latter mechanism is not guaranteed to run at any specific time, therefore NamedTempFile
tries to generate
a unique filename to minimise the risk of collision with a leaked file.
Security
There are a fair number of OS-specific details to take into account when working with temporary files, but
tempfile
takes care of all of them for us.
In particular, there are
some security considerations
when working with NamedTempFile
. When it comes to usage in test suites, you're in the clear.
tempfile()
tempfile
also provides a tempfile()
function
that returns a special kind of File
: the OS is guaranteed to delete the file when the last handle to it is dropped.
There's a caveat though: you can't access the path to the file.
Refactoring
We could choose to refactor get_cli_path
to make tempfile()
viable for our testing needs.
#![allow(unused)] fn main() { use std::io::BufRead; use std::path::PathBuf; fn get_cli_path<R>(config: R) -> PathBuf where R: BufRead, { let path = config .lines() .next() .expect("The config is empty") .expect("First line is not valid UTF-8"); PathBuf::from(path) } }
We are no longer performing any filesystem operation in get_cli_path
: the configuration "source" is now abstracted
behind the BufRead
trait.
We could now use get_cli_path
to process a File
, a String
(using std::io::Cursor
), or other types that implement
BufRead
.
This is a valuable refactoring to have in your toolkit, but it's not a panacea.
You'll still need to deal with that filesystem access at some point. You could move it to the binary entrypoint, but
does it really count as "thin" and "boring"?
You'll probably have logic to handle failures, with different code paths depending on the error. You should test that!
Evaluate on a case-by-case basis whether it's worth it to refactor your code to make it easier to test with
something like tempfile()
.
Exercise
The exercise for this section is located in 05_filesystem_isolation/02_tempfile