Skip to content

Source of lifetime coercion is not reported starting in 1.63 #99256

Open
@mqudsi

Description

@mqudsi

When the lifetime of a value is coerced to a longer one due to the use of an associated function or trait member and the previous lifetime used by other call sites is no longer sufficient, rustc no longer includes any mention of the coercion site (and includes a red herring that forces one to go chasing after the wrong goose). This makes it much, much harder to figure out why code is not working as all the locations rustc mentions are perfectly fine and have nothing to do with the actual cause of the error.

Code

use std::path::{Path, PathBuf};

enum PolyStr<'a> {
    Str(&'a str),
}

impl<'a> From<&'static str> for PolyStr<'static> {
    fn from(s: &'static str) -> Self {
        PolyStr::Str(s)
    }
}

#[derive(Default)]
struct Ffcommand<'a> {
    inputs: Vec<&'a Path>,
    output: Option<PolyStr<'a>>,
}

impl<'a> Ffcommand<'a> {
    pub fn run(&self) -> bool {
        true
    }

    pub fn input(mut self, p: &'a Path) -> Self {
        self.inputs.push(p);
        self
    }

    pub fn output<P>(mut self, p: P) -> Self
    where
        P: Into<PolyStr<'a>> + 'a
    {
        self.output = Some(p.into());
        self
    }
}

#[derive(Default)]
struct Ffinfo<'a> {
    cmd: Ffcommand<'a>,
    path: PathBuf,
}

impl Ffinfo<'_> {
    fn foo(&self) -> bool {
        let cmd = Ffcommand::default()
            .input(&self.path)
            // uncomment the next line to see the error
            // .output("pipe:1")
            ;
            
        cmd.run()
    }
}

fn main() {
    let mut info = Ffinfo::default();
    info.path = PathBuf::from("./hello.world");
    info.foo();
}

When the commented-out line is added back in, rustc will emit the following error:

error[[E0521]](https://doc.rust-lang.org/beta/error-index.html#E0521): borrowed data escapes outside of associated function
  --> src/main.rs:46:19
   |
45 |       fn foo(&self) -> bool {
   |              -----
   |              |
   |              `self` is a reference that is only valid in the associated function body
   |              let's call the lifetime of this reference `'1`
46 |           let cmd = Ffcommand::default()
   |  ___________________^
47 | |             .input(&self.path)
   | |                              ^
   | |                              |
   | |______________________________`self` escapes the associated function body here
   |                                argument requires that `'1` must outlive `'static`

For more information about this error, try `rustc --explain E0521`.
error: could not compile `playground` due to previous error

Try on playground.

The error message points to self and .input() as the locations pertaining to this error, when in fact the error stems entirely from the usage of .output(), which coerces the lifetime to 'static due to the trait impl, at the same time as .input(), which doesn't last as long.

rustc stable (1.62) emits the following error message instead, which is infinitely more helpful:

error[[E0759]](https://doc.rust-lang.org/stable/error-index.html#E0759): `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src/main.rs:47:20
   |
45 |     fn foo(&self) -> bool {
   |            ----- this data with an anonymous lifetime `'_`...
46 |         let cmd = Ffcommand::default()
47 |             .input(&self.path)
   |                    ^^^^^^^^^^ ...is used here...
48 |             .output("pipe:1")
   |              ------ ...and is required to live as long as `'static` here

For more information about this error, try `rustc --explain E0759`.
error: could not compile `playground` due to previous error

Notice that here the correct call sites (.input() and .output()) are mentioned. In particular, the fact that .output() is where 'static is required is clearly mentioned, making it easy to figure out what is going on.

Needless to say, this is a highly stripped-down example for illustration purposes. It took me perhaps half-an-hour to chase down the actual issue in a real codebase; IIRC the .output() equivalent was part of the original code and .input() was added some months later, and there were a lot of false trails to chase down before finding the .output() lifetime coercion.

Version it worked on

It most recently worked on: 1.62.0

Version with regression

rustc --version --verbose:

1.63.0-beta.5 2022-07-10 93ef0cd7fd86d3d05cee

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-diagnosticsArea: Messages for errors, warnings, and lintsA-lifetimesArea: Lifetimes / regionsC-bugCategory: This is a bug.P-highHigh priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.WG-diagnosticsWorking group: Diagnosticsregression-from-stable-to-stablePerformance or correctness regression from one stable version to another.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions