nom parser borrow checker issue
Asked Answered
W

2

7

I have this Rust program using nom 4.2.2. (I have taken the liberty of expanding the nom parser function.)

extern crate failure;
extern crate nom;

use failure::Error;
use std::fs::File;
use std::io::Read;

fn nom_parser(i: &[u8]) -> ::nom::IResult<&[u8], String, u32> {
    { ::nom::lib::std::result::Result::Ok((i, ("foo".to_owned()))) }
}

fn my_parser(buf: &[u8]) -> Result<(&[u8], String), Error> {
  Ok((buf, "foo".to_owned()))
}

fn main() -> Result<(), Error> {
  let handler = |mut entries: String| { entries.clear() };
  loop {
    let mut buf = Vec::new();
    File::open("/etc/hosts")?.read_to_end(&mut buf)?;
    let res = nom_parser(&buf)?.1;
    // let res = my_parser(&buf)?.1;
    handler(res);
  }
}

Compiling this program with rustc 1.33.0 (2aa4c46cf 2019-02-28) yields the following issue:

error[E0597]: `buf` does not live long enough
  --> nom-parsing/src/main.rs:21:26
   |
21 |     let res = nom_parser(&buf)?.1;
   |               -----------^^^^-
   |               |          |
   |               |          borrowed value does not live long enough
   |               argument requires that `buf` is borrowed for `'static`
...
24 |   }
   |   - `buf` dropped here while still borrowed

Switching to the commented out version of the parser compiles just fine. How are my_parser and nom_parser different? Who is borrowing buf? How should I change the program to placate the borrow checker?

Weaks answered 15/3, 2019 at 14:30 Comment(0)
I
12
let res = nom_parser(&buf)?.1;
                          ^ here

You are using the ? operator to propagate the error out of main. The IResult<&[u8], String, u32> = Result<(&[u8], String), nom::Err<&[u8], u32>>. So in case of error the &buf is returned as part of it, so it must stay alive even after main function exits, but it won't because buf is local variable inside main.

In your case the nom_parser never returns error, but the validation only cares about the types and function signatures.

To fix it, you should process the error somehow before propagating it up. For example:

let res = nom_parser(&buf).map_err(|_| failure::format_err!("Parsing failed!"))?.1;

Note that Err in the IResult is not always hard error. It could be nom::Err::Incomplete, meaning that the parsing may succeed if more data is supplied, or nom::Err::Error meaning that the input was not matched by the parser (so perhaps another parser in alt! could succeed), or nom::Err::Failure, meaning that something went really wrong during parsing. Depending on the situation, you may consider them all as failure, or handle them differently.

Indigent answered 15/3, 2019 at 15:0 Comment(1)
Thank you, that does indeed seem to be the case! Simply mapping the error into a new error with the old error as a string did the trick. The real code uses CompleteStr so it should not have a "problem" with incomplete data.Weaks
J
0

The problem appears to be in IResult<I, O, E = u32>, which expends to Result<(I, O), Err<I, E>>

As you can see, when you use the ?, the Err that you may return can still contain a reference to the type I, which is your &[u8], and return from your function.

The only way for the function to return this reference would be that the reference has a lifetime that doesn't end with the function, 'static

A simple solution to your problem would be to change the &[u8] to a Vec<u8>, even if I'm not sure what you're trying to do with it.

Jeu answered 15/3, 2019 at 15:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.