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.
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 variableinput
, and everything else behind it, is referencing the data in output.stdout
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(())
}