pub trait Storage: ReadStorage {
// Required methods
fn write(
&self,
path: &Path,
contents: &[u8],
) -> impl Future<Output = Result<()>>;
fn create_dir_all(&self, path: &Path) -> impl Future<Output = Result<()>>;
fn remove_file(&self, path: &Path) -> impl Future<Output = Result<()>>;
fn remove_dir_all(&self, path: &Path) -> impl Future<Output = Result<()>>;
fn rename(&self, from: &Path, to: &Path) -> impl Future<Output = Result<()>>;
// Provided methods
fn capabilities(&self) -> Capabilities { ... }
fn sync(
&self,
path: &Path,
need: Durability,
) -> impl Future<Output = Result<()>> { ... }
fn write_atomic(
&self,
path: &Path,
contents: &[u8],
) -> impl Future<Output = Result<()>> { ... }
}Expand description
An async filesystem backend prov can drive — ReadStorage plus everything
that changes bytes on disk.
Each method mirrors the std::fs function of the same name. Backends
implement the write/mutate/durability surface here and the read surface on
ReadStorage.
Required Methods§
Sourcefn write(
&self,
path: &Path,
contents: &[u8],
) -> impl Future<Output = Result<()>>
fn write( &self, path: &Path, contents: &[u8], ) -> impl Future<Output = Result<()>>
Write a file, replacing it if it already exists. Mirrors
std::fs::write.
Sourcefn create_dir_all(&self, path: &Path) -> impl Future<Output = Result<()>>
fn create_dir_all(&self, path: &Path) -> impl Future<Output = Result<()>>
Create a directory and all missing parents. Mirrors
std::fs::create_dir_all.
Sourcefn remove_file(&self, path: &Path) -> impl Future<Output = Result<()>>
fn remove_file(&self, path: &Path) -> impl Future<Output = Result<()>>
Remove a regular file. Mirrors std::fs::remove_file.
Sourcefn remove_dir_all(&self, path: &Path) -> impl Future<Output = Result<()>>
fn remove_dir_all(&self, path: &Path) -> impl Future<Output = Result<()>>
Recursively remove a directory and its contents. Mirrors
std::fs::remove_dir_all.
Provided Methods§
Sourcefn capabilities(&self) -> Capabilities
fn capabilities(&self) -> Capabilities
What durability guarantees this backend can make. Defaults to
Capabilities::NONE — a backend promises a guarantee only by saying so,
so an adapter that forgets to override this degrades to the most defensive
path rather than to a false promise.
Sourcefn sync(
&self,
path: &Path,
need: Durability,
) -> impl Future<Output = Result<()>>
fn sync( &self, path: &Path, need: Durability, ) -> impl Future<Output = Result<()>>
Flush path — and nothing else — to the strength need asks for.
path names one object, and only that object is flushed. To make a
directory entry durable (the naming half of a create or a rename), sync
the directory itself: on a POSIX filesystem a directory is a thing that
can be opened and fsynced, and prov’s own
write_atomic does exactly that after its
rename. Folding the parent into every call instead would flush twice as
much as any single step needs, and would leave the caller unable to say
which of the two it actually meant.
need is the weakest guarantee that is still correct at the call site,
not a wish. Durability::Ordered asks only that everything written to
path before this call land before anything written after it — enough to
stop a rename overtaking the bytes it publishes, and on some platforms far
cheaper than the real thing. Durability::Durable asks that the bytes
survive power loss. A backend may always answer with something stronger
than it was asked for; it may never answer with something weaker.
The default is a no-op, which is the correct behavior for any backend
whose capabilities report
SyncGuarantee::None: it cannot make the promise, so it must not
pretend to. A backend that can flush must both override this and report
the strongest request it genuinely honors — the two always travel
together, and SyncGuarantee::satisfies is how a caller asks.
Sourcefn write_atomic(
&self,
path: &Path,
contents: &[u8],
) -> impl Future<Output = Result<()>>
fn write_atomic( &self, path: &Path, contents: &[u8], ) -> impl Future<Output = Result<()>>
Replace path’s contents with contents atomically and durably: no
observer — concurrent reader or post-crash survivor — ever sees a splice
of old and new bytes, and once this returns the new contents outlive a
power loss.
The default composes the primitives into the standard protocol, whenever
capabilities report atomic_replace:
- write the bytes to a temporary sibling;
syncthat siblingOrdered, so the rename cannot be reordered ahead of the bytes it publishes;renameit over the target — this is the atomic instant;syncthe target’s parent directoryDurable, which is what carries the rename itself through a power cut.
Two flushes, and each one is load-bearing. Neither of the two this protocol conspicuously does not do would buy anything. The bytes are never flushed under their final name, because a rename does not move an inode: the file the target now names is the very one step 2 flushed, and nothing has been written to it since. The sibling’s own directory entry is never flushed either, because nobody is owed a temporary that survives a crash — only the directory state after the rename is worth a barrier.
A backend that cannot rename atomically falls back to a plain durable
write, which is not crash-atomic; a caller that needs the guarantee
consults capabilities and leans on the journal instead of pretending
this call gave it. A backend with a better native path — a transactional
store — overrides this method wholesale.
The temporary is removed on any failure, so a torn attempt leaves the target exactly as it was and no litter behind. It is a dotted sibling in the target’s own directory, so the follow-up rename stays within one filesystem (a cross-device rename is neither atomic nor, often, even permitted).
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".