Skip to content

style(proto): enforce clippy:let_unit_value - #4231

Merged
cratelyn merged 4 commits into
masterfrom
kate/let-unit-value
Oct 13, 2026
Merged

cratelyn merged 4 commits into
masterfrom
kate/let-unit-value

Conversation

@cratelyn

@cratelyn cratelyn commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

see #4071.

see https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value.

this commit addresses the let_unit_value clippy lint. only two sites
are affected, relating to tracing spans.

see #4071.

this commit addresses the `let_unit_value` clippy lint. only two sites
are affected, relating to tracing spans.

Signed-off-by: katelyn martin <git@katelyn.world>
@cratelyn
cratelyn marked this pull request as ready for review October 10, 2026 04:22
Comment thread src/proto/h1/role.rs Outdated
}

let _entered = trace_span!("parse_headers");
trace_span!("parse_headers");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trace_span returns a guard when tracing features is enabled. So the binding here actually serves a purpose.

@cratelyn cratelyn Oct 13, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yikes, thank you for spotting that @dswij. faf0de4 reverts this change to the trace_span! call-sites.

this is tricky because clippy warns about let_unit_value if the tracing flag isn't active, but as you point out, we must bind a variable here because a RAII guard is returned when the flag is active.

there's a "simple" solution here, wherein we add something like this to the assorted call-sites:

+#[cfg_attr(not(feature = "tracing"), allow(clippy::let_unit_value))]
 let _entered = trace_span!("instrumented");

i don't love how this introduces friction to adding telemetry though, it strikes me as something that would become a recurring papercut in CI, and let_unit_value seems like a worthy lint to enforce in general.

i thought about this for a bit today, and spun up a little playground to explore some possible solutions: https://github.com/cratelyn/hyper-span-macro-example.

that led me to the idea of introducing a type to stand-in as a disabled guard when the tracing flag isn't active. how does 804e64e look to you?

there's more information in the commit message there, but the nice part about this approach is that it avoids the need to decorate call-sites for these macros. what do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would a new unit struct make the lint happy?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it does! that's the core of what 804e64e is doing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think unit struct is the way to go, that's my first thought too. Thanks for exploring that!

Just one nit on the commit message though: I guess we're not introducing a mock guard, but a ZST marker as an alternative to the unit type (since we're not really mocking any of the guard functionality).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also since they can just be dropped immediately without triggering a warning, do {}_span! macros not have #[must_use]?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit on the commit message though: I guess we're not introducing a mock guard, but a ZST marker as an alternative to the unit type (since we're not really mocking any of the guard functionality).

that's a good point, thank you. i polished the doc comment for this type to refer to it as a ZST marker rather than a guard in 7149e65.

@cratelyn cratelyn Oct 13, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also since they can just be dropped immediately without triggering a warning, do {}_span! macros not have #[must_use]?

i was really curious about this as well. i think i figured this out:

first, i confirmed that Entered is marked with this attribute: https://docs.rs/tracing/0.1.44/src/tracing/span.rs.html#387

and i was also able to confirm that a warning appears when building the errant commit b236593b with a nightly toolchain, with the tracing feature active.

moreover, i even found that these did generate warnings in CI, here: https://github.com/hyperium/hyper/actions/runs/38023510701/job/114129390066#step:7:217

however, note the specific warnings we deny when running cargo hack, here:

      - name: check --feature-powerset with tracing feature
        run: cargo hack --no-dev-deps check --feature-powerset --depth 2 --features tracing --skip ffi
        env:
          RUSTFLAGS: "--cfg hyper_unstable_tracing -D dead_code -D unused_imports"

...as compared to the compiler warning:

  warning: unused `EnteredSpan` that must be used
    --> src/proto/h1/role.rs:86:5
     |
  86 |     trace_span!("parse_headers");
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
     = note: once a span has been entered, it should be exited
     = note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default
  help: use `let _ = ...` to ignore the resulting value
     |
  86 |     let _ = trace_span!("parse_headers");;
     |     +++++++                             +

we are only denying dead_code and unused_imports, and this is a separate linting rule.

we don't enforce warnings as strictly when running cargo hack, because we only deny warnings in tests here:

// src/lib.rs
#![cfg_attr(all(test, feature = "full"), deny(unreachable_pub))]
#![cfg_attr(all(test, feature = "full"), deny(warnings))]
#![cfg_attr(all(test, feature = "nightly"), feature(test))]

https://github.com/cratelyn/hyper/blob/b236593b60195dd25b53fefbb6b3f47b422b2db9/src/lib.rs#L4-L6

i think we can fix this easily, but i wanted to write this up here to (a) explain in-band why we didn't see a CI failure previously, and (b) act as a comment i can spin out into a separate issue.

thank you again for your thoughtful review, @dswij, i appreciate it very much.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filed #4232 to track the conversation about enforcing warnings like this in the future. 🔖

cratelyn added a commit to cratelyn/hyper-span-macro-example that referenced this pull request Oct 12, 2026
this repository is a small reproduction of hyper's tracing macros, for
use in <hyperium/hyper#4231>.

when this program is run, one sees:

```
; just run
cargo run
   Compiling hyper-span-macro-example v0.1.0
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/hyper-span-macro-example`
2026-10-12T22:39:16.992644Z  INFO hyper_span_macro_example: before entering span
2026-10-12T22:39:16.992672Z  INFO instrumented: hyper_span_macro_example: after entering span
2026-10-12T22:39:16.992683Z TRACE instrumented: hyper_span_macro_example: within an instrumented function
```

when this program is linted, one sees:

```
; just check
cargo clippy --no-default-features
    Checking hyper-span-macro-example v0.1.0
error: this let-binding has unit value
  --> src/main.rs:13:5
   |
13 |     let _entered = trace_span!("instrumented");
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#let_unit_value
note: the lint level is defined here
  --> src/main.rs:2:9
   |
 2 | #![deny(clippy::all)]
   |         ^^^^^^^^^^^
   = note: `#[deny(clippy::let_unit_value)]` implied by `#[deny(clippy::all)]`
help: omit the `let` binding
   |
13 -     let _entered = trace_span!("instrumented");
13 +     trace_span!("instrumented");
   |

error: could not compile `hyper-span-macro-example` (bin "hyper-span-macro-example") due to 1 previous error
error: Recipe `lint-no-tracing` failed on line 5 with exit code 101
```

in subsequent commits, we will explore solutions to satisfying the
`clippy::let_unit_value` rule in hyper.

Signed-off-by: katelyn martin <git@katelyn.world>
cratelyn added a commit to cratelyn/hyper-span-macro-example that referenced this pull request Oct 13, 2026
this repository is a small reproduction of hyper's tracing macros, for
use in <hyperium/hyper#4231>.

when this program is run, one sees:

```
; just run
cargo run
   Compiling hyper-span-macro-example v0.1.0
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/hyper-span-macro-example`
2026-10-12T22:39:16.992644Z  INFO hyper_span_macro_example: before entering span
2026-10-12T22:39:16.992672Z  INFO instrumented: hyper_span_macro_example: after entering span
2026-10-12T22:39:16.992683Z TRACE instrumented: hyper_span_macro_example: within an instrumented function
```

when this program is linted, one sees:

```
; just check
cargo clippy --no-default-features
    Checking hyper-span-macro-example v0.1.0
error: this let-binding has unit value
  --> src/main.rs:52:5
   |
52 |     let _entered = trace_span!("instrumented");
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#let_unit_value
note: the lint level is defined here
  --> src/main.rs:6:9
   |
 6 | #![deny(clippy::all)]
   |         ^^^^^^^^^^^
   = note: `#[deny(clippy::let_unit_value)]` implied by `#[deny(clippy::all)]`
help: omit the `let` binding
   |
52 -     let _entered = trace_span!("instrumented");
52 +     trace_span!("instrumented");
   |

error: could not compile `hyper-span-macro-example` (bin "hyper-span-macro-example") due to 1 previous error
error: Recipe `lint-no-tracing` failed on line 5 with exit code 101
```

in subsequent commits, we will explore solutions to satisfying the
`clippy::let_unit_value` rule in hyper.

Signed-off-by: katelyn martin <git@katelyn.world>
this partially reverts commit b236593.

only the changes to `trace_span!` call-sites are reverted.
@cratelyn
cratelyn marked this pull request as draft October 13, 2026 00:30
this commit introduces a `Disabled` span guard.

this is based upon experimenting in
<https://github.com/cratelyn/hyper-span-macro-example>.

to restate some of those learnings here:

 ### background

hyper contains a `trace` submodule, introduced in #3326,
that contains macros like the following:

```rust
macro_rules! trace {
    ($($arg:tt)*) => {
        #[cfg(feature = "tracing")]
        {
            tracing::trace!($($arg)+);
        }
    }
}

macro_rules! trace_span {
    ($($arg:tt)*) => {
        {
            #[cfg(feature = "tracing")]
            {
                let _span = tracing::trace_span!($($arg)+);
                _span.entered()
            }
        }
    }
}
```

these thinly wrap `tracing`'s macros, to account for the fact that tracing
support in hyper is opt-in. it is gated behind a feature flag, and
additionally mandates that the `hyper_unstable_tracing` flag be enabled.

however, this leads to violations of clippy's `let_unit_value` rule when
compiled without the `tracing` feature flag is enabled. this is because
`trace_span!` only returns a `tracing::span::Entered` when that flag is active,
otherwise it returns `()`.

thus, we *must* bind these calls to a variable, so that we properly hold
on to the span guard when the tracing flag is active. however, we must
contend with the fact that when this flag is not active, strict clippy
linting yields us warnings like this:

```text
cargo clippy --no-default-features
    Checking hyper-span-macro-example v0.1.0
error: this let-binding has unit value
  --> src/main.rs:52:5
   |
52 |     let _entered = trace_span!("instrumented");
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#let_unit_value
```

 ### why not use `#[allow(..)]`

one might try to selectively apply `#[allow(clippy::let_unit_value)]`
attributes to stifle this.

however, applying attributes to expressions is still a nightly feature
we cannot rely on yet, so call-sites for our macros must be updated
rather than modifying the code generated by the macro. see
rust-lang/rust#15701.

this would require us to do something like this throughout the codebase,
wherever we create tracing spans:

```rust
 #[cfg_attr(not(feature = "tracing"), allow(clippy::let_unit_value))]
 let _entered = trace_span!("instrumented");
```

`#[cfg_attr]` is a bit esoteric, and if we require that instrumenting code in
hyper require this song and dance every time we create a span, it will
generally discourage instrumentation.

 ### changes

this commit proposes another route: introduce a `Disabled` type to
represent a mock RAII guard when tracing instrumentation is disabled.

this satisfies clippy's `let_unit_value` rule because macros like
`trace_span!` will never return `()` no matter which feature flags are
active. this _also_ prevents accidentally writing code that would drop
span guards once the tracing flag is enabled, because an inert
`#[must_use]` guard acts as a stand-in when the flag isn't present.

Signed-off-by: katelyn martin <git@katelyn.world>
@cratelyn
cratelyn marked this pull request as ready for review October 13, 2026 01:54
@cratelyn
cratelyn requested a review from dswij October 13, 2026 01:55
`Disabled` is better described as a ZST marker, rather than a "guard",
since it is not actually connected to a semantically meaningful `Drop`
implementation.

see #4231 (comment).

Signed-off-by: katelyn martin <git@katelyn.world>
@cratelyn
cratelyn merged commit b073378 into master Oct 13, 2026
24 checks passed
@cratelyn
cratelyn deleted the kate/let-unit-value branch October 13, 2026 17:47
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.

3 participants