How to interpret "`self` escapes the method body here"?

I tried this code:

fn foo<'a>(x: &'static &'a mut u64) -> &'static u64 {
    x
}

struct WithLifetime<'a> {
    phantom: std::marker::PhantomData<&'a ()>,
}

impl<'a> WithLifetime<'a> {
    fn call(self) {
        let x = Box::leak::<'a>(Box::new(42u64));
        let y = Box::leak(Box::new(x));
        let z = foo(y);

        println!("{}", z);
    }
}

When I try to compile this code, I got this error:

error[E0521]: borrowed data escapes outside of method
  --> src/lib.rs:13:17
   |
9  | impl<'a> WithLifetime<'a> {
   |      -- lifetime `'a` defined here
10 |     fn call(self) {
   |             ---- `self` is a reference that is only valid in the method body
...
13 |         let z = foo(y);
   |                 ^^^^^^
   |                 |
   |                 `self` escapes the method body here
   |                 argument requires that `'a` must outlive `'static`

For more information about this error, try `rustc --explain E0521`.

I understand that 'a must outlive 'static because of function foo's signature. However, I don't quite get the point what does the compile error mean. Why is self escaping the method body when I call foo that does not use self?

Lifetimes shouldn't be abused like this. But we still can learn something from this anti-pattern.

The best way to learn from the error is to first read its error code page. Unluckily, the description in the webpage is a bit inaccurate:

-Borrowed data escapes outside of closure.
+borrowed data escapes outside of method.

So the second trick to reduce your complex code into a minimum one:

struct WithLifetime<'a> {
    phantom: std::marker::PhantomData<&'a ()>,
}

impl<'a> WithLifetime<'a> {
    fn call1(self) {
        f(self);
    }
}

fn f(_: WithLifetime<'static>) {}

error[E0521]: borrowed data escapes outside of method
 --> src/lib.rs:7:9
  |
5 | impl<'a> WithLifetime<'a> {
  |      -- lifetime `'a` defined here
6 |     fn call1(self) {
  |              ---- `self` is a reference that is only valid in the method body
7 |         f(self);
  |         ^^^^^^^
  |         |
  |         `self` escapes the method body here
  |         argument requires that `'a` must outlive `'static`

Cool, the same error msg occurs, and we can see the compiler fails to understand 'a in call1 method ought to be 'static. Why couldn't it be so?

impl<'a> WithLifetime<'a> means an impl for WithLifetime<'any> including WithLifetime<'short>, WithLifetime<'long>, and even WithLifetime<'static>, so with call method comsuming self, the lifetime 'any should end in the enclosing block in that method. Only the 'static can meet the (implicit or explicit) 'static bound in call method, but you specify 'any to do it, which results in the error since 'short can't escape the method call's scope.

The common error you may have seen is as follows:

impl<'a> WithLifetime<'a> {
    fn call2(self) {
        let _: WithLifetime<'static> = self;
    }
}

error: lifetime may not live long enough
  --> src/lib.rs:10:16
   |
5  | impl<'a> WithLifetime<'a> {
   |      -- lifetime `'a` defined here
...
10 |         let _: WithLifetime<'static> = self;
   |                ^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`

Rust Playground

The solution to both is impl WithLifetime<'static>.

1 Like

Thanks for the reply. This is indeed some misuse of lifetime, I'm just interested in how rustc will complain about it.
Anyway, I think the error "self escapes the method body" does make sense in the use case:

fn call1(self) {
    f(self);
}

This is because self is passed to f, and lifetime constraints are easy to understand.
But in my code, self isn't passed to foo. The argument passed to foo is of type &'static &'a u64, which, in my opinion, has nothing to do with self: it is only related to the type of self. I wonder why rustc will relate self with something that isn't actually derived from self together.

You've specified Box::leak::<'a> in the method. f(self) just is a way to trigger 'any vs 'static problem, as Box::leak<'a> brings.
As another solution in your code, remove <'a> in Box::leak::<'a> to let the compiler infer lifetimes on leak are 'static.

To reproduce the error in the second form, you can remove self but keeping <'a> on leak

impl<'a> WithLifetime<'a> {
    fn call() {
        let x = Box::leak::<'a>(Box::new(42u64));

error: lifetime may not live long enough
  --> src/lib.rs:13:17
   |
9  | impl<'a> WithLifetime<'a> {
   |      -- lifetime `'a` defined here
...
13 |         let z = foo(y);
   |                 ^^^^^^ argument requires that `'a` must outlive `'static`

This is correct, 'a just must outlive 'static to make the code work. However, what I'm interested in is how self is involved in the process that rustc finds out the lifetime error. If we do the following:

-fn call(self) {
+fn call(_: Self) {

The error message will now be easier to understand:

error[E0521]: borrowed data escapes outside of associated function
  --> src/lib.rs:13:17
   |
9  | impl<'a> WithLifetime<'a> {
   |      -- lifetime `'a` defined here
...
13 |         let z = foo(y);
   |                 ^^^^^^ argument requires that `'a` must outlive `'static`

For more information about this error, try `rustc --explain E0521`.

And if we further do this:

-fn call(_: Self) {
+fn call() {

The error message will become something that is very very easy to understand:

error: lifetime may not live long enough
  --> src/lib.rs:13:17
   |
9  | impl<'a> WithLifetime<'a> {
   |      -- lifetime `'a` defined here
...
13 |         let z = foo(y);
   |                 ^^^^^^ argument requires that `'a` must outlive `'static`

Obviously, the existence of self or Self changes some behaviors of the compiler. I'm just wondering what are the reasons behind such changes.

BTW When searching in the borrowed data escapes outside of ... issues, I found the case for functions besides for the closures and methods mentioned above:

use std::any::Any;

fn check(x: &mut dyn Any) {
    let _ = x.type_id();
}

error[E0521]: borrowed data escapes outside of function
 --> src/lib.rs:4:13
  |
3 | fn check(x: &mut dyn Any) {
  |          -  - let's call the lifetime of this reference `'1`
  |          |
  |          `x` declared here, outside of the function body
  |          `x` is a reference that is only valid in the function body
4 |     let _ = x.type_id();
  |             ^^^^^^^^^^^
  |             |
  |             `x` escapes the function body here
  |             argument requires that `'1` must outlive `'static`

And there's one interesting issue to improve the diagnostics of escape spot for methods: `[E0521]: borrowed data escapes outside of method` points to the wrong escape point · Issue #115783 · rust-lang/rust · GitHub

I mean for now, the error reporting is not bad, at least it varies in various cases, not just error: lifetime may not live long enough, which promisingly means diagnostics are handled separately.

The diagnostic is clearly built around the common case of the error being due to a reference lifetime not being long enough.[1] The general idea is that a lifetime that might be as short as the method call conflicts with some use that requires it to be different. Depending on the usage, this can be something like trying to store it in a longer-lived structure or send it to a non-scoped thread, say - some form of "escaping".

An arg of s: Self has the same diagnostic; I think it just gets omitted when the value has no name.

I agree the use of the variable name doesn't apply to your OP and is thus a diagnostic shortfall -- but not a common use case, as lifetimes are generally not annotated in function bodies (and thus need uncommon gymnastics to apply to values that don't involve the lifetime in the signature). Removing the annotation allows the example to complie.

(There are other cases I've seen where lifetimes "infect" each other in ways that would actually allow data flow, causing legitimate borrow check errors.)


  1. Probably being involved in a borrow check error generally - I have a diagnostic issue about the error being backwards for a contravariant argument somewhere. ↩︎

2 Likes

Thank you for the reply, that's really helpful. Indeed, the common case of the error is some method taking &self but incorrectly "extends" its lifetime.

Thank you for the reply. I also think the difference of error messages is good most of the time, except in a few corner cases it might be somewhat confusing :slight_smile: