如何从字符串创建迭代器并将其存储在结构中?

How to create an iterator from string _and_ store it in a struct?

我正在尝试为我的结构创建一个构造函数,它将存储一个迭代器 String 从文件中读取。问题是,一旦函数 returns,String 被删除,编译器就会抱怨 new() returns a value referencing data owned by the current function。有没有办法以某种方式将 String 与结构相关联,以便它不会在 return 之后被删除?

我想我理解这里的抱怨,但我不知道如何处理它,因为我希望构造函数同时处理文件读取和迭代器创建。

pub struct CharStream<'a> {
    input: std::str::Chars<'a>,
    filename: String,
}
impl<'a> CharStream<'a> {
    pub fn new(filename: String) -> CharStream<'a> {
        let mut file = File::open(&filename).unwrap();
        let mut input = String::new();
        file.read_to_string(&mut input);
        CharStream {
            input: input.chars(), // Create an iterator over `input`
            filename: filename,
        }
        // `input` is dropped here
    }
}

String::chars() 返回的迭代器仅在原始字符串 input 存在期间有效。 inputnew 的末尾被删除,因此无法从函数返回迭代器。

为了解决这个问题,您还想将 input 字符串存储在结构中,但是您 运行 陷入了其他问题,因为一个结构成员无法引用同一结构的另一个成员。原因之一是该结构将变得不可移动,因为移动它会使引用无效。

最简单的解决方案可能是将 char 收集到 Vec<char> 中并将该向量存储在 CharStream 中。然后添加一个 usize 索引并编写您自己的 Iterator<Item = char> 实现。

另一种方法(内存效率更高)是存储 String 本身,并根据需要创建 Chars 迭代器,但这当然会导致不同的 API .

涉及 RefCell 或类似包装器的解决方案可能也是可能的。

我会将 CharStream 重命名为 FileContents 并让它拥有文件的 filenamecontents 作为 Strings。然后,当您需要生成 TokenIter 以迭代 contents 中的 char 块时,您可以按需创建 Chars<'a> 并将其传递给 TokenIter 然后。完整示例:

use std::fs;
use std::str::Chars;

struct FileContents {
    filename: String,
    contents: String,
}

impl FileContents {
    fn new(filename: String) -> Self {
        let contents = fs::read_to_string(&filename).unwrap();
        FileContents { filename, contents }
    }
    fn token_iter(&self) -> TokenIter<'_> {
        TokenIter {
            chars: self.contents.chars(),
        }
    }
}

struct TokenIter<'a> {
    chars: Chars<'a>,
}

struct Token; // represents some chunk of chars

impl<'a> Iterator for TokenIter<'a> {
    type Item = Token;
    fn next(&mut self) -> Option<Self::Item> {
        self.chars.next(); // call as many times as necessary to create token
        Some(Token) // return created token here
    }
}

fn example(filename: String) {
    let file_contents = FileContents::new(filename);
    let tokens = file_contents.token_iter();
    for token in tokens {
        // more processing here
    }
}

playground