74304


Rust のパーザコンビネータライブラリ combine を使おうとしていますが、ライフタイム関連のエラーに悩まされています。

数字列を整数に変換するパーザを書きたいのですが、  
次の関数定義が "lifetime of return value does not outlive the function call"
というエラーになってしまいます：

[code]

    extern crate combine;
    use combine::{
        error::ParseError,
        Parser,
        parser::regex::find,
        stream::RangeStream,
    };
    use regex::Regex;
    
    fn re(pattern: &str) -> Regex {
        Regex::new(pattern).unwrap()
    }
    
    pub fn integer<'a, I>() -> impl Parser<I, Output = i32>
        where I: RangeStream<Token = char, Range=&'a str>,
              I::Error: ParseError<I::Token, I::Range, I::Position>,
    {
        let token = find(re("^[0-9]+"));
        token.map(|v: &'a str| v.parse::<i32>().unwrap())
    }
    
[/code]

数値への変換をやめると、（要件は満たさなくなりますが）エラーは出なくなります：

[code]

    pub fn integer<'a, I>() -> impl Parser<I, Output = /*i32*/ &'a str>
        where I: RangeStream<Token = char, Range=&'a str>,
              I::Error: ParseError<I::Token, I::Range, I::Position>,
    {
        let token = find(re("^[0-9]+"));
        // token.map(|v: &'a str| v.parse::<i32>().unwrap())
        token
    }
    
[/code]

以下のことがわからず困っています：

  * 前者で何がライフタイム規則に違反しているのか
  * なぜ前者は違反で後者は違反でないのか
  * 前者をどう直せばいいか

どなたかわかりますでしょうか？

combine のバージョンは 4.5.1 です。


CC BY-SA 4.0
2021-02-27T13:22:12.623
74302
2021-02-27T14:54:38.030
9245
question
rust combine
combine ライブラリ使用時のライフタイムエラー


ライフタイムのアノテーションを次のように2か所追加することでコンパイルが通るようになりました:

[code]

    pub fn integer<'a, I>() -> impl Parser<I, Output = i32> + 'a // + 'aを追加
    where
        I: RangeStream<Token = char, Range = &'a str> + 'a, // + 'aを追加
        I::Error: ParseError<I::Token, I::Range, I::Position>,
    {
        let token = find(re("^[0-9]+"));
        token.map(|v: &'a str| v.parse::<i32>().unwrap())
    }
    
[/code]

* * *

まず、元のコードでエラーが生じた理由について説明します0。  
元のコードに対してRustコンパイラが送出するエラーメッセージは次の通りです。

[code]

    error[E0482]: lifetime of return value does not outlive the function call
     --> src/main.rs:9:28
      |
    9 | pub fn integer<'a, I>() -> impl Parser<I, Output = i32>
      |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
    note: the return value is only valid for the lifetime `'a` as defined on the function body at 9:16
     --> src/main.rs:9:16
      |
    9 | pub fn integer<'a, I>() -> impl Parser<I, Output = i32>
      |                ^^
    
[/code]

このエラーの根本的な原因は戻り値の位置の`impl Trait`構文では **依存するライフタイム引数を明示的に指定しなければならない**
という言語仕様にあります1。  
関数`integer`では、`token.map`に渡したクロージャにライフタイム`'a`が出現しますから、(コンパイラが生成する)戻り値型は`'a`を引数として含めなければいけません。

プログラムを使って説明すると、次のようになるでしょうか。

[code]

    // クロージャで'aを使うのでジェネリクス引数'aが必要
    struct ReturnType<'a, I> {
        // token.map(|v: &'a str| v.parse::<i32>().unwrap())の型
        map: Map<Find<Regex, I>, Closure<'a>>,
    }
    
[/code]

一方で、戻り値型に`impl Parser<I, Output =
i32>`と記述した場合、上述の言語仕様からRustコンパイラは「戻り値型は型引数の`I`にだけ依存するのだろう」と仮定してコンパイルを行います(上の例で言うと`struct
ReturnType<I> { ... }`と宣言されることになります)。  
しかし、実際の戻り値はクロージャを経由して`'a`に依存しますから、コンパイルエラーに繋がるものと思われます。  
したがって、戻り値に`+ 'a`と指定を追加してあげることでこのエラーは解決するのですが、また別のエラーが発生しました。

次のエラーメッセージはこの通りです。

[code]

    error[E0309]: the parameter type `I` may not live long enough
     --> src/main.rs:9:28
      |
    9 | pub fn integer<'a, I>() -> impl Parser<I, Output = i32> + 'a
      |                    -       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `combine::parser::combinator::Map<Find<regex::Regex, I>, [closure@src/main.rs:15:15: 15:53]>` will meet its required lifetime bounds
      |                    |
      |                    help: consider adding an explicit lifetime bound...: `I: 'a`
    
[/code]

このエラーは型引数`I`が`'a`よりも長く生存しなければならないと言っています。  
combineのトレイトが複雑すぎてよく分からないのですが、`RangeStream<Token = char, Range = &'a
str>`は`&'a str`のチャンクを生成できることを表しているようなので妥当でしょう。  
このエラーを修正するにはコンパイラの指示通り`+ 'a`を`I`のトレイトバウンドに追加してあげればOKです。

結果としてこれら2つの修正によって冒頭の通りコンパイルを通すことができました。

* * *

0: 正しさにあまり自信が無いのでぜひご指摘ください。

1: [RFC 1951](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-
impl-trait.md#scoping-for-type-and-lifetime-parameters)にて、戻り値の位置の`impl
Trait`型は

  1. 型引数は全てキャプチャする(in scope)
  2. ライフタイム引数は`+ 'a`で指定したもののみキャプチャする

と記述されています。日本語では[impl
Traitの解説記事](https://qnighy.hatenablog.com/entry/2018/01/28/220000)の「ライフタイムに関する仮定」節に同じ内容が少し書かれています。


CC BY-SA 4.0
2021-02-27T14:54:38.030
74304
2021-02-27T14:54:38.030
8573
74302
answer