rust-lang / rust-lang/rust

Tracking issue for future-compatibility warning `recursion_depth_exceeding_limit`

Open
#159,228 7 comments 5 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-future-incompatibility C-tracking-issue T-compiler T-types WG-trait-system-refactor
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

This is the summary issue for the recursion_depth_exceeding_limit future-compatibility warning. The goal of this page is to describe why this change was made and how you can fix code that is affected by it. It also provides a place to ask questions
or register a complaint if you feel the change should not be made. For more information on the policy around future-compatibility warnings, see our breaking change policy guidelines.

What is this warning for

The recursion_depth_exceeding_limit lint detects cases where the compiler previously did not correctly track the required recursion depth. Properly tracking the required depth then causes overflow errors as it exceeds the value set for the #[recursion_limit = "N"]. We are temporarily weakening these errors to a future compatibility warning.

Example
#![recursion_limit = "8"]

// The field order matters 😂
struct Foo<T> {
    t: T,
    opt_t: Option<T>,
}

fn require_sync<T: Sync>() {}

fn main() {
    // The warning: "overflow evaluating the requirement `Foo<Foo<Foo<Foo<Foo<Foo<()>>>>>>: Sync`"
    require_sync::<Foo<Foo<Foo<Foo<Foo<Foo<()>>>>>>>();
}

The above code compiles with the old solver, while it requires #[recursion_limit = "12"] for the next solver. To prove Foo^N: Sync, we have to prove constituent nested goals Foo^N-1: Sync and Option<Foo^N-1>: Sync. If we require depth d to prove the former, the next solver requires the depth d + 1 for the later, because we goes down one depth from Option<Foo^N-1>: Sync to its constituent goal Foo^N-1: Sync, and we hit the cache with required depth d for this nested goal.
So, the total required depth for the root goal is proportional to N with slope 2. But for the old solver, we check the recursion limit when we actually consider the obligation and don't record/lookup the required depth for its deeply nested goals.
Therefore, the total required depth for the root goal is proportional to N with slope 1 and the next-solver requires twice more recursion limits as N grows.

How to best fix this

It's strongly recommended to add manual impls for auto traits like below, if possible.

// First identify the relevant auto trait, usually `Send/Sync`. It's possible that the auto trait
// is in an indirect where clause of impls rather than in the displayed clause.
// Then find the type required to implement the auto trait. The type is expected to be 
// deeply nested, requiring recursive visits into constituent fields.
//     - If the type is concrete, use `unsafe impl YourAutoTrait for YourType {}`
//     - If the type is generic, use `unsafe impl<T: YourAutoTrait> YourAutoTrait for YourType<T> {}`
// To ensure the fields of your type actually implement the auto trait, you can validate them like below.
//
// Adapted from the `non_structural_derive` crate. You may want to use it directly.
unsafe impl<T: Sync> Sync for Foo<T> {}
fn _check_bound<T: Sync>(_: &T) {}
fn _validate_fields<T: Sync>(x: &Foo<T>) {
    let Foo { t, opt_t } = x;
    _check_bound::<T>(&t);
    _check_bound::<Option<T>>(&opt_t);
}

This helps your downstream users avoid having to increase the recursion_limit when they use your deeply nested types. This also improves the performance of the type system when compared to the builtin auto-trait impl which may matter for very large types.

If your overflow isn't caused by auto traits, you may have to increase the recursion_limit by adding the following to your crate root.

#![recursion_limit = "a big number like 256"]

It is generally better to change the code to not require a large recursion depth as this requires all downstream users to also increase their recursion_limit.

Impact

This affects wgpu = "25.0.2" and a lot of other crates. This was investigated and explained by @ShoyuVanilla. zulip discussion

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the recursion_depth_exceeding_limit example and the linked breaking change policy guidelines, then review the referenced Zulip discussion for the compiler context. The issue names no Rust source files or tests and does not specify a concrete implementation task, so completion criteria are not defined.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.