如何建立与较高排名的特质限制相关的关系

huangapple go评论55阅读模式
英文:

How to establish relation with respect to higher ranked trait bounds

问题

fn static_dispatcher<'a, 'b, I>(
    tokenize: impl for<'a> Fn(&'a str) -> I,
    ifs: BufReader<File>,
) -> Result<()>
where
    I: Iterator<Item = &'b str>,
{
    ifs.lines()
        .map(|line| tokenize(&line.unwrap()).count())
        .for_each(move |n| {
            println!("{}", n);
        });
    Ok(())
}
英文:

I am trying to create a high order function which takes in (a function that takes &str and returns iterator of &str). What I am having difficulty is to relate lifetime variables of for&lt;&#39;a&gt; for the function and the return type of that function. It is hard to describe in words, so let's jump right into the code:

use std::fs::File;
use std::io::{BufRead, BufReader, Result};

fn static_dispatcher&lt;&#39;b, I&gt;(
    tokenize: impl for&lt;&#39;a&gt; Fn(&amp;&#39;a str) -&gt; I,
    ifs: BufReader&lt;File&gt;,
) -&gt; Result&lt;()&gt;
where
    I: Iterator&lt;Item = &amp;&#39;b str&gt;,
{
    ifs.lines()
        .map(|line| tokenize(&amp;line.unwrap()).count())
        .for_each(move |n| {
            println!(&quot;{n}&quot;);
        });
    Ok(())
}

fn main() -&gt; Result&lt;()&gt; {
    let ifs = BufReader::new(File::open(&quot;/dev/stdin&quot;)?);
    static_dispatcher(|line| line.split_whitespace(), ifs)
    // static_dispatcher(|line| line.split(&#39;\t&#39;), ifs)
}

The compiler complains that the lifetime relation of the tokenize's input &#39;a and output &#39;b is not specified.

  --&gt; src/main.rs:21:30
   |
21 |     static_dispatcher(|line| line.split_whitespace(), ifs)
   |                        ----- ^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `&#39;1` must outlive `&#39;2`
   |                        |   |
   |                        |   return type of closure is SplitWhitespace&lt;&#39;2&gt;
   |                        has type `&amp;&#39;1 str`
   |
   = note: requirement occurs because of the type `SplitWhitespace&lt;&#39;_&gt;`, which makes the generic argument `&#39;_` invariant
   = note: the struct `SplitWhitespace&lt;&#39;a&gt;` is invariant over the parameter `&#39;a`
   = help: see &lt;https://doc.rust-lang.org/nomicon/subtyping.html&gt; for more information about variance

I want to specify 'a = 'b, but I can't because 'a comes from for&lt;&#39;a&gt;, which is not visible for the type I.

I also tried

fn static_dispatcher&lt;&#39;a, I&gt;(
    tokenize: impl Fn(&amp;&#39;a str) -&gt; I,
    ifs: BufReader&lt;File&gt;,
) -&gt; Result&lt;()&gt;
where
    I: Iterator&lt;Item = &amp;&#39;a str&gt;,

but this does not work either b/c the lifetime of tokenize argument must be generic, i.e., must be used with for &lt;&#39;a&gt;.

How can I fix this problem?

答案1

得分: 0

你可以使用自定义特性:

trait IteratorReturningSingleArgFn<Arg>: Fn(Arg) -> Self::Iter {
    type Iter: Iterator<Item = Self::Item>;
    type Item;
}

impl<Arg, F, Iter> IteratorReturningSingleArgFn<Arg> for F
where
    F: Fn(Arg) -> Iter,
    Iter: Iterator
{
    type Iter = Iter;
    type Item = Iter::Item;
}

fn static_dispatcher(
    tokenize: impl for<'a> IteratorReturningSingleArgFn<&'a str, Item = &'a str>,
    ifs: BufReader<File>,
) -> Result<()> {
    // ...
}

这让类型推断变得复杂(我可以理解),并要求你在static_dispatcher()内部的闭包中显式指定类型:

    ifs.lines()
        .map(|line: Result<String>| tokenize(&line.unwrap()).count())

以及在main()中:

static_dispatcher(|line: &str| line.split_whitespace(), ifs)

但不幸的是它仍然不起作用:由于闭包中混杂的生命周期推断,编译器不使用HRTB生命周期,而是尝试应用具体的生命周期。这可以通过使用夜间版的closure_lifetime_binder特性来修复:

#![feature(closure_lifetime_binder)]

static_dispatcher(for<'a> |line: &'a str| -> std::str::SplitWhitespace<'a> { line.split_whitespace() }, ifs)

或者通过使用函数而不是闭包来修复:

fn tokenize(line: &str) -> impl Iterator<Item = &str> {
    line.split_whitespace()
}
static_dispatcher(tokenize, ifs)
英文:

You can use a custom trait:

trait IteratorReturningSingleArgFn&lt;Arg&gt;: Fn(Arg) -&gt; Self::Iter {
    type Iter: Iterator&lt;Item = Self::Item&gt;;
    type Item;
}

impl&lt;Arg, F, Iter&gt; IteratorReturningSingleArgFn&lt;Arg&gt; for F
where
    F: Fn(Arg) -&gt; Iter,
    Iter: Iterator
{
    type Iter = Iter;
    type Item = Iter::Item;
}

fn static_dispatcher(
    tokenize: impl for&lt;&#39;a&gt; IteratorReturningSingleArgFn&lt;&amp;&#39;a str, Item = &amp;&#39;a str&gt;,
    ifs: BufReader&lt;File&gt;,
) -&gt; Result&lt;()&gt; {
    // ...
}

This makes inference mad (and I can understand it 如何建立与较高排名的特质限制相关的关系 ) and requires you to specify the type in the closure inside static_dispatcher() explicitly:

    ifs.lines()
        .map(|line: Result&lt;String&gt;| tokenize(&amp;line.unwrap()).count())

And in main():

static_dispatcher(|line: &amp;str| line.split_whitespace(), ifs)

But unfortunately it still doesn't work: because of the nasty lifetime inference in closures, the compiler doesn't use HRTB lifetime here and instead try to apply concrete lifetimes. This can be fixed by using the nightly closure_lifetime_binder feature:

#![feature(closure_lifetime_binder)]

static_dispatcher(for&lt;&#39;a&gt; |line: &amp;&#39;a str| -&gt; std::str::SplitWhitespace&lt;&#39;a&gt; { line.split_whitespace() }, ifs)

Or by using a function instead of a closure:

fn tokenize(line: &amp;str) -&gt; impl Iterator&lt;Item = &amp;str&gt; {
    line.split_whitespace()
}
static_dispatcher(tokenize, ifs)

huangapple
  • 本文由 发表于 2023年2月6日 07:24:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/75356212.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定