nomcode (it is a parser combinator library for Rust). To my surprise I discovered that there is no combinator for creating a parser which always succeeds returning a certain given value. At least not without using macros which is discouraged in nom v5. That combinator would be something like
purein Haskell Parsec. It's not very useful on its own, but can be used as part of other combinators, say providing a default alternative for
successto nom. After looking at the library code, I realised that it uses closures quite heavily and I didn't use them much in Rust, so I had some questions. Here is my version of
successbasically copy-pasted from a similar combinator
Earguments here (input and error types), we can just rewrite it like this, omitting irrelevant details:
val? After all it looks like l I have a value and just want to pass the ownership to the parser, no need to clone anything.
moveclosure, but return type of the function is
impl Fn(something)and not
impl FnOnce(something)I thought that when we use
movethen we move the captured environment into the closure and
FnOncetrait matches that behaviour.
moveor change the type to
Clonei.e. to remove any of those things which I didn't understand and still make it work? Are they actually necessary?
movedetermines how captured variables are moved into the returned closure. Then returned
impl Fn/FnMut/FnOnceputs restrictions on how they are used inside that closure (which in turn defines whether the closure can be used once or more). We can
moveinto closure but still only use the captured values by reference and return
impl Fnto allow multiple calls of the returned closure. And yes, everything in the code above was necessary :)
T) is determined by the usage of the captured variables inside the closure. Or it can be forced to be
T, i.e. to passing the ownership to the closure, by using
moveclosure, it only captures a single variable
valof generic type
self.valwhich is behind a reference. So we can't do that for
&selfin the call method, so we can pass the ownership further with
Ok((input, self.val)). However, using FnOnce means that we can use the resulting closure just once, perhaps that's not we want in a parser. Why? I don't know for sure, but I suspect that we may want to do certain lookaheads while parsing and this means invoking the closure and then backtracking if it doesn't match. But this mean we would have spoiled the parser closure and can't use it again for another run. So let's assume that our parsers should be
Fns which is indeed Nom's API.
movecan coexist with returning
Fnis also clear now.
movedetermines how values are captured into that closure-structure, i.e. which type they have there (refs, mutable refs or owned values) while
Fn/FnOnce/FnMuttrait is determined by the way they are used in that closure (in our
callmethod above). For example, we can
moveinto a closure but still only use the captured values by reference and return
impl Fnto allow multiple calls of the returned closure.
moveclosure here if we only access the variable by reference anyway. The explanation is that we are returning this closure, but
valwill be dropped immediately on return and the closure can't outlive it. In other words, if we didn't use
move, we would have
val: &'a Oin that closure structure field. But that reference would immediately become invalid since when we return our closure,
valis dropped, so no references to that are allowed, including inside our returned closure. So that won't work and we would get "val does not live long enough" error message.
successcombinator were actually needed.