Why an overlay filesystem¶
What problem does this crate actually solve?¶
A command-line tool ships with defaults: config templates, scaffolding files, help text, themes. Those need to be inside the binary, because a single downloadable executable that then hunts for a support directory is a worse tool.
Users then need to override them. Site administrators want a
/etc/mytool copy that applies to everyone; individuals want their own
under their config directory; the tool's own tests want neither, and
need fixtures that exist only in memory for the length of a test.
Without an abstraction, every place in the tool that reads an asset
grows the same ladder of if user_file_exists { … } else if system_file
_exists { … } else { embedded }. It gets written slightly differently
each time, the precedence drifts between call sites, and the test path
becomes a separate branch that production never runs.
rtb-assets collapses that to one decision made once, at startup,
where the layers are registered. Everywhere else, code asks for a path
and gets bytes.
Why is it read-only?¶
Because the alternative is a filesystem, and a filesystem is a much larger promise.
Writing into an overlay raises questions that have no single right answer: which layer receives the write, what happens when the winning layer is an embedded one that physically cannot be written, whether a write to a shadowed path is visible, what happens to the merged view of a YAML file when one contributor is edited. Every answer is defensible and none is obviously correct, so the crate declines the question.
Tools that need to write — a scaffolder emitting a project, a command saving user config — write through their own code, with their own notion of where output belongs. That is a different concern, made clearer by not living behind a read API.
The read-only contract also buys the cheap Clone. Layers sit behind
Arc and nothing mutates, so an Assets can be handed to every
command, thread and task without a lock.
Why not use an existing virtual filesystem crate?¶
The obvious candidate is the vfs crate's OverlayFS, and it was
considered and rejected on two counts.
OverlayFS is a two-layer construct: a writable upper and a read-only
lower. The requirement here is N layers — embedded, system, user, and
whatever else a tool wants to stack — with a precedence that is
declared once and holds for the life of the process.
More importantly, no general-purpose virtual filesystem knows what a YAML file is, and it should not. A filesystem's job is bytes. But the central use case for asset layering is configuration, and for configuration, last-wins shadowing is the wrong answer: a user who wants to change one colour in a theme should not have to copy the whole theme file to do it. That requirement — structured deep merge across the same stack of layers that serves bytes — is what makes this a small purpose-built trait rather than a wrapper over a general one.
The trade is interoperability. An Assets is not a VfsPath and
cannot be handed to code that expects one. If that ever bites, an
adapter can be added without changing the model; the model cannot
easily be added to an adapter.
Why a trait rather than a fixed set of three layers?¶
Three layers cover the tool cases: embedded, disk, memory. The trait exists so the crate does not have to grow a fourth, a fifth and a sixth for the cases it did not anticipate — an in-process archive, a remote-fetched theme pack, a layer synthesised from a database.
The trait is deliberately tiny — read, list, name — which keeps
the cost of implementing one low and the risk of a badly-behaved layer
small. There is no error channel, no capability negotiation and no
metadata, because every one of those would have to be understood by
Assets, and each would push behaviour into layers where callers
cannot see it.
Where this sits in the toolkit¶
rtb-assets is one crate of the phpboyscout Rust toolkit, extracted
from rust-tool-base so it can be used without adopting the framework.
Within the framework, the application context holds an Assets, the
CLI builder threads a user-constructed overlay through it, and the
interactive docs browser reads its markdown through list_dir and
open_text.
Outside it, the crate has no framework dependencies at all — the
dependency ban list refuses rust-tool-base by name — so it stands
alone in any binary that wants embedded defaults with overrides.