Making the trait RustEmbed dyn-compatible

13 views
Skip to first unread message

swift.b...@sitegui.dev

unread,
Sep 11, 2026, 5:49:16 AMSep 11
to rust-em...@googlegroups.com
Hello :)
Thanks for this nice crate!

To give more context for this discussion, I'm working on a personal library that implements a `WebRender` struct and I'd like it to compose an embedded directory.

My first approach failed because `RustEmbed` is not dyn-compatible:
```rust
struct WebRender {
    dir: Box<dyn RustEmbed>
}
```

fails with
```
error[E0038]: the trait `rust_embed::RustEmbed` is not dyn compatible
    |
236 |         dir: Box<dyn RustEmbed>
    |                  ^^^^^^^^^^^^^ `rust_embed::RustEmbed` is not dyn compatible
    |
note: for a trait to be dyn compatible it needs to allow building a vtable
    |
48 |   fn get(file_path: &str) -> Option<EmbeddedFile>;
    |      ^^^ the trait is not dyn compatible because associated function `get` has no `self` parameter
...
57 |   fn iter() -> impl Iterator<Item = std::borrow::Cow<'static, str>> + 'static;
    |      ^^^^ the trait is not dyn compatible because associated function `iter` has no `self` parameter
```

I don't want to use generics and static dispatch in this part of the code, so I've worked around this by introducing my own dyn-compatible trait and implementing it for all embedded types:

```rust
trait RustEmbedDynCompatible {
    fn get(&self, file_path: &str) -> Option<EmbeddedFile>;
    fn names(&self) -> Vec<Cow<'static, str>>;
}

impl<T: RustEmbed> RustEmbedDynCompatible for T {
    fn get(&self, file_path: &str) -> Option<EmbeddedFile> {
        T::get(file_path)
    }
    fn names(&self) -> Vec<Cow<'static, str>> {
        T::iter().collect()
    }
}
```

The methods take `&self` and the return of `iter()` changes to something concrete (I'm using a `Vec`). I've also renamed `iter()` to avoid confusion since it does not produce an iterator.

and it works :)

```rust
#[derive(Embed)]
#[folder = "."]
struct ExampleFolder;

let web_render = WebRender {
    dir: Box::new(ExampleFolder),
};
```

My question for the maintainers of this crate: does the use-case of dyn-compatible makes sense to integrate in the crate?

If yes, how to go about it? Some example solutions:
- change the current `RustEmbed` trait in a backwards-incompatible way to make it like the example `RustEmbedDynCompatible` above
- change the current `RustEmbed` to the two dyn-compatible equivalent methods and restrict the current two with `where Self: Sized`
- introduce a new trait like the example above

If no, maybe we can update the documentation of the trait to mention that the user can create their own trait like above to work around the restriction?

I'd love to contribute with the solution (code and docs), but the design requires some further feedback from the maintainers and community.

Best regards,
Gui

Peter John

unread,
Sep 16, 2026, 4:32:10 AM (11 days ago) Sep 16
to rust-embed-devs
Hi Gui,

Thanks. Looks good, if its a very specific use case that might be needed by a few users we can expose another Trait and document it, that would be the best approach I believe.

What do you think of something like this?

From 38f5767114fbb9830ff0628556de4d6f9ed6a741 Mon Sep 17 00:00:00 2001 From: Peter John <pyr...@Peters-MacBook-Pro.local> Date: Wed, 16 Sep 2026 14:00:10 +0530 Subject: [PATCH] Add DynRustEmbed for dyn-compatible trait objects RustEmbed's methods take no self parameter, so it can't be used as dyn RustEmbed. DynRustEmbed is a blanket-implemented, object-safe counterpart so an embedded directory can be stored behind Box<dyn DynRustEmbed> without requiring generics/static dispatch. Co-Authored-By: Claude Sonnet 5 <nor...@anthropic.com> --- changelog.md | 4 ++++ src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/lib.rs | 15 ++++++++++++++- 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 6cea03f..85b69b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,55 @@ pub trait RustEmbed { pub use RustEmbed as Embed; +/// A dyn-compatible ("object safe") counterpart to [`RustEmbed`]. +/// +/// `RustEmbed` cannot be used as `dyn RustEmbed` because its methods have no +/// `self` parameter (they are associated functions on a marker type). This +/// trait exists for callers who need to store or pass around an embedded +/// directory as a trait object, e.g. `Box<dyn DynRustEmbed>`. +/// +/// It is blanket-implemented for every type that implements `RustEmbed`, so +/// there is nothing to derive or implement manually: +/// +/// ``` +/// use rust_embed::{DynRustEmbed, Embed}; +/// +/// #[derive(Embed)] +/// #[folder = "examples/public/"] +/// struct Asset; +/// +/// fn main() { +/// let embed: Box<dyn DynRustEmbed> = Box::new(Asset); +/// let _ = embed.get("index.html"); +/// } +/// ``` +pub trait DynRustEmbed { + /// Get file compressed. See [`RustEmbed::compressed`]. + #[cfg(feature = "compression")] + fn compressed(&self, file_path: &str) -> Option<EmbeddedCompressedFile>; + + /// Get an embedded file and its metadata. See [`RustEmbed::get`]. + fn get(&self, file_path: &str) -> Option<EmbeddedFile>; + + /// Returns the file paths in the folder. See [`RustEmbed::iter`]. + fn names(&self) -> Vec<std::borrow::Cow<'static, str>>; +} + +impl<T: RustEmbed> DynRustEmbed for T { + #[cfg(feature = "compression")] + fn compressed(&self, file_path: &str) -> Option<EmbeddedCompressedFile> { + T::compressed(file_path) + } + + fn get(&self, file_path: &str) -> Option<EmbeddedFile> { + T::get(file_path) + } + + fn names(&self) -> Vec<std::borrow::Cow<'static, str>> { + T::iter().collect() + } +} + /// An iterator over filenames. /// /// This enum exists for optimization purposes, to avoid boxing the iterator in diff --git a/tests/lib.rs b/tests/lib.rs index 86fa2d1..9a36776 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,4 +1,4 @@ -use rust_embed::{Embed, RustEmbed}; +use rust_embed::{DynRustEmbed, Embed, RustEmbed}; /// Test doc comment #[derive(Embed)] @@ -70,3 +70,16 @@ fn iter_can_be_boxed() { assert_eq!(filenames.len(), 7); assert!(filenames.contains(&"index.html".to_string())); } + +/// Test that an embedded directory can be used as a trait object via +/// `DynRustEmbed`, since `RustEmbed` itself is not dyn-compatible. +#[test] +fn dyn_rust_embed_works() { + let boxed: Box<dyn DynRustEmbed> = Box::new(Asset); + assert!(boxed.get("index.html").is_some(), "index.html should exist"); + assert!(boxed.get("gg.html").is_none(), "gg.html should not exist"); + + let names = boxed.names(); + assert_eq!(names.len(), 7); + assert!(names.iter().any(|n| n == "index.html")); +} -- 2.54.0


Regards,
Peter
Reply all
Reply to author
Forward
0 new messages