log icon indicating copy to clipboard operation
log copied to clipboard

non-primitive cast error when using multiple key-value pairs in debug! macro inside async_trait

Open xhermosilla opened this issue 2 months ago • 2 comments

Description

When using the debug! (or other log-level) macro with multiple key-value pairs, compilation fails with the following error:

error[E0605]: non-primitive cast: `&[(&'static str, Value<'_>); 2]` as `&[(&str, Value<'_>)]`

However, when only one key-value pair is used, or when no key-values are included, it compiles and runs correctly.

### Example

This compiles fine:

debug!(host = "localhost", "Deleting documents");

But adding an additional key-value pair causes a compilation error:

debug!(host = "localhost", version = "1.0.0"; "Deleting documents");

This produces:

error[E0605]: non-primitive cast: `&[(&'static str, Value<'_>); 2]` as `&[(&str, Value<'_>)]`

Context

This happens inside a function annotated with #[async_trait], but the same pattern works fine outside of async traits. It seems related to how Rust infers lifetimes and array types in macro expansions involving slices of tuples.

The generated code from the macro expands roughly into:

&[("host", host_value), ("version", version_value)] as &[(&str, Value)]

which causes the compiler to reject the cast because the inferred array type has 'static keys (&'static str) but the target expects &str.

### Workaround

Replacing the slice construction inside the macro with this expression fixes the issue:

&vec![
    ($crate::__log_key!($key), $crate::__log_value!($key $(:$capture)* = $($value)*))
] as &[_],

Using a vec![] and taking a reference seems to allow proper type coercion to &[(&str, Value)].

### Possible Cause

This likely comes from a type coercion mismatch between: • the fixed array literal ([ ... ]) which has a concrete type like [(&'static str, Value<'>); N], • and the expected trait-bound type &[(&str, Value<'>)].

The compiler disallows this cast because arrays of tuples with different lifetimes on the first element are not directly coercible to slices of tuples with more general lifetimes.

Using vec![] allows dynamic slice coercion at runtime and avoids this limitation.

## Environment

•	Rust version: 1.81.0
•	Crate: log (latest release at time of writing)
•	Feature: kv enabled
•	Context: using within #[async_trait] functions

xhermosilla avatar Oct 18 '25 05:10 xhermosilla

This seems more of a Rust/async_trait issue, than a log issue to be honest. If it works without the #[async_trait] attribute, that's the things that is breaking the code 🤷

As for the suggestion of using a vector, I don't consider it an option to allocate for each call to log, especially considering it works for normal and async functions already.

Thomasdezeeuw avatar Oct 18 '25 15:10 Thomasdezeeuw

I wonder if this is some variant of another macro lifetime related issue I noticed elsewhere recently. At some point it looks like the edition rules of the macro source crate were applied to expanded code in the target. Now it seems like the target crate's rules are applied, causing expanded code relying on older edition rules to be invalid.

I haven't been able to repro this yet, but am wondering if a change like this might fix it: https://github.com/rust-lang/log/pull/707

KodrAus avatar Oct 19 '25 10:10 KodrAus