借用的值不够长(BufReader lines()到String的迭代器)

时间:2017-06-13 00:05:32

标签: rust borrow-checker

使用此示例代码:

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

type BoxIter<T> = Box<Iterator<Item=T>>;

fn tokens_from_str<'a>(text: &'a str) 
-> Box<Iterator<Item=String> + 'a> {
    Box::new(text.lines().flat_map(|s|
        s.split_whitespace().map(|s| s.to_string())
    ))
}

// Returns an iterator of an iterator. The use case is a very large file where
// each line is very long. The outer iterator goes over the file's lines.
// The inner iterator returns the words of each line.
pub fn tokens_from_path<P>(path_arg: P) 
-> BoxIter<BoxIter<String>>
where P: AsRef<Path> {
    let reader = reader_from_path(path_arg);
    let iter = reader.lines()
        .filter_map(|result| result.ok())
        .map(|s| tokens_from_str(&s));
    Box::new(iter)
}

fn reader_from_path<P>(path_arg: P) -> BufReader<File>
where P: AsRef<Path> {
    let path = path_arg.as_ref();
    let file = File::open(path).unwrap();
    BufReader::new(file)
}

我收到此编译器错误消息:

rustc 1.18.0 (03fc9d622 2017-06-06)
error: `s` does not live long enough
  --> <anon>:23:35
   |
23 |         .map(|s| tokens_from_str(&s));
   |                                   ^- borrowed value only lives until here
   |                                   |
   |                                   does not live long enough
   |
   = note: borrowed value must be valid for the static lifetime...

我的问题是:

  • 如何修复(如果可能,不更改功能签名?)

  • 有关更好的函数参数和返回值的任何建议吗?

3 个答案:

答案 0 :(得分:2)

一个问题是.split_whitespace()需要引用,并且不拥有其内容。因此,当您尝试使用拥有的对象构建SplitWhitespace对象时(当您调用.map(|s| tokens_from_str(&s))时会发生这种情况),删除字符串s,而SplitWhitespace仍在尝试参考它。我通过创建一个结构来快速修复此问题,该结构取得String的所有权并按需生成SplitWhitespace

use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::iter::IntoIterator;
use std::str::SplitWhitespace;

pub struct SplitWhitespaceOwned(String);

impl<'a> IntoIterator for &'a SplitWhitespaceOwned {
    type Item = &'a str;
    type IntoIter = SplitWhitespace<'a>;
    fn into_iter(self) -> Self::IntoIter {
        self.0.split_whitespace()
    }
}

// Returns an iterator of an iterator. The use case is a very large file where
// each line is very long. The outer iterator goes over the file's lines.
// The inner iterator returns the words of each line.
pub fn tokens_from_path<P>(path_arg: P) -> Box<Iterator<Item = SplitWhitespaceOwned>>
    where P: AsRef<Path>
{
    let reader = reader_from_path(path_arg);
    let iter = reader
        .lines()
        .filter_map(|result| result.ok())
        .map(|s| SplitWhitespaceOwned(s));
    Box::new(iter)
}

fn reader_from_path<P>(path_arg: P) -> BufReader<File>
    where P: AsRef<Path>
{
    let path = path_arg.as_ref();
    let file = File::open(path).unwrap();
    BufReader::new(file)
}

fn main() {
    let t = tokens_from_path("test.txt");

    for line in t {
        for word in &line {
            println!("{}", word);
        }
    }
}

答案 1 :(得分:2)

免责声明:框架挑战

处理大文件时,最简单的解决方案是使用Memory Mapped Files

也就是说,告诉操作系统您希望整个文件可以在内存中访问,并且由它来处理文件内部和内存的分页部分。

一旦关闭,您的整个文件都可以&[u8]&str(方便的时候)访问,您可以轻松访问它的切片。

它可能并不总是最快的解决方案;它当然是最简单的。

答案 2 :(得分:0)

这里的问题是你使用to_string()将每个项目变成一个拥有的值,这是懒惰的。由于它是懒惰的,因此在返回的迭代器状态中仍然使用to_string之前的值(&str),因此无效(因为源字符串在您的{{ {1}}闭包返回)。

天真的解决方案

这里最简单的解决方案是删除迭代器的那部分的惰性求值,并在分配行后立即分配所有令牌。这不会那么快,并且会涉及额外的分配,但是对当前函数的改动很小,并保持相同的签名:

map

此解决方案适用于任何小型工作负载,并且它将仅为该线路同时分配大约两倍的内存。这会带来性能损失,但除非你有10mb +行,否则它可能无所谓。

如果您选择此解决方案,我建议您更改// Returns an iterator of an iterator. The use case is a very large file where // each line is very long. The outer iterator goes over the file's lines. // The inner iterator returns the words of each line. pub fn tokens_from_path<P>(path_arg: P) -> BoxIter<BoxIter<String>> where P: AsRef<Path> { let reader = reader_from_path(path_arg); let iter = reader.lines() .filter_map(|result| result.ok()) .map(|s| { let collected = tokens_from_str(&s).collect::<Vec<_>>(); Box::new(collected.into_iter()) as Box<Iterator<Item=String>> }); Box::new(iter) } 的功能签名以直接返回tokens_from_path

BoxIter<String>

替代方案:解耦pub fn tokens_from_path<P>(path_arg: P) -> BoxIter<String> where P: AsRef<Path> { let reader = reader_from_path(path_arg); let iter = reader.lines() .filter_map(|result| result.ok()) .flat_map(|s| { let collected = tokens_from_str(&s).collect::<Vec<_>>(); Box::new(collected.into_iter()) as Box<Iterator<Item=String>> }); Box::new(iter) } tokens_from_path

原始代码不起作用,因为您试图将借用返回到您不会返回的字符串。

我们可以通过返回String来修复它 - 只是隐藏在不透明的API后面。这与breeden's solution非常相似,但在执行方面略有不同。

tokens_from_str

正如您所注意到的,use std::fs::{File}; use std::io::{BufRead, BufReader}; use std::path::Path; type BoxIter<T> = Box<Iterator<Item=T>>; /// Structure representing in our code a line, but with an opaque API surface. pub struct TokenIntermediate(String); impl<'a> IntoIterator for &'a TokenIntermediate { type Item = String; type IntoIter = Box<Iterator<Item=String> + 'a>; fn into_iter(self) -> Self::IntoIter { // delegate to tokens_from_str tokens_from_str(&self.0) } } fn tokens_from_str<'a>(text: &'a str) -> Box<Iterator<Item=String> + 'a> { Box::new(text.lines().flat_map(|s| s.split_whitespace().map(|s| s.to_string()) )) } // Returns an iterator of an iterator. The use case is a very large file where // each line is very long. The outer iterator goes over the file's lines. // The inner iterator returns the words of each line. pub fn token_parts_from_path<P>(path_arg: P) -> BoxIter<TokenIntermediate> where P: AsRef<Path> { let reader = reader_from_path(path_arg); let iter = reader.lines() .filter_map(|result| result.ok()) .map(|s| TokenIntermediate(s)); Box::new(iter) } fn reader_from_path<P>(path_arg: P) -> BufReader<File> where P: AsRef<Path> { let path = path_arg.as_ref(); let file = File::open(path).unwrap(); BufReader::new(file) } 没有区别,tokens_from_str只返回这个不透明的tokens_from_path结构。这将与原始解决方案一样可用,它只是将中间TokenIntermediate值的所有权推送到调用者,因此他们可以迭代它们中的标记。