Repository navigation
style(proto): enforce clippy:let_unit_value - #4231
Conversation
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>
| } | ||
|
|
||
| let _entered = trace_span!("parse_headers"); | ||
| trace_span!("parse_headers"); |
There was a problem hiding this comment.
trace_span returns a guard when tracing features is enabled. So the binding here actually serves a purpose.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Would a new unit struct make the lint happy?
There was a problem hiding this comment.
yes, it does! that's the core of what 804e64e is doing.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Also since they can just be dropped immediately without triggering a warning, do {}_span! macros not have #[must_use]?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
filed #4232 to track the conversation about enforcing warnings like this in the future. 🔖
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>
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.
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>
`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>
see #4071.
see https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value.
this commit addresses the
let_unit_valueclippy lint. only two sitesare affected, relating to tracing spans.