Skip to content

Add a new mismatched-lifetime-syntaxes lint #138677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 5, 2025

Conversation

shepmaster
Copy link
Member

@shepmaster shepmaster commented Mar 18, 2025

The lang-team discussed this and I attempted to summarize their decision. The summary-of-the-summary is:

  • Using two different kinds of syntax for elided lifetimes is confusing. In rare cases, it may even lead to unsound code! Some examples:

    // Lint will warn about these
    fn(v: ContainsLifetime) -> ContainsLifetime<'_>;
    fn(&'static u8) -> &u8;
  • Matching up references with no lifetime syntax, references with anonymous lifetime syntax, and paths with anonymous lifetime syntax is an exception to the simplest possible rule:

    // Lint will not warn about these
    fn(&u8) -> &'_ u8;
    fn(&'_ u8) -> &u8;
    fn(&u8) -> ContainsLifetime<'_>;
  • Having a lint for consistent syntax of elided lifetimes will make the future goal of warning-by-default for paths participating in elision much simpler.


This new lint attempts to accomplish the goal of enforcing consistent syntax. In the process, it supersedes and replaces the existing elided-named-lifetimes lint, which means it starts out life as warn-by-default.


EDIT(jieyouxu): for the bigger picture, this lint is part of a set of lints that tries to encourage more consistent usage of lifetime syntaxes to make it less confusing for users. The language team FCP'd the overall direction for the set of lints:

We have three categories of lifetime syntaxes: named, elided, and hidden.

  • Named means that an explicitly-bound generic lifetime parameter or 'static was used, e.g. W<'a>, &'a ().
  • Elided means that an anonymous lifetime was used with syntactic indication, e.g. W<'_>, &'_ (), &().
  • Hidden means that an anonymous lifetime was used without syntactic indication, e.g. W.

We correspondingly add four lints.

  • mismatched_lifetime_syntaxes fires when more than one of the lifetime syntaxes are used in a signature to refer to the same generic lifetime parameter.
  • hidden_lifetimes_in_input_paths fires when a function input parameter type uses the hidden lifetime syntax.
  • hidden_lifetimes_in_output_paths fires when a function return type uses the hidden lifetime syntax.
  • hidden_lifetimes_in_type_paths fires when a type elsewhere is a path that uses the hidden lifetime syntax.

The first of these we will make immediately warn-by-default.

Examples of all of these cases are here:

Playground link

As before, there is some mapping of old to new lint names and groups:

  • elided_lifetime_in_path maps to the hidden_lifetimes_in_paths group
  • elided_lifetimes_in_paths maps to the hidden_lifetimes_in_paths group
  • the hidden_lifetimes_in_paths group expands to hidden_lifetimes_in_input_paths, hidden_lifetimes_in_output_paths, and hidden_lifetimes_in_type_paths

All three hidden_lifetimes_* lints will still be a part of the rust_2018_idioms group.

I notice also that in the PR, the elided_named_lifetimes lint is superseded by the mismatched_lifetime_syntaxes one, and that sounds right to me as well.

(Note that "in the PR" in the quote refers to #120808, where this PR #138677 splits out from #120808 the elided_named_lifetimes -> mismatched_lifetime_syntaxes case.)

@rustbot
Copy link
Collaborator

rustbot commented Mar 18, 2025

r? @wesleywiser

rustbot has assigned @wesleywiser.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Mar 18, 2025
@rust-log-analyzer

This comment has been minimized.

@shepmaster
Copy link
Member Author

Thanks to:

  • @GrigorenkoPV for the original implementation of elided_named_lifetimes.
  • @compiler-errors for the push to write the lint using HIR instead of in rustc_resolve.
  • @estebank for the diagnostic help and wording feedback.

@shepmaster shepmaster force-pushed the consistent-elided-lifetime-syntax branch from 478a98e to e15c083 Compare March 18, 2025 21:01
@rust-log-analyzer

This comment has been minimized.

@shepmaster shepmaster force-pushed the consistent-elided-lifetime-syntax branch from e15c083 to ca86221 Compare March 20, 2025 13:34
@shepmaster shepmaster changed the title Add a new mismatched_elided_lifetime_styles lint Add a new mismatched-lifetime-syntaxes lint Mar 20, 2025
@rust-log-analyzer

This comment has been minimized.

@shepmaster shepmaster force-pushed the consistent-elided-lifetime-syntax branch from ca86221 to 6b4ba02 Compare March 20, 2025 13:57
@rust-log-analyzer

This comment has been minimized.

@traviscross traviscross dismissed their stale review March 20, 2025 19:33

Addressed.

@bors

This comment was marked as off-topic.

@shepmaster shepmaster force-pushed the consistent-elided-lifetime-syntax branch from 6b4ba02 to 7ff70d4 Compare March 23, 2025 00:48
@rustbot

This comment was marked as resolved.

@rustbot rustbot added the A-translation Area: Translation infrastructure, and migrating existing diagnostics to SessionDiagnostic label Mar 23, 2025
@rust-log-analyzer

This comment has been minimized.

@shepmaster shepmaster force-pushed the consistent-elided-lifetime-syntax branch from 7ff70d4 to 3a95f66 Compare March 24, 2025 17:29
@rustbot

This comment was marked as outdated.

@rust-log-analyzer

This comment has been minimized.

@shepmaster shepmaster force-pushed the consistent-elided-lifetime-syntax branch from 3a95f66 to 1625496 Compare March 24, 2025 20:01
@rust-log-analyzer

This comment has been minimized.

@shepmaster shepmaster force-pushed the consistent-elided-lifetime-syntax branch from 1625496 to 1cd8999 Compare March 25, 2025 00:55
@jieyouxu
Copy link
Member

jieyouxu commented Jun 5, 2025

Based on #138677 (comment), AFAICT this is ready on the lang side, and it looks good on the impl side, with concerns addressed. There's an outstanding non-blocking follow-up that's tracked in a separate issue. So:

@bors r=traviscross,jieyouxu rollup=never

Thanks a lot @shepmaster for working on this and addressing all the feedback 🩵

@bors
Copy link
Collaborator

bors commented Jun 5, 2025

📌 Commit d35ad94 has been approved by traviscross,jieyouxu

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 5, 2025
@traviscross
Copy link
Contributor

traviscross commented Jun 5, 2025

Agreed, and to echo it, thanks to @shepmaster for pushing this forward.

@bors
Copy link
Collaborator

bors commented Jun 5, 2025

⌛ Testing commit d35ad94 with merge ccf3198...

@bors
Copy link
Collaborator

bors commented Jun 5, 2025

☀️ Test successful - checks-actions
Approved by: traviscross,jieyouxu
Pushing ccf3198 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Jun 5, 2025
@bors bors merged commit ccf3198 into rust-lang:master Jun 5, 2025
11 checks passed
@rustbot rustbot added this to the 1.89.0 milestone Jun 5, 2025
Copy link
Contributor

github-actions bot commented Jun 5, 2025

What is this? This is an experimental post-merge analysis report that shows differences in test outcomes between the merged PR and its parent PR.

Comparing 076ec59 (parent) -> ccf3198 (this PR)

Test differences

Show 314 test diffs

Stage 1

  • [ui] tests/ui/lifetimes/mismatched-lifetime-syntaxes-details/example-from-issue48686.rs: [missing] -> pass (J0)
  • [ui] tests/ui/lifetimes/mismatched-lifetime-syntaxes-details/missing-lifetime-kind.rs: [missing] -> pass (J0)
  • [ui] tests/ui/lifetimes/mismatched-lifetime-syntaxes-details/not-tied-to-crate.rs: [missing] -> pass (J0)
  • [ui] tests/ui/lifetimes/mismatched-lifetime-syntaxes-details/static.rs: [missing] -> pass (J0)
  • [ui] tests/ui/lifetimes/mismatched-lifetime-syntaxes.rs: [missing] -> pass (J0)
  • [ui] tests/ui/lint/elided-named-lifetimes/example-from-issue48686.rs: pass -> [missing] (J0)
  • [ui] tests/ui/lint/elided-named-lifetimes/missing-lifetime-kind.rs: pass -> [missing] (J0)
  • [ui] tests/ui/lint/elided-named-lifetimes/not-tied-to-crate.rs: pass -> [missing] (J0)
  • [ui] tests/ui/lint/elided-named-lifetimes/static.rs: pass -> [missing] (J0)

Stage 2

  • [ui] tests/ui/lifetimes/mismatched-lifetime-syntaxes-details/example-from-issue48686.rs: [missing] -> pass (J1)
  • [ui] tests/ui/lifetimes/mismatched-lifetime-syntaxes-details/missing-lifetime-kind.rs: [missing] -> pass (J1)
  • [ui] tests/ui/lifetimes/mismatched-lifetime-syntaxes-details/not-tied-to-crate.rs: [missing] -> pass (J1)
  • [ui] tests/ui/lifetimes/mismatched-lifetime-syntaxes-details/static.rs: [missing] -> pass (J1)
  • [ui] tests/ui/lifetimes/mismatched-lifetime-syntaxes.rs: [missing] -> pass (J1)
  • [ui] tests/ui/lint/elided-named-lifetimes/example-from-issue48686.rs: pass -> [missing] (J1)
  • [ui] tests/ui/lint/elided-named-lifetimes/missing-lifetime-kind.rs: pass -> [missing] (J1)
  • [ui] tests/ui/lint/elided-named-lifetimes/not-tied-to-crate.rs: pass -> [missing] (J1)
  • [ui] tests/ui/lint/elided-named-lifetimes/static.rs: pass -> [missing] (J1)

Additionally, 296 doctest diffs were found. These are ignored, as they are noisy.

Job group index

Test dashboard

Run

cargo run --manifest-path src/ci/citool/Cargo.toml -- \
    test-dashboard ccf3198de316b488ee17441935182e9d5292b4d3 --output-dir test-dashboard

And then open test-dashboard/index.html in your browser to see an overview of all executed tests.

Job duration changes

  1. dist-x86_64-apple: 6996.6s -> 10155.4s (45.1%)
  2. x86_64-apple-1: 7661.2s -> 9584.7s (25.1%)
  3. mingw-check-1: 1995.4s -> 2487.7s (24.7%)
  4. dist-i686-mingw: 8003.9s -> 8617.1s (7.7%)
  5. x86_64-msvc-1: 8836.7s -> 8190.9s (-7.3%)
  6. x86_64-gnu-debug: 5901.7s -> 6305.2s (6.8%)
  7. dist-i586-gnu-i586-i686-musl: 5402.0s -> 5047.8s (-6.6%)
  8. x86_64-mingw-1: 9107.3s -> 8538.7s (-6.2%)
  9. test-various: 4403.3s -> 4655.6s (5.7%)
  10. dist-arm-linux-gnueabi: 4664.2s -> 4890.3s (4.8%)
How to interpret the job duration changes?

Job durations can vary a lot, based on the actual runner instance
that executed the job, system noise, invalidated caches, etc. The table above is provided
mostly for t-infra members, for simpler debugging of potential CI slow-downs.

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (ccf3198): comparison URL.

Overall result: ❌ regressions - please read the text below

Our benchmarks found a performance regression caused by this PR.
This might be an actual regression, but it can also be just noise.

Next Steps:

  • If the regression was expected or you think it can be justified,
    please write a comment with sufficient written justification, and add
    @rustbot label: +perf-regression-triaged to it, to mark the regression as triaged.
  • If you think that you know of a way to resolve the regression, try to create
    a new PR with a fix for the regression.
  • If you do not understand the regression or you think that it is just noise,
    you can ask the @rust-lang/wg-compiler-performance working group for help (members of this group
    were already notified of this PR).

@rustbot label: +perf-regression
cc @rust-lang/wg-compiler-performance

Instruction count

This is the most reliable metric that we have; it was used to determine the overall result at the top of this comment. However, even this metric can sometimes exhibit noise.

mean range count
Regressions ❌
(primary)
1.6% [0.2%, 9.7%] 72
Regressions ❌
(secondary)
0.3% [0.2%, 0.5%] 11
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-0.1% [-0.1%, -0.1%] 1
All ❌✅ (primary) 1.6% [0.2%, 9.7%] 72

Max RSS (memory usage)

Results (primary 1.8%, secondary 2.3%)

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
1.8% [0.6%, 3.2%] 57
Regressions ❌
(secondary)
2.3% [1.5%, 4.5%] 7
Improvements ✅
(primary)
-1.0% [-1.0%, -1.0%] 1
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 1.8% [-1.0%, 3.2%] 58

Cycles

Results (primary 2.6%)

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
2.6% [0.7%, 5.6%] 22
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 2.6% [0.7%, 5.6%] 22

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 750.189s -> 750.435s (0.03%)
Artifact size: 371.75 MiB -> 371.90 MiB (0.04%)

@shepmaster shepmaster deleted the consistent-elided-lifetime-syntax branch June 6, 2025 12:23
TheLostLambda added a commit to TheLostLambda/byos-queuer that referenced this pull request Jun 6, 2025
@therealprof
Copy link
Contributor

Those regressions are really bad... 9% on stm32f4xx-hal opt builds should amount to pretty all gains that have been painfully obtained the last 5 years...

Any plans to address this?

@jieyouxu Are those triggers based on stm32f4xx-hal code itself or coming from the stm32f4 crate? If the latter, there's probably very little that can be done about that since it's generated code... We could only try updating the test case then which is at 0.22.1 now.

@SpecificProtagonist
Copy link
Contributor

SpecificProtagonist commented Jun 6, 2025

As per the summary, this lint should not be triggered when the styles match, which makes sense. However, this lint triggers on functions taking a reference and returning a lifetimed adt when both use the 'hidden lifetimes' style. Is this intentional? In Bevy this triggers 231 warnings of this form and 0 true positives.

Edit: Ah, lang team decided that for references, hidden lifetimes count as elided instead.
I understand the reasoning insofar as this allows using an unnamed lifetime for an adt without having to use an unnamed lifetime for the a reference with the same lifetime, as it's clear that references have a lifetime:

fn function(&self) -> Lifetimed<'_>;

However, this doesn't mean the following should be forbidden by this lint:

fn function(&self) -> Lifetimed;

Of course, it may make sense to lint against the later case, such as via a hidden_lifetimes_in_output_paths lint proposed in #120808, but doing so in mismatched-lifetime-syntaxes is confusing and arguably incorrect.

@shepmaster
Copy link
Member Author

this doesn't mean the following should be forbidden by this lint

Does the lint documentation clarify the language team's perspective as to why those are two different categories?

@Noratrieb
Copy link
Member

@therealprof are you seeing any other regressions in your builds or just looking at the perf report? note that the 9% is incremental, which will not be hit for cargo dependencies.
overall, this shouldn't really have an impact on the dependency, since most of the regression is in emitting the lint, which doesn't happen for deps

@Noratrieb
Copy link
Member

@SpecificProtagonist
Copy link
Contributor

SpecificProtagonist commented Jun 6, 2025

Does the lint documentation clarify the language team's perspective as to why those are two different categories?

It clarifies that a reference without a marked lifetime is classified as ellided rather than hidden. It does not clarify why.

As far as I can tell based on the comments and meeting notes, this classification is there to avoid linting against that, while potentially stylistically incongruent, are not confusing. That this reclassification causes this lint to trigger in my second example seems unintentional, which is why I asked.

@ThomasHabets
Copy link

ThomasHabets commented Jun 7, 2025

I too wonder about if triggering this lint on fn function(&self) -> Lifetimed; is intentional, or merely a result of two locally reasonable decisions?

Yes, in general it would seem confusing to use different syntaxes. Yes, &str can reasonably be called "elided" and Lifetimed "hidden".

But is the effect of these decisions a desirable one? Is forbidding/discouraging fn function(&self) -> Lifetimed; really desirable, or does it just mean that the programmer needs to sprinkle needless "null" lifetime annotations to make the linter happy?

Lifetime annotations are already tricky for beginners, so does this specific case actually add any clarity for the compiler or the reader? In my opinion it does not.

In fact, to a naive relative newbie such as myself, the linter proposed solution is more inconsistent: fn function(&self) -> Lifetimed<'_>;

Isn't it more inconsistent to force a lifetime annotation on the output, when the input doesn't have one?

@therealprof
Copy link
Contributor

Lifetime annotations are already tricky for beginners, so does this specific case actually add any clarity for the compiler or the reader? In my opinion it does not.

In fact, to a naive relative newbie such as myself, the linter proposed solution is more inconsistent: fn function(&self) -> Lifetimed<'_>;

Isn't it more inconsistent to force a lifetime annotation on the output, when the input doesn't have one?

I agree and I made the same comment some hours ago on the Zulip discussion.

@shepmaster
Copy link
Member Author

For ongoing discussion about the semantics and behavior of the lint, not performance, please use the #t-lang > Feedback on mismatched_lifetime_syntaxes Zulip thread.

@jieyouxu
Copy link
Member

jieyouxu commented Jun 8, 2025

Both for making separate discussions manageable (because PR comments often get collapsed or becomes impossible to find) and visible (to not just only people subscribed to this PR), please redirect further feedback/issues to one of:

  1. The performance discussion compiler thread.
  2. The semantics and behavior of the mismatched_lifetime_syntaxes lint language thread. This includes feedback on which cases the lint should or should not warn on.
  3. Open a dedicated issue, and tag it with @rustbot label: +L-mismatched_lifetime_syntaxes.

I'm going to lock this PR because it becomes impossible to follow multiple distinct discussions, all intermixed as top-level comments.

@rust-lang rust-lang locked as too heated and limited conversation to collaborators Jun 8, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. A-translation Area: Translation infrastructure, and migrating existing diagnostics to SessionDiagnostic L-mismatched_lifetime_syntaxes Lint: mismatched_lifetime_syntaxes merged-by-bors This PR was explicitly merged by bors. perf-regression Performance regression. perf-regression-triaged The performance regression has been triaged. relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.