Lifetime issue using `anyhow` crate in combination with `nom` crate
Asked Answered
C

1

5

I'm having an issue with lifetimes that I'm not sure how to solve, since it seems like the change I'm making should be trivial with regards to lifetimes.

Given:

use anyhow::Context;
use nom::{IResult, bytes::complete::tag};

The following code compiles:

let input = std::str::from_utf8(&output.stdout).unwrap();

let mut lines = input.lines();
let branch_line = lines.next().context("no output from `git status`")?;
let branch: IResult<&str, &str> = tag("On branch ")(branch_line);
let (branch, _) = branch.expect("failed to get name of current branch");

After changing the expect in the final line to context, the code no longer compiles:

let input = std::str::from_utf8(&output.stdout).unwrap();

let mut lines = input.lines();
let branch_line = lines.next().context("no output from `git status`")?;
let branch: IResult<&str, &str> = tag("On branch ")(branch_line);
let (branch, _) = branch.context("failed to get name of current branch")?;
error[E0597]: `output.stdout` does not live long enough
   --> src/status.rs:303:41
    |
303 |         let input = std::str::from_utf8(&output.stdout).unwrap();
    |                                         ^^^^^^^^^^^^^^ borrowed value does not live long enough
...
307 |         let branch: IResult<&str, &str> = tag("On branch ")(branch_line);
    |                     ------------------- type annotation requires that `output.stdout` is borrowed for `'static`
...
436 |     }
    |     - `output.stdout` dropped here while still borrowed

Looking at the docs for anyhow, it doesn't appear to me like it should introduce any lifetime bounds on the &output.stdout.

fn context<C>(self, context: C) -> Result<T, Error>
where
    C: Display + Send + Sync + 'static, 

Scratching my head. Still new to lifetimes.

Chromous answered 26/8, 2022 at 16:14 Comment(1)
Please provide a minimal reproducible example. You say it compiles for you; it doesn't for me. It says cannot find value 'output' in this scope. Apart of that, you can't directly execute code in Rust, you need to write it in a main() function. I know this seems nitpicky, but it's a way of respecting the SO community; the time that you saved by not writing a minimal reproducible example means that now I have to do it before I can start answering your question.Chaperone
C
7

The problem is the type of branch, which is IResult<&str, &str>.

If you look at the implementation, you can see that this is an alias for IResult<&str, &str, nom::error::Error<&str>>, which again is an alias for Result<(&str, &str), nom::internal::err<nom::error::Error<&str>>>.

This seems complicated and all, but the main point I'm trying to make is that branch is a Result type, and the Err case of it has the type nom::internal::err<nom::error::Error<&str>>. With other words, the error carries a &str.

This is on purpose, because ownership is a big problem for nom. This is strongly related to these known problems of the current borrow checker. Nom solves this by returning ownership back through the Err type.

That sadly means that it is incompatible with anyhow. The error types of nom are meant to be consumed by nom, or at least manually converted into something else before raised into user code.

To explain the exact error you are getting:

  • output.stdout is a local variable
  • input, and everything else behind it, is referencing the data in output.stdout
  • The Err variant of branch is still referencing output.stdout
  • .context(), or to be more precise, the ? behind it, tries to return the Err variant out of the function, which fails because it still references output.stdout, and the reference would then outlive the data it references.

This is not a problem for next().context(), because the None value of next() does not carry a reference to output.stdout.

One way to fix this is to break the reference by converting the &str from the Err type to an owned String:

use anyhow::{Context, Result};
use nom::{bytes::complete::tag, IResult};

fn main() -> Result<()> {
    let input = "aaaaaa".to_string();

    let mut lines = input.lines();
    let branch_line = lines.next().context("no output from `git status`")?;
    let branch: IResult<&str, &str> = tag("On branch ")(branch_line);
    let (branch, _) = branch
        .map_err(|e| e.to_owned())
        .context("failed to get name of current branch")?;

    Ok(())
}
Chaperone answered 26/8, 2022 at 20:46 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.