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() {
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.
// Adapted from crate `non_structural_derive`. You may want to use it directly.
unsafe impl<T: Send> Send for Foo<T> {}
fn _check_bound<T: Send>(_: &T) {}
fn _validate_fields<T: Send>(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 have to increase the recursion_limit by adding the following to your crate root.
#![recursion_limit = "a big number like 256"]
Impact
This affects wgpu = "25.0.2" and a lot of other crates. This was investigated and explained by @ShoyuVanilla. zulip discussion
This is the summary issue for the
recursion_depth_exceeding_limitfuture-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 questionsor 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_limitlint 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
The above code compiles with the old solver, while it requires
#[recursion_limit = "12"]for the next solver.To proveFoo^N: Sync, we have to prove constituent nested goalsFoo^N-1: SyncandOption<Foo^N-1>: Sync. If we require depthdto prove the former, the next solver requires the depthd + 1for the later, because we goes down one depth fromOption<Foo^N-1>: Syncto its constituent goalFoo^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
Nwith slope2. 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
Nwith slope1and the next-solver requires twice more recursion limits asNgrows.How to best fix this
It's strongly recommended to add manual impls for auto traits like below, if possible.
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 have to increase the recursion_limit by adding the following to your crate root.
#![recursion_limit = "a big number like 256"]Impact
This affects
wgpu = "25.0.2"and a lot of other crates. This was investigated and explained by @ShoyuVanilla. zulip discussion