Close file before drop

I have a struct like this:

struct Foo {
    file: File,
    dir_path: &'static Path,
}

when create a Foo, it first create the dir_path directory, then create a file in it.

What I want is to delete this directory when Foo is dropped, so I wirte this:

impl Drop for Foo {
    fn drop(&mut self) {
        std::fs::remove_dir(self.dir_path);
    }
}

but this may have problems:
the drop of Foo is called before the drop of file, so when the directory is deleted, the file is not closed, that could be a problem.

But I didn't find how to manually close a file in rust (although there are crates to do this, let say we don't use them).

What I do now is make file a MaualluDrop, and drop them manually before delete folder:

struct Foo {
    file: ManuallyDrop<File>,
    dir_path: &'static Path,
}

impl Drop for Foo {
    fn drop(&mut self) {
        let file = unsafe { ManuallyDrop::take(&mut self.file) };
        drop(file);
        std::fs::remove_dir(self.dir_path);
    }
}

I wondered if there's a better way, I'm trying to avoid using unsafe codes as much as possible.

To avoid unsafe, there might be macros that safely encapsulate the unsafe use-case of manually dropping struct fields. But I'm not aware of any crate offering such macros. A completely safe alternative to using ManuallyDrop would be to use Option instead. You'd need to unwrap a lot of times when using it though, but you can use Option::take during the Drop implementation.

In this case, you can also rely on the fact that Rust (guarantees to) drop struct fields in order of declaration. So you could just wrap the dir_path field into another newtype, and implement Drop for that one, so it deletes the dir, after the previous field containing the File handle has already been dropped:

struct Foo {
    file: File,
    dir: TemporaryDir, // field is *after* `file`!
}

struct TemporaryDir {
    path: &'static Path,
}

impl Drop for TemporaryDir {
    fn drop(&mut self) {
        // when this is called while dropping `Foo`,
        // the `File` is already dropped
        std::fs::remove_dir(self.path);
    }
}
3 Likes

That's really a brilliant idea.

This will not happen.

Files are reference counted object owned by the process[es] that opened it and file system tree leaves. Files still exist after all its file system paths are deleted if the process being terminated or close its handle. Actually it's how the tempfile crate guarantee the file will be removed even if the process aborts without running destructors.

Clicking through the tempfile crate, I arrive at tempdir which has a completely different resource-leaking story:

Resource Leaking

Various platform-specific conditions may cause TempDir to fail to delete the underlying directory. It’s important to ensure that handles (like File and ReadDir ) to files inside the directory are dropped before the TempDir goes out of scope. The TempDir destructor will silently ignore any errors in deleting the directory; to instead handle errors call TempDir::close() .

Note that if the program exits before the TempDir destructor is run, such as via std::process::exit() , by segfaulting, or by receiving a signal like SIGINT , then the temporary directory will not be deleted.

So it sounds like deleting a directory is problematic while File handles for files inside of the directory aren't dropped yet?

Except it doesn't work like that on Windows: The DeleteFile function marks a file for deletion on close. Therefore, the file deletion does not occur until the last handle to the file is closed.

On Windows you can remove file while it's opened and it would, correctly, disappear when you would close the open handle (that includes process crashing and that is how tempfile works there) but both things have to happen before you can remove the directory where file resides.

And Windows is kinda too important to ignore for many folks.

Yes. Windows-only issue. There are no such problems on POSIX platforms.

P.S. And there are also another problem, not Windows-specific, but read-world specific: some (most?) antiviruses keep file open for some time after you close your handle to analyze it, thus calling DeleteFile/RemoveDirectory in sequence is not enough to remove directory, you may need to wait for some time before you can actually successfully call RemoveDirectory… worse: they try to move such file into a different directory thus sometimes it may work… depending on system load and amount of free memory).

4 Likes