Build failure when proc-macro dependency or build dependency uses mime-guess

40 views
Skip to first unread message

David Tolnay

unread,
Jun 10, 2026, 3:44:20 PMJun 10
to rust-embed-devs
The Embed macro generates a function call with a different number of arguments depending on whether the rust-embed-impl crate's "mime-guess" feature is enabled **in the host platform**, whereas the function being called takes a different number of arguments based on whether the rust-embed-utils crate's "mime-guess" feature is enabled **in the target platform**.

In general the host platform and target platform are going to resolve different features.

Correct behavior would be to arrange for the macro to expand based on the presence of "mime-guess" in the target platform only, without regard for what is enabled in the host platform. That means you must not use #[cfg(…)] anywhere in rust-embed-impl. It can still expand to different code by passing tokens to a macro_rules macro defined in rust-embed-utils, with different definitions controlled by #[cfg(…)] located in rust-embed-utils.

Cargo.toml:

    [package]
    name = "repro"
    edition = "2024"
    publish = false

    [dependencies]
    rust-embed = "8.11.0"

    [build-dependencies]
    rust-embed = { version = "8.11.0", features = ["mime-guess"] }

src/lib.rs:

    #[derive(rust_embed::Embed)]
    #[folder = "src/"]
    pub struct Asset;

build.rs:

    fn main() {}

`cargo check --release`:

    error[E0061]: this function takes 3 arguments but 4 arguments were supplied
      --> src/lib.rs:3:10
       |
     3 | #[derive(rust_embed::Embed)]
       |          ^^^^^^^^^^^^^^^^^ unexpected argument #4 of type `&'static str`
       |
    note: associated function defined here
      --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/rust-embed-utils-8.11.0/src/lib.rs:59:16
       |
    59 |   pub const fn __rust_embed_new(
       |                ^^^^^^^^^^^^^^^^
       = note: this error originates in the derive macro `rust_embed::Embed` (in Nightly builds, run with -Z macro-backtrace for more info)

Peter John

unread,
Jun 15, 2026, 12:26:11 PMJun 15
to rust-embed-devs
Hi David,
Fix for this, please review and let me know.

From 91d0d219557c469fb2cbd65a3875b6ecd12cfa7d Mon Sep 17 00:00:00 2001
From: pyrossh <pyrossh>
Date: Mon, 15 Jun 2026 21:55:01 +0530
Subject: [PATCH] Fix mime guess host/target issue

---
impl/src/lib.rs | 15 +++++++--------
utils/src/lib.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+), 8 deletions(-)

diff --git a/impl/src/lib.rs b/impl/src/lib.rs
index 927e3ea..861de76 100644
--- a/impl/src/lib.rs
+++ b/impl/src/lib.rs
@@ -276,13 +276,12 @@ fn embed_file(
};
(last_modified, created)
};
- #[cfg(feature = "mime-guess")]
- let mimetype_tokens = {
- let mt = file.metadata.mimetype();
- quote! { , #mt }
- };
- #[cfg(not(feature = "mime-guess"))]
- let mimetype_tokens = TokenStream2::new();
+ // Always compute and emit the mimetype, without branching on this crate's own
+ // `mime-guess` feature. This crate is a proc-macro compiled for the host, so
+ // its features are resolved independently of the target the generated code is
+ // compiled into. The decision of whether to keep the mimetype is made
+ // target-side by the `__rust_embed_metadata!` macro in `rust-embed-utils`.
+ let mimetype = rust_embed_utils::_mimetype_of(Path::new(full_canonical_path));
let embedding_code = if metadata_only {
quote! {
@@ -312,7 +311,7 @@ fn embed_file(
#crate_path::EmbeddedFile {
data: ::std::borrow::Cow::Borrowed(&BYTES),
- metadata: #crate_path::Metadata::__rust_embed_new([#(#hash),*], #last_modified, #created #mimetype_tokens)
+ metadata: #crate_path::utils::__rust_embed_metadata!([#(#hash),*], #last_modified, #created, #mimetype)
}
}
})
diff --git a/utils/src/lib.rs b/utils/src/lib.rs
index 2f3fefd..e638676 100644
--- a/utils/src/lib.rs
+++ b/utils/src/lib.rs
@@ -54,6 +54,54 @@ pub struct Metadata {
mimetype: Cow<'static, str>,
}
+/// Constructs a [`Metadata`] from tokens emitted by the derive macro.
+///
+/// The derive macro (`rust-embed-impl`) is a proc-macro and is therefore always
+/// compiled for the *host*, where its `mime-guess` feature is resolved
+/// independently of the *target* that the generated code is compiled into. It
+/// must not branch on its own `#[cfg(feature = "mime-guess")]`, since that
+/// reflects the host rather than the target. Instead it unconditionally emits
+/// all four arguments (including a mimetype string) and defers to this macro,
+/// which *is* compiled for the target and so discards the mimetype when
+/// `mime-guess` is disabled there.
+#[cfg(feature = "mime-guess")]
+#[doc(hidden)]
+#[macro_export]
+macro_rules! __rust_embed_metadata {
+ ($hash:expr, $last_modified:expr, $created:expr, $mimetype:expr $(,)?) => {
+ $crate::Metadata::__rust_embed_new($hash, $last_modified, $created, $mimetype)
+ };
+}
+
+/// See the `mime-guess` variant above. This variant drops the mimetype token.
+#[cfg(not(feature = "mime-guess"))]
+#[doc(hidden)]
+#[macro_export]
+macro_rules! __rust_embed_metadata {
+ ($hash:expr, $last_modified:expr, $created:expr, $mimetype:expr $(,)?) => {
+ $crate::Metadata::__rust_embed_new($hash, $last_modified, $created)
+ };
+}
+
+/// Computes the mimetype of a file at build time.
+///
+/// This is always available (regardless of the `mime-guess` feature) so that
+/// the derive macro can unconditionally produce a mimetype token without using
+/// `#[cfg(…)]`. The token is only ever used by [`__rust_embed_metadata`] when
+/// the target enables `mime-guess`; otherwise the empty string returned here is
+/// discarded by the macro.
+#[doc(hidden)]
+pub fn _mimetype_of(_path: &Path) -> String {
+ #[cfg(feature = "mime-guess")]
+ {
+ mime_guess::from_path(_path).first_or_octet_stream().to_string()
+ }
+ #[cfg(not(feature = "mime-guess"))]
+ {
+ String::new()
+ }
+}
+
impl Metadata {
#[doc(hidden)]
pub const fn __rust_embed_new(
--
2.54.0


David Tolnay

unread,
Jun 15, 2026, 11:53:56 PMJun 15
to rust-embed-devs
The new `__rust_embed_metadata` is on the right track but `_mimetype_of` is not. When cross-compiling, rust-embed-utils is built twice: once because rust-embed-impl depends on rust-embed-utils built for the host, and rust-embed depends on rust-embed-utils built for the target. On the host, the build of rust-embed-utils will have "mime-guess" enabled or disabled based on the host's feature resolution. With your patch it's possible that "mime-guess" is enabled in the target platform and not the host platform (reverse of my repro above) and `_mimetype_of` will evaluate to empty string despite the target wanting an actual mimetype.

Rust-embed-impl needs to generate output tokens that are independent of "mime-guess" in the host platform.

One correct way would be to delete $mimetype from `__rust_embed_metadata` and compute the mimetype using a proc macro call from within the target's cfg(feature="mime-guess") version of the macro. `$crate::Metadata::__rust_embed_new($hash, $last_modified, $created, $crate_path::__mimetype_of!($full_canonical_path))`

Alternatively change `__rust_embed_metadata` into a proc macro (more specifically a cfg-specific re-export of a proc macro, see below) and move most of what `embed_file` currently does into it.
    // rust-embed::utils
    #[cfg(feature = "mime-guess")]
    pub use rust_embed_impl::__rust_embed_metadata_with_mimetype as __rust_embed_metadata;
    #[cfg(not(feature = "mime-guess"))]
    pub use rust_embed_impl::__rust_embed_metadata_without_mimetype as __rust_embed_metadata;

A third option, similar to the second option but for all of derive(Embed), not just embed_file: rust_embed::Embed would be a re-export of either rust_embed_impl::EmbedWithMimetypes or EmbedWithoutMimetypes according to rust_embed's "mime-guess" feature on the target as above.

Thanks for making progress on this!

David

Peter John

unread,
Jun 27, 2026, 8:47:16 AM (9 days ago) Jun 27
to rust-embed-devs
Hi David,

Please review this code seems like it fixes the problem.

From 6679d58e170171a47de746c0feea6d665e7ca89d Mon Sep 17 00:00:00 2001
From: pyrossh <pyrossh>
Date: Sat, 27 Jun 2026 18:15:10 +0530
Subject: [PATCH] Fix rust-embed-utils is built twice when cross-compiling

---
Cargo.toml | 2 +-
impl/Cargo.toml | 7 ++++++-
impl/src/lib.rs | 32 ++++++++++++++++++++++++--------
utils/src/lib.rs | 31 +++++++------------------------
4 files changed, 38 insertions(+), 34 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 5c153ff..209f8b8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -93,7 +93,7 @@ sha2 = "0.10"
debug-embed = ["rust-embed-impl/debug-embed", "rust-embed-utils/debug-embed"]
interpolate-folder-path = ["rust-embed-impl/interpolate-folder-path"]
compression = ["rust-embed-impl/compression", "include-flate"]
-mime-guess = ["rust-embed-impl/mime-guess", "rust-embed-utils/mime-guess"]
+mime-guess = ["rust-embed-utils/mime-guess"]
include-exclude = [
"rust-embed-impl/include-exclude",
"rust-embed-utils/include-exclude",
diff --git a/impl/Cargo.toml b/impl/Cargo.toml
index 3dc77e4..6708cc7 100644
--- a/impl/Cargo.toml
+++ b/impl/Cargo.toml
@@ -17,6 +17,12 @@ proc-macro = true
[dependencies]
rust-embed-utils = { version = "8.11.0", path = "../utils" }
+# Unconditional (host-only, this is a proc-macro crate) so that the `__mimetype_of`
+# proc macro can always compute a mimetype regardless of how the `mime-guess`
+# feature resolves for the host. Whether the result is actually used is decided
+# target-side by the `__rust_embed_metadata!` macro in `rust-embed-utils`.
+mime_guess = "2.0.4"
+
syn = { version = "2", default-features = false, features = [
"derive",
"parsing",
@@ -35,6 +41,5 @@ optional = true
debug-embed = []
interpolate-folder-path = ["shellexpand"]
compression = []
-mime-guess = ["rust-embed-utils/mime-guess"]
include-exclude = ["rust-embed-utils/include-exclude"]
deterministic-timestamps = []
diff --git a/impl/src/lib.rs b/impl/src/lib.rs
index 861de76..9e8ca7c 100644
--- a/impl/src/lib.rs
+++ b/impl/src/lib.rs
@@ -276,13 +276,6 @@ fn embed_file(
};
(last_modified, created)
};
- // Always compute and emit the mimetype, without branching on this crate's own
- // `mime-guess` feature. This crate is a proc-macro compiled for the host, so
- // its features are resolved independently of the target the generated code is
- // compiled into. The decision of whether to keep the mimetype is made
- // target-side by the `__rust_embed_metadata!` macro in `rust-embed-utils`.
- let mimetype = rust_embed_utils::_mimetype_of(Path::new(full_canonical_path));
-
let embedding_code = if metadata_only {
quote! {
const BYTES: &'static [u8] = &[];
@@ -311,7 +304,12 @@ fn embed_file(
#crate_path::EmbeddedFile {
data: ::std::borrow::Cow::Borrowed(&BYTES),
- metadata: #crate_path::utils::__rust_embed_metadata!([#(#hash),*], #last_modified, #created, #mimetype)
+ // The mimetype is computed by a proc macro (so it does not depend
+ // on the host's `mime-guess` feature) and passed as the final
+ // argument. `__rust_embed_metadata!` is selected target-side: when
+ // the target lacks `mime-guess` it drops this argument without ever
+ // expanding the `__mimetype_of!` call.
+ metadata: #crate_path::utils::__rust_embed_metadata!([#(#hash),*], #last_modified, #created, #crate_path::__mimetype_of!(#full_canonical_path))
}
}
})
@@ -449,3 +447,21 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
Err(e) => e.to_compile_error().into(),
}
}
+
+/// Expands to a string literal containing the mimetype of the file at the given
+/// path, guessed from its extension.
+///
+/// This lives in the proc-macro crate (which depends on `mime_guess`
+/// unconditionally) rather than being computed inside the derive's host-side
+/// logic: the derive must emit tokens that do not depend on the *host*'s
+/// `mime-guess` feature, and this proc macro is invoked only from the
+/// target-side `__rust_embed_metadata!` macro. When the target has `mime-guess`
+/// disabled, the invocation generated by the derive is discarded by that macro
+/// without ever being expanded.
+#[doc(hidden)]
+#[proc_macro]
+pub fn __mimetype_of(input: TokenStream) -> TokenStream {
+ let path = parse_macro_input!(input as syn::LitStr).value();
+ let mimetype = mime_guess::from_path(&path).first_or_octet_stream().to_string();
+ quote! { #mimetype }.into()
+}
diff --git a/utils/src/lib.rs b/utils/src/lib.rs
index e638676..217fbb9 100644
--- a/utils/src/lib.rs
+++ b/utils/src/lib.rs
@@ -60,10 +60,10 @@ pub struct Metadata {
/// compiled for the *host*, where its `mime-guess` feature is resolved
/// independently of the *target* that the generated code is compiled into. It
/// must not branch on its own `#[cfg(feature = "mime-guess")]`, since that
-/// reflects the host rather than the target. Instead it unconditionally emits
-/// all four arguments (including a mimetype string) and defers to this macro,
-/// which *is* compiled for the target and so discards the mimetype when
-/// `mime-guess` is disabled there.
+/// reflects the host rather than the target. Instead it unconditionally emits a
+/// `__mimetype_of!(...)` call as the final argument and defers to this macro,
+/// which *is* compiled for the target. This variant (target has `mime-guess`)
+/// forwards the argument, expanding the proc macro to the real mimetype.
#[cfg(feature = "mime-guess")]
#[doc(hidden)]
#[macro_export]
@@ -73,7 +73,9 @@ macro_rules! __rust_embed_metadata {
};
}
-/// See the `mime-guess` variant above. This variant drops the mimetype token.
+/// See the `mime-guess` variant above. This variant (target lacks `mime-guess`)
+/// drops the final argument, so the `__mimetype_of!` call it contains is never
+/// expanded.
#[cfg(not(feature = "mime-guess"))]
#[doc(hidden)]
#[macro_export]
@@ -83,25 +85,6 @@ macro_rules! __rust_embed_metadata {
};
}
-/// Computes the mimetype of a file at build time.
-///
-/// This is always available (regardless of the `mime-guess` feature) so that
-/// the derive macro can unconditionally produce a mimetype token without using
-/// `#[cfg(…)]`. The token is only ever used by [`__rust_embed_metadata`] when
-/// the target enables `mime-guess`; otherwise the empty string returned here is
-/// discarded by the macro.
-#[doc(hidden)]
-pub fn _mimetype_of(_path: &Path) -> String {
- #[cfg(feature = "mime-guess")]
- {
- mime_guess::from_path(_path).first_or_octet_stream().to_string()
- }
- #[cfg(not(feature = "mime-guess"))]
- {
- String::new()
- }
-}
-
impl Metadata {
#[doc(hidden)]
pub const fn __rust_embed_new(
--
2.54.0


Reply all
Reply to author
Forward
0 new messages