Description
While working on a diagnostics improvement for the compiler, I came across an essentially unreadable error. The following (minimized) code:
#![feature(nll)]
// Covariant over 'a, invariant over 'tcx
struct FnCtxt<'a, 'tcx: 'a>(&'a (), *mut &'tcx ());
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn use_it(&self, _: &'tcx ()) {}
}
struct Consumer<'tcx>(&'tcx ());
impl<'tcx> Consumer<'tcx> {
fn bad_method<'a>(&self, fcx: &FnCtxt<'a, 'tcx>) {
let other = self.use_fcx(fcx);
fcx.use_it(other);
}
fn use_fcx<'a>(&self, _: &FnCtxt<'a, 'tcx>) -> &'a () {
&()
}
}
produces this error:
error[E0521]: borrowed data escapes outside of function
--> src/lib.rs:14:21
|
13 | fn bad_method<'a>(&self, fcx: &FnCtxt<'a, 'tcx>) {
| ----- --- `fcx` is a reference that is only valid in the function body
| |
| `self` is declared here, outside of the function body
14 | let other = self.use_fcx(fcx);
| ^^^^^^^^^^^^^^^^^ `fcx` escapes the function body here
|
= help: consider adding the following bound: `'a: 'tcx`
There are several problems with this error message:
- The span is very unhelpful. Commenting out the call to
fcx.use_it(other);
causes this code to compile (I think due to the fact that the lifetime ofother
is no longer required to outlivetcx
). However, the error message points to a completely different line, which is not really the source of the problem. - The 'escapes outside of function' terminology is very misleading. In general, there's nothing wrong with passing a reference to another function. The problem is that that by passing
fcx
touse_fcx
, we (indirectly) end up constraininga
. - The only place where the error message refers to any lifetimes is in the suggestion to add
'a: 'tcx
. Without reading the hint, you might not even realize that the named lifetimes are involved in any way. - The error depends on the invariance of the
'tcx
lifetime (caused by the raw pointer, which is invariant over the pointee type). I discovered this through blind luck, as I've seen variance-related errors before. If someone has never even heard of variance, it would be virtually impossible for them to discover this - the invariance of a lifetime may be caused by the lifetime being passed to a deeply nested invariant type.
Removing #![feature(nll)]
makes the error marginally better:
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter 'a in function call due to conflicting requirements
--> src/lib.rs:14:26
|
14 | let other = self.use_fcx(fcx);
| ^^^^^^^
|
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the method body at 13:19...
--> src/lib.rs:13:19
|
13 | fn bad_method<'a>(&self, fcx: &FnCtxt<'a, 'tcx>) {
| ^^
note: ...but the lifetime must also be valid for the lifetime `'tcx` as defined on the impl at 12:6...
--> src/lib.rs:12:6
|
12 | impl<'tcx> Consumer<'tcx> {
| ^^^^
= note: ...so that the expression is assignable:
expected &FnCtxt<'_, '_>
found &FnCtxt<'a, 'tcx>
Here, we at least know that 'a
and 'tcx
are involved. However, the span is still bad - there's no indication that the call to fcx.use_it(other)
is involved at all. The note 'so that the expression is assignable' also lacks a span entirely, making it difficult to tell what it actually means.
For some reason, removing the 'tcx: 'a
requirement from the FnCtxt
definition (e.g. changing it to just 'tcx
) results in a better span with #![feature(nll)]
:
error[E0521]: borrowed data escapes outside of function
--> src/lib.rs:15:9
|
13 | fn bad_method<'a>(&self, fcx: &FnCtxt<'a, 'tcx>) {
| ----- --- `fcx` is a reference that is only valid in the function body
| |
| `self` is declared here, outside of the function body
14 | let other = self.use_fcx(fcx);
15 | fcx.use_it(other);
| ^^^^^^^^^^^^^^^^^ `fcx` escapes the function body here
|
= help: consider adding the following bound: `'a: 'tcx`
error: aborting due to previous error
but the error is unchanged when #![feature(nll)]
is not enabled.
cc #62953