是否有任何 rust 函数用于包装依赖于引用的迭代器,以便包装器包含引用?

Are there any rust functions for wrapping an iterator that is dependent on a reference so the wrapper contains the referent?

提问人:true equals false 提问时间:4/12/2023 最后编辑:true equals false 更新时间:4/12/2023 访问量:77

问:

在这种情况下,我想从标准输入中读取整数,以便它们用空格换行符分隔。我的第一次尝试类似于以下代码:

fn splitter(x: String) -> impl Iterator<Item=&'static str> {
    x.as_str().split_whitespace()
}

fn valuereader<A: std::str::FromStr>() -> impl Iterator<Item=A> 
where <A as std::str::FromStr>::Err: std::fmt::Debug
{
    let a = std::io::stdin().lines();
    let b = a.map(Result::unwrap);
    let c = b.flat_map(splitter);
    c.map(|x|x.parse().expect("Not an integer!"))
}

fn main() {
    let temp: Vec<usize> = valuereader().collect();
    println!("{:?}", temp);
}

问题是想要一个 ,但返回一个拥有的 .我不想使用 ,因为我不想分配临时向量。split_whitespace&strstd::io::stdin().lines()Stringx.as_str().split_whitespace().collect()

我能想到的最好的解决方案是使用一个包装器,其中包含拥有的和依赖于 的迭代器,使用不安全的代码。包装器的实现只是依赖于 的迭代器的包装器。结果是这样的:StringStringIteratorString

mod move_wrapper {
    use std::pin::Pin;
    pub fn to_wrapper<'b, A: 'b, F, B: 'b> (a: A, f: F) -> Wrapper<A,B>
    where
        F: FnOnce (&'b A) -> B
    {
        let contained_a = Box::pin(a);
        // Here is the use of unsafe. It is necessary to create a reference to a that can live as long as long as needed.
        // This should not be dangerous as no-one outside this module will be able to copy this reference, and a will live exactly as long as b inside Wrapper.
        let b = f(unsafe{&*core::ptr::addr_of!(*contained_a)});
        Wrapper::<A,B> {_do_not_use:contained_a, dependent:b}
    }

    pub struct Wrapper<A,B> {
        _do_not_use: Pin<Box<A>>,
        dependent: B
    }

    impl<A,B: Iterator> Iterator for Wrapper<A,B>
    {
        type Item = B::Item;
        fn next(&mut self) -> Option<Self::Item> {
            self.dependent.next()
        }
    }
}

fn splitter(x: String) -> impl Iterator<Item=&'static str> {
    move_wrapper::to_wrapper(x, |a|a.as_str().split_whitespace())
}

fn valuereader<A: std::str::FromStr>() -> impl Iterator<Item=A> 
where <A as std::str::FromStr>::Err: std::fmt::Debug
{
    let a = std::io::stdin().lines();
    let b = a.map(Result::unwrap);
    let c = b.flat_map(splitter);
    c.map(|x|x.parse().expect("Not an integer!"))
}

fn main() {
    let temp: Vec<usize> = valuereader().collect();
    println!("{:?}", temp);
}

现在来谈谈实际问题。如果可能的话,您将如何在不使用任何不安全代码的情况下尽可能惯用地做到这一点(这里调用的函数是否存在)?我是否编写了安全不安全的代码?有没有办法让我的工作适合所有特征,而不仅仅是?to_wrapperWrapperIterator

编辑

更清楚地说,这个问题是关于创建一个方法,你可以随时应用,将所有权授予需要引用的东西,而不是关于如何从标准输入读取并解析为整数。

Rust 迭代器 不安全 的标准库

评论

0赞 jthulhu 4/12/2023
不安全代码是指上下文中的代码,即已显式标记为不安全的代码。所有这些都表明,没有意外编写不安全代码的“风险”。unsafe
0赞 true equals false 4/12/2023
@jthulhu 我有没有说过我不小心写了不安全的代码有什么风险?在这种情况下,我是故意这样做的。问题是我是否设法以安全的方式做到这一点。
0赞 PitaJ 4/12/2023
在你的代码中很难发现。你至少应该发表评论,解释为什么你认为它是合理的。unsafe// SAFETY
0赞 PitaJ 4/12/2023
顺便说一句:很慢,因为它必须为每行分配一个新的,最好直接使用。但是,当然,您不能使用.lines()Stringread_lineIterator
0赞 ShadowRanger 4/12/2023
@PitaJ:虽然OP可能仍然想这样做;如果他们如此关心由于分配临时而令人反感的性能,那么每行的分配活动可能也是一个问题。请注意,这几乎可以肯定是过早的优化;即使在最快的现代硬盘上,I/O 也几乎肯定会淹没任何分配器的开销,所以我只会做方便的事情,直到我有证据证明它导致了问题。x.as_str().split_whitespace().collect()VecString

答:

0赞 drewtato 4/12/2023 #1

您无法在安全 rust 中创建自引用类型,如此处所述。不幸的是,解决此问题仍然会留下下一个问题。

返回不能超过迭代器的项的迭代器是不可能的,此处对此进行了解释。你的限制性更强:它试图创建在获取下一个项目时不存在的项目,这意味着你需要一个借用迭代器才能使其处于当前状态。

使用一些嵌套的 for 循环创建你的是非常容易的:Vec

fn valuereader<A: FromStr>() -> Vec<A>
where
    A::Err: Debug,
{
    let mut v = Vec::new();
    for line in std::io::stdin().lines() {
        for word in line.unwrap().split_whitespace() {
            let a = word.parse().unwrap();
            v.push(a);
        }
    }
    v
}

但是,这并不是很有启发性,并且会创建许多临时分配,尤其是当您只需要一个迭代器而不是 .Vec

为了使你的原始想法以惯用的方式工作,你需要一个迭代器来生成拥有的项。幸运的是,你的最终项目类型是(或任何必须拥有的 ,所以你可以创建一个迭代器来创建这些。这只会分配一个 ,它将增长到最长行的长度。(游乐场)usizeparseString

use std::fmt::Debug;
use std::io::BufRead;
use std::marker::PhantomData;
use std::str::FromStr;

#[derive(Debug, Clone)]
struct ValueReader<B, V> {
    // The underlying BufRead
    buffer: B,
    // The current line being read
    line: String,
    // The current offset into the current line
    index: usize,
    // The type being parsed
    _value_type: PhantomData<V>,
}

impl<B, V> ValueReader<B, V> {
    fn new(b: B) -> Self {
        Self {
            buffer: b,
            line: String::new(),
            index: 0,
            _value_type: PhantomData,
        }
    }

    fn value(&mut self) -> Option<V>
    where
        V: FromStr,
        V::Err: Debug,
        B: BufRead,
    {
        loop {
            // Check if line is consumed, or the iterator just started
            if self.line.is_empty() {
                let bytes = self.buffer.read_line(&mut self.line).unwrap();
                // Buffer is completely consumed
                if bytes == 0 {
                    return None;
                }
            }

            let unconsumed = self.line[self.index..].trim_start();
            self.index = self.line.len() - unconsumed.len();

            let Some(word) = unconsumed.split_whitespace().next() else {
                // Line is consumed, reset to original state
                self.index = 0;
                self.line.clear();
                continue;
            };
            self.index += word.len();

            return Some(word.parse().unwrap());
        }
    }
}

impl<B, V> Iterator for ValueReader<B, V>
where
    V: FromStr,
    V::Err: Debug,
    B: BufRead,
{
    type Item = V;

    fn next(&mut self) -> Option<Self::Item> {
        self.value()
    }
}

通过使用并且一次只读取一个单词,缩短 .报告错误而不是解包也是明智的。fill_bufconsumeString

评论

0赞 true equals false 4/12/2023
通过我对问题的编辑,应该很清楚这并不能真正回答这个问题。但它至少回答了老问题的很大一部分!