英文:
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<'a>
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<'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(())
}
fn main() -> Result<()> {
let ifs = BufReader::new(File::open("/dev/stdin")?);
static_dispatcher(|line| line.split_whitespace(), ifs)
// static_dispatcher(|line| line.split('\t'), ifs)
}
The compiler complains that the lifetime relation of the tokenize
's input 'a
and output 'b
is not specified.
--> src/main.rs:21:30
|
21 | static_dispatcher(|line| line.split_whitespace(), ifs)
| ----- ^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure is SplitWhitespace<'2>
| has type `&'1 str`
|
= note: requirement occurs because of the type `SplitWhitespace<'_>`, which makes the generic argument `'_` invariant
= note: the struct `SplitWhitespace<'a>` is invariant over the parameter `'a`
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
I want to specify 'a = 'b, but I can't because 'a comes from for<'a>
, which is not visible for the type I
.
I also tried
fn static_dispatcher<'a, I>(
tokenize: impl Fn(&'a str) -> I,
ifs: BufReader<File>,
) -> Result<()>
where
I: Iterator<Item = &'a str>,
but this does not work either b/c the lifetime of tokenize
argument must be generic, i.e., must be used with for <'a>
.
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<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<()> {
// ...
}
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<String>| tokenize(&line.unwrap()).count())
And in main()
:
static_dispatcher(|line: &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<'a> |line: &'a str| -> std::str::SplitWhitespace<'a> { line.split_whitespace() }, ifs)
Or by using a function instead of a closure:
fn tokenize(line: &str) -> impl Iterator<Item = &str> {
line.split_whitespace()
}
static_dispatcher(tokenize, ifs)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论