---
title: Asset source reference
description: The AssetSource trait and the three built-in layers — EmbeddedSource, DirectorySource, MemorySource — with their path rules and failure behaviour.
tags: [reference, api, sources]
---
# Asset source reference
A source is one layer of the overlay. `rtb-assets` ships three, and the
`AssetSource` trait lets you add your own. All four are re-exported at
the crate root from `rtb_assets::source`.
## The `AssetSource` trait
```rust
pub trait AssetSource: Send + Sync + 'static {
fn read(&self, path: &str) -> Option<Vec<u8>>;
fn list(&self, dir: &str) -> Vec<String>;
fn name(&self) -> &str;
}
```
| `read` | The bytes at `path` if this layer provides it, `None` otherwise. `None` must mean "not here", never "here but broken" — there is no error channel. |
| `list` | The immediate entries of `dir` — files and subdirectory names, with the `dir` prefix stripped. Empty when `dir` does not exist on this layer. |
| `name` | A diagnostic-only label, quoted in parse errors and printed by `Debug`. The empty string is acceptable for anonymous sources. |
The `Send + Sync + 'static` bound is not optional: `Assets` stores every
layer as `Arc<dyn AssetSource>` and is itself `Send + Sync`, so a source
holding a `Rc`, a `RefCell` or a borrowed lifetime will not compile.
Implementations are shared, never cloned — `Assets::clone` bumps a
refcount. A source that is expensive to construct is constructed once.
`read` returns an owned `Vec<u8>` rather than a borrow, so every call
allocates and copies. That is the price of letting a layer synthesise
bytes rather than only hand out ones it is already holding.
## What paths are accepted?
Asset paths are `/`-separated strings, and the same string is expected
to address the same asset on every layer. The rules differ subtly by
layer, which is worth knowing before you debug a lookup:
| `a/b.txt` | matches the key exactly | joined onto the root |
| `./a/b.txt` | **no match** — keys are compared literally | matches; `.` is a no-op component |
| `/a/b.txt` | no match | rejected — absolute |
| `a/../b.txt` | no match | rejected — `..` is refused even when it stays inside the root |
| `a\b.txt` | no match on Unix | on Unix, one filename containing a backslash |
Two consequences worth internalising:
- **Do not prefix asset paths with `./`.** It works on a directory layer
and fails on an embedded or in-memory one, which makes for a bug that
only appears once a user adds an override directory.
- **Do not build asset paths with `std::path::Path`.** On Windows it
will hand you backslashes, which no layer treats as separators for
key lookup.
Path rejection on `DirectorySource` is lexical and silent — see
[Why path traversal is rejected lexically](../explanation/path-safety.md).
## `EmbeddedSource<E>` — files compiled into the binary
```rust
pub struct EmbeddedSource<E: RustEmbed + Send + Sync + 'static> { /* private */ }
impl<E: RustEmbed + Send + Sync + 'static> EmbeddedSource<E> {
pub const fn new(name: &'static str) -> Self;
}
```
Adapts a [`rust-embed`](https://crates.io/crates/rust-embed) type. The
struct is zero-sized: all the bytes live in the static tables
`#[derive(RustEmbed)]` generates, so an embedded layer costs nothing at
runtime beyond the `Arc`.
`new` is `const` and takes a `&'static str`, so an embedded layer's
label cannot be computed at runtime — unlike the other two sources,
which take `impl Into<String>`. Register it through the builder:
```rust
#[derive(rust_embed::RustEmbed)]
#[folder = "assets/"]
struct Defaults;
let assets = Assets::builder().embedded::<Defaults>("defaults").build();
```
The `#[folder]` path is resolved relative to the consuming crate's
`Cargo.toml`. A missing folder is a compile error unless you add
`#[allow_missing = true]`, which yields an empty layer instead.
`read` delegates to `E::get`; `list` walks `E::iter()` and keeps the
first path segment after the prefix. Both see exactly what was embedded
at build time — [assets are never read from disk at runtime](crate-features.md#debug-embed-is-on-and-that-changes-rust-embeds-default-behaviour),
including in debug builds.
## `DirectorySource` — files on the host filesystem
```rust
pub struct DirectorySource { /* private */ }
impl DirectorySource {
pub fn new(root: impl Into<PathBuf>, name: impl Into<String>) -> Self;
}
```
Reads under a fixed root. Nothing about the root is validated at
construction: a root that does not exist, is not a directory, or is not
readable constructs fine and then behaves as an empty layer. That is
deliberate — an override directory the user has not created yet is the
normal case, not an error.
| `read` on a file that exists and is readable | `Some(bytes)` |
| `read` on a missing file, a directory, or a permission-denied file | `None` |
| `read` on a rejected path (`..`, absolute, Windows prefix) | `None`, no filesystem call made |
| `list("")` or `list(".")` | entries of the root |
| `list` on a missing or rejected directory | `[]` |
Because `None` covers both "absent" and "present but unreadable", a
file the process lacks permission to read is indistinguishable from one
that is not there, and a lower-priority layer will supply it instead.
If that matters to your tool, check the file yourself before building
the overlay.
Entries returned by `list` are sorted, and include subdirectory names
alongside filenames. Symlinks are followed by the underlying
`std::fs` calls; the lexical path check does not attempt to detect
them.
## `MemorySource` — a map of bytes
```rust
pub struct MemorySource { /* private */ }
impl MemorySource {
pub fn new(name: impl Into<String>, files: HashMap<String, Vec<u8>>) -> Self;
}
```
Backed by a `HashMap<String, Vec<u8>>`. Keys are matched literally, so
they should be written the way callers will ask for them:
`"templates/readme.md"`, not `"./templates/readme.md"` and not an
absolute path.
The map is moved in and is immutable thereafter — there is no way to
add, remove, or replace an entry on a built layer. Build a new `Assets`
instead; it is cheap.
Intended for tests and for scaffolders that generate content in memory
before writing it out. Nothing stops you using it in production, but a
`MemorySource` holds every byte resident for the life of the process.
## Writing your own source
Implement the trait and register it with `.source(Arc::new(MySource))`.
Three rules keep a custom layer well-behaved:
1. **Never panic**, and never block indefinitely. `read` is called on
whatever thread the caller is on, including inside a merge across
layers.
2. **Return `None` for anything you cannot serve**, including your own
internal errors. Callers cannot distinguish causes and a lower layer
is expected to get its turn.
3. **Make `list` agree with `read`.** A name returned by `list` should
be readable by `read` under `dir/name`, or be a directory. Listings
that promise files that cannot be read are the one failure the
overlay cannot paper over.
There is no way for a custom source to report an error, request a
retry, or veto lower layers. If your layer is a network fetch, cache it
behind the trait and decide there what a failure means.