Skip to content

Conversation

@hds
Copy link
Contributor

@hds hds commented Nov 27, 2025

Motivation

The valueset! macro imports display, debug and Value from
tracing::field. This is unhygienic in that tracing macros that use
valueset! (all the event and span macros) cannot use any symbols named
display or debug when defining fields inside the macro.

A side affect of this is that these three symbols can be used
unqualified within the definition of fields within the tracing macros.
So the following is valid:

tracing::error!(foo = display("foo"));
tracing::error!(quux = debug(4_u8 as u64));
tracing::error!(bar = &num as &dyn Value);

As per Hyrum's Law, this behavior depended on by plenty of users and
has been for years. At least 2 attempts have been made to clean this up,
but they've had to be reverted as it is effectively a breaking change
(see #820 and #3424 for details).

Solution

To partially resolve this situation, this change adds support for
display and debug patterns to the valueset! macro explicitly. As
such, the use of display(expr), display(ident) and the same for
debug are matched in the patterns the same way that the % and ?
sigils already are.

These patterns then get expanded the same way that the sigils do, into
the fully qualified path to the appropriate function.

As the use of bare display and debug are now caught by the macro, we
can safely remove the import of those 2 symbols. This still leaves the
import of $crate::field::Value, but I don't know if that's avoidable,
and has also proved to be less problematic as it's a type and less
likely to clash to the use of variables. However, we still don't want to
break user code by removing it.

The bare display and debug also continue to work with the
#[instrument] attribute macro:

 #[tracing::instrument(fields(foo = debug(Some(5))))]
 fn foo() {}

Fairly extensive tests have been added to cover all the cases that the
macros support (which are all the cases that could be thought up).

This means that we can support all previously valid macro invocations,
but we can also support the following, which didn't work before:

let display = "display";
let debug = "debug";
tracing::error!("{debug:?} {display}");

This change deliberately doesn't add support for wrapping an identifier
in debug or display with no key. This isn't supported now and there
is no need to support it when the sigils are available. That is to say
that the following didn't compile before and will continue not to
compile:

tracing::error!(display(disp));
tracing::error!(debug(deb));

To make this clear, UI tests have been added for the compilation error.

@hds hds requested review from a team and hawkw as code owners November 27, 2025 15:07
@hds hds force-pushed the hds/valueset-display-debug-support branch from 121995c to b787232 Compare November 27, 2025 15:21
@hds hds marked this pull request as draft November 27, 2025 15:22
@mladedav
Copy link
Contributor

This works for me when patching this into our repo 👍

The `valueset!` macro imports `display`, `debug` and `Value` from
`tracing::field`. This is unhygienic in that tracing macros that use
`valueset!` (all the event and span macros) cannot use any symbols named
`display` or `debug` when defining fields inside the macro.

A side affect of this is that these three symbols can be used
unqualified within the definition of fields within the tracing macros.
So the following is valid:

```rust
tracing::error!(foo = display("foo"));
tracing::error!(quux = debug(4_u8 as u64));
tracing::error!(bar = &num as &dyn Value);
```

As per [Hyrum's Law], this behavior depended on by plenty of users and
has been for years. At least 2 attempts have been made to clean this up,
but they've had to be reverted as it is effectively a breaking change
(see #820 and #3424 for details).

[Hyrum's Law]: https://www.hyrumslaw.com/

To partially resolve this situation, this change adds support for
`display` and `debug` patterns to the `valueset!` macro explicitly. As
such, the use of `display(expr)`, `display(ident)` and the same for
`debug` are matched in the patterns the same way that the `%` and `?`
sigils already are.

These patterns then get expanded the same way that the sigils do, into
the fully qualified path to the appropriate function.

As the use of bare `display` and `debug` are now caught by the macro, we
can safely remove the import of those 2 symbols. This still leaves the
import of `$crate::field::Value`, but I don't know if that's avoidable,
and has also proved to be less problematic as it's a type and less
likely to clash to the use of variables. However, we still don't want to
break user code by removing it.

The bare `display` and `debug` also continue to work with the
`#[instrument]` attribute macro:

```rust
 #[tracing::instrument(fields(foo = debug(Some(5))))]
 fn foo() {}
```

Fairly extensive tests have been added to cover all the cases that the
macros support (which are all the cases that could be thought up).

This means that we can support all previously valid macro invocations,
but we can also support the following, which didn't work before:

```rust
let display = "display";
let debug = "debug";
tracing::error!("{debug:?} {display}");
```

This change deliberately doesn't add support for wrapping an identifier
in `debug` or `display` with no key. This isn't supported now and there
is no need to support it when the sigils are available. That is to say
that the following didn't compile before and will continue not to
compile:

```rust
tracing::error!(display(disp));
tracing::error!(debug(deb));
```

To make this clear, UI tests have been added for the compilation error.
@hds hds force-pushed the hds/valueset-display-debug-support branch from 533f768 to 86e33c8 Compare November 28, 2025 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants