J-F-Liu / pom

PEG parser combinators using operator overloading without macros.
MIT License
496 stars 30 forks source link

Static lifetime of pom::Parser does not specialize / example code should not encourage pom::Parser #54

Open mcclure opened 1 year ago

mcclure commented 1 year ago

Today I tried to use pom. I got stuck for a long time on this problem:

Say I take the sample code in the README and modify it so that instead of the input being hardcoded in the program, it comes from a string (code linked).

I get this error:

74 |         println!("{:?}", json().parse(input.as_bytes()));
   |                          -------------^^^^^^^^^^^^^^^^-
   |                          |            |
   |                          |            borrowed value does not live long enough
   |                          argument requires that `input` is borrowed for `'static`
75 |     } else {
   |     - `input` dropped here while still borrowed

I don't understand why I need to borrow because it should be okay to drop input immediately. But okay, I add the &. This doesn't work either:

error[E0597]: `input` does not live long enough
  --> src\main.rs:74:34
   |
74 |         println!("{:?}", json().parse(&input.as_bytes()));
   |                          --------------^^^^^^^^^^^^^^^^-
   |                          |             |
   |                          |             borrowed value does not live long enough
   |                          argument requires that `input` is borrowed for `'static`
75 |     } else {
   |     - `input` dropped here while still borrowed

I am new to Rust so maybe I am confused about lifetimes. But I think what is happening here is that the sample code is using pom::Parser. As the docs explain "pom::Parser<I, O> is alias of pom::parser::Parser<'static, I, O>". This means that when json() is created, the parser object is using lifetime 'static, and therefore it requires any data it parse()s to have lifetime 'static. In other words this code can only work on data that lives the entire lifetime of the application, such as an inline constant! This is not usually what you want.

I showed this to more experienced Rust programmers and they seemed surprised that the Rust compiler did not automatically specialize the 'static to a narrower lifetime. But somehow it does not.

The solution is to not use pom::Parser and instead use normal pom::parser::Parser. This requires rewriting every function signature to pass along the lifetime variable, for example fn space() -> Parser<u8, ()> becomes fn space<'a>() -> Parser<'a, u8, ()>. I made this change (code linked) and the code works, even without borrowing input.as_bytes().

I think you need to do one of the following:

  1. Make whatever change is necessary for Rust to figure out, automatically, that 'static is actually something narrower. Did this used to work at some time in the past? (I do not know if this is possible.)

  2. Make it harder to use the static pom::Parser by accident. Probably pom::Parser should be named pom::StaticParser so it is obvious it must be used with static values. Also, you should link some sample code in the documentation (such as the pom-2 code I link above) demonstrating how to use Pom with runtime values. All the sample code right now seems to use pom::Parser.

If someone copy-pastes the current sample code, like I did, they will probably waste a lot of time trying to figure out why it does not work on simple strings until they figure out they must add lifetimes. I think this is the real error being seen in #32.

J-F-Liu commented 1 year ago

Agree