-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Make lifetime elision rules for closures consistent with lifetime elision rules for functions #86921
Comments
Why would this be a breaking change? Wouldn't it only permit compilation of more code? |
Hm, I was worried that it might break code like the following: let closure = |x: &str| -> &str { "hello" }; ...but actually maybe that might compile just fine? I thought I remembered somebody coming up with a reason that it couldn't be done backwards-compatibly, but I can't recall it now. |
See #56537 for why this would be a breaking change. It would be great to see this inconsistency fixed for new code. Some proc macros want to wrap the body of a function in a closure to inspect the returned value, and this is a blocker. This is the root cause of https://gitlab.com/karroffel/contracts/-/issues/11 for example, and it makes that crate basically unusable whenever mutable references are involved. |
Hit this rather obscure issue in a rather simple code. Which got me thinking: Making closures to use lifetime elision doesn't make sense to me, it feels exactly backwards. We have lifetime elision and not lifetime inference for functions because we want function's signature to specify function's interface completely and create an abstraction boundary. We explitelly do not want function body to affect the types, which allows us to declare functions "at the top level" and do various separate-compilation-like things (like having semver guarantees, for example). Closures are deliberately different from functions in that they explicitly allow inferring types from bodies. The price we pay for this is that we need "context-aware inference" to figure out closure's type, so we don't allow closures on the top-level. That is, fns and closures are different mechanisms with offer different trade-offs to the user. For this reason, we don't want closure's lifetimes to be elided (which is an approximation), we want them to be inferred. So it seems to be that the original example in the issue is just a bug in lifetime inference? That is, the compiler should understand precisely what's different between fn main<'x>(x: &'x str) {
let f: |y: &str| -> x;
let g: |y: &str| -> y;
} Both casse should just work without adding any kind of extra syntax or annotations and infer |
Yes, that argument does seem reasonable, and as a bonus it means that this wouldn't be a breaking change. On the other hand, while I presume that making closures participate in lifetime elision would be an extremely small amount of work, this sounds like it might be much more work (does the compiler have existing mechanisms for lifetime inference anywhere?), and it also raises questions about whether this would impact compilation times negatively (closures are already a big offender, though for monomorphization reasons). |
An instance of this discrepancy being encountered in the wild: https://www.reddit.com/r/rust/comments/rjtphp/why_doesnt_the_compiler_infer_the_proper_lifetimes/ |
The following code does not compile:
Output:
For
function
, the lifetime of the return type is properly inferred to be equal to the only input lifetime, as per the lifetime elision rules:However, for
closure
this is not done, and the return type is assigned a distinct lifetime from the input. AFAIK it is only an accident of history that closures do not have the same lifetime elision rules as functions. This would be a breaking change to fix, but should be able to be done over an edition.Here's a simplified example of code that compiles today that might break if the function elision rules were applied to closures:
See #56537 for prior discussion.
The text was updated successfully, but these errors were encountered: