如何返回一个由一个以'&'a mut self (当self是本地创建时)为参数的函数生成的迭代器?

6

更新:已更新帖子标题,并将答案移出问题。简短的答案是你不能。请参见我对这个问题的回答。

我正在关注一个错误处理博客文章这里(它的github在这里),并尝试对代码进行一些修改,使search函数返回一个Iterator而不是Vec。这非常困难,我被卡住了。

我已经到达了这个点:

fn search<'a, P: AsRef<Path>>(file_path: &Option<P>, city: &str)
    -> Result<FilterMap<csv::reader::DecodedRecords<'a, Box<Read>, Row>,
                        FnMut(Result<Row, csv::Error>)
                            -> Option<Result<PopulationCount, csv::Error>>>,
              CliError>  {
    let mut found = vec![];
    let input: Box<io::Read> = match *file_path {
        None => Box::new(io::stdin()),
        Some(ref file_path) => Box::new(try!(fs::File::open(file_path))),
    };

    let mut rdr = csv::Reader::from_reader(input);
    let closure = |row: Result<Row, csv::Error>| -> Option<Result<PopulationCount, csv::Error>> {
        let row = match row {
            Ok(row) => row,
            Err(err) => return Some(Err(From::from(err))),
        };
        match row.population {
            None => None,
            Some(count) => if row.city == city {
                Some(Ok(PopulationCount {
                    city: row.city,
                    country: row.country,
                    count: count,
                }))
            } else {
                None
            }
        }
    };
    let found = rdr.decode::<Row>().filter_map(closure);

    if !found.all(|row| match row {
        Ok(_) => true,
        _ => false,
    }) {
        Err(CliError::NotFound)
    } else {
        Ok(found)
    }
}

编译器给出以下错误:

src/main.rs:97:1: 133:2 error: the trait `core::marker::Sized` is not implemented for the type `core::ops::FnMut(core::result::Result<Row, csv::Error>) -> core::option::Option<core::result::Result<PopulationCount, csv::Error>>` [E0277]
src/main.rs:97 fn search<'a, P: AsRef<Path>>(file_path: &Option<P>, city: &str) -> Result<FilterMap<csv::reader::DecodedRecords<'a, Box<Read>, Row>, FnMut(Result<Row, csv::Error>) -> Option<Result<PopulationCount, csv::Error>>>, CliError>  {
src/main.rs:98     let mut found = vec![];
src/main.rs:99     let input: Box<io::Read> = match *file_path {
src/main.rs:100         None => Box::new(io::stdin()),
src/main.rs:101         Some(ref file_path) => Box::new(try!(fs::File::open(file_path))),
src/main.rs:102     };
                ...
src/main.rs:97:1: 133:2 note: `core::ops::FnMut(core::result::Result<Row, csv::Error>) -> core::option::Option<core::result::Result<PopulationCount, csv::Error>>` does not have a constant size known at compile-time
src/main.rs:97 fn search<'a, P: AsRef<Path>>(file_path: &Option<P>, city: &str) -> Result<FilterMap<csv::reader::DecodedRecords<'a, Box<Read>, Row>, FnMut(Result<Row, csv::Error>) -> Option<Result<PopulationCount, csv::Error>>>, CliError>  {
src/main.rs:98     let mut found = vec![];
src/main.rs:99     let input: Box<io::Read> = match *file_path {
src/main.rs:100         None => Box::new(io::stdin()),
src/main.rs:101         Some(ref file_path) => Box::new(try!(fs::File::open(file_path))),
src/main.rs:102     };
                ...
error: aborting due to previous error

我也尝试了这个函数定义:

fn search<'a, P: AsRef<Path>, F>(file_path: &Option<P>, city: &str)
    -> Result<FilterMap<csv::reader::DecodedRecords<'a, Box<Read>, Row>, F>,
              CliError>
    where F:  FnMut(Result<Row, csv::Error>)
                  -> Option<Result<PopulationCount, csv::Error>> {

由编译器产生的这些错误:
src/main.rs:131:12: 131:17 error: mismatched types:
 expected `core::iter::FilterMap<csv::reader::DecodedRecords<'_, Box<std::io::Read>, Row>, F>`,
 found    `core::iter::FilterMap<csv::reader::DecodedRecords<'_, Box<std::io::Read>, Row>, [closure src/main.rs:105:19: 122:6]>`
(expected type parameter,
found closure) [E0308]
src/main.rs:131         Ok(found)

我不能使用Box对闭包进行封装,否则它将无法被filter_map接受。

然后我尝试了这个:

fn search<'a, P: AsRef<Path>>(file_path: &Option<P>, city: &'a str)
    -> Result<(Box<Iterator<Item=Result<PopulationCount, csv::Error>> + 'a>, csv::Reader<Box<io::Read>>), CliError> {
    let input: Box<io::Read> = match *file_path {
        None => box io::stdin(),
        Some(ref file_path) => box try!(fs::File::open(file_path)),
    };

    let mut rdr = csv::Reader::from_reader(input);
    let mut found = rdr.decode::<Row>().filter_map(move |row| {
        let row = match row {
            Ok(row) => row,
            Err(err) => return Some(Err(err)),
        };
        match row.population {
            None => None,
            Some(count) if row.city == city => {
                Some(Ok(PopulationCount {
                    city: row.city,
                    country: row.country,
                    count: count,
                }))
            },
            _ => None,
        }
    });

    if found.size_hint().0 == 0 {
        Err(CliError::NotFound)
    } else {
        Ok((box found, rdr))
    }
}

fn main() {
    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.decode())
                            .unwrap_or_else(|err| err.exit());


    match search(&args.arg_data_path, &args.arg_city) {
        Err(CliError::NotFound) if args.flag_quiet => process::exit(1),
        Err(err) => fatal!("{}", err),
        Ok((pops, rdr)) => for pop in pops {
            match pop {
                Err(err) => panic!(err),
                Ok(pop) => println!("{}, {}: {} - {:?}", pop.city, pop.country, pop.count, rdr.byte_offset()),
            }
        }
    }
}

这会给我一个错误:

src/main.rs:107:21: 107:24 error: `rdr` does not live long enough
src/main.rs:107     let mut found = rdr.decode::<Row>().filter_map(move |row| {
                                    ^~~
src/main.rs:100:117: 130:2 note: reference must be valid for the lifetime 'a as defined on the block at 100:116...
src/main.rs:100     -> Result<(Box<Iterator<Item=Result<PopulationCount, csv::Error>> + 'a>, csv::Reader<Box<io::Read>>), CliError> {
src/main.rs:101     let input: Box<io::Read> = match *file_path {
src/main.rs:102         None => box io::stdin(),
src/main.rs:103         Some(ref file_path) => box try!(fs::File::open(file_path)),
src/main.rs:104     };
src/main.rs:105     
                ...
src/main.rs:106:51: 130:2 note: ...but borrowed value is only valid for the block suffix following statement 1 at 106:50
src/main.rs:106     let mut rdr = csv::Reader::from_reader(input);
src/main.rs:107     let mut found = rdr.decode::<Row>().filter_map(move |row| {
src/main.rs:108         let row = match row {
src/main.rs:109             Ok(row) => row,
src/main.rs:110             Err(err) => return Some(Err(err)),
src/main.rs:111         };
                ...
error: aborting due to previous error

我是否设计有误,或者采取了错误的方法?我是否错过了一些非常简单和愚蠢的东西?我不确定接下来该怎么做。


你能解释一下为什么这不是正确返回迭代器的方法的重复吗? - Shepmaster
我认为 Nashenas 实际上已经掌握了返回迭代器,但还有另一个问题——借用本地变量。 - bluss
@bluss 是正确的。我根据我的目标命名了问题,但看起来它应该根据我所学到的重新命名。你们有什么建议? - Nashenas
很好的总结。我认为评估并不完全正确。你不能通过移动被借用的值来纠正这个借用错误,因为 Rust 并不那么“聪明”。decode 只是借用了 Reader - bluss
此外,在得到答案后不要过于担心重新命名您的问题 - 考虑未来的搜索者如何找到这个问题。例如,如果您必须知道解决方案的名称才能找到问题,那么它将不会太有帮助。^_^ - Shepmaster
显示剩余2条评论
2个回答

10

返回迭代器是可能的,但它带有一些限制。

为了演示它是可能的,有两个例子,(A)使用显式迭代器类型,(B)使用装箱(playpen链接)。

use std::iter::FilterMap;

fn is_even(elt: i32) -> Option<i32> {
    if elt % 2 == 0 {
        Some(elt)
    } else { None }
}

/// (A)
pub fn evens<I: IntoIterator<Item=i32>>(iter: I)
    -> FilterMap<I::IntoIter, fn(I::Item) -> Option<I::Item>>
{
    iter.into_iter().filter_map(is_even)
}

/// (B)
pub fn cumulative_sums<'a, I>(iter: I) -> Box<Iterator<Item=i32> + 'a>
    where I: IntoIterator<Item=i32>,
          I::IntoIter: 'a,
{
    Box::new(iter.into_iter().scan(0, |acc, x| {
        *acc += x;
        Some(*acc)
    }))
}

fn main() {
    // The output is:
    //  0 is even, 10 is even, 
    //  1, 3, 6, 10, 
    for even in evens(vec![0, 3, 7, 10]) {
        print!("{} is even, ", even);
    }
    println!("");

    for cs in cumulative_sums(1..5) {
        print!("{}, ", cs);
    }
    println!("");
}

您遇到了(A)的一个问题--显式类型!使用|a,b,c|..语法从常规lambda表达式中获得的未装箱闭包具有唯一的匿名类型。函数需要明确的返回类型,因此在这里不起作用。

返回闭包的一些解决方案:

  • 像示例(A)中一样使用函数指针fn()。通常您无需闭包环境。
  • 将闭包打包。即使迭代器目前不支持调用它,这也是合理的。不是你的错。
  • 框住迭代器
  • 返回自定义迭代器结构体。需要一些样板文件。

您可以看到,在示例(B)中,我们必须非常小心寿命。它表示返回值为Box<Iterator<Item=i32> + 'a>,这是什么'a?这是盒子内部任何内容所需的最短生命周期!我们还在I::IntoIter上放置了'a边界--这确保我们可以将其放入盒子中。

如果只说Box<Iterator<Item=i32>>,它会假定'static

我们必须显式声明我们盒子内容的生命周期。只是为了安全起见。

这实际上是您函数的根本问题。你有这个:DecodedRecords<'a, Box<Read>, Row>, F>

看到了吗,一个'a!这种类型借用了某些东西。问题是它没有从输入中借用它。输入中没有'a

您会意识到,它从您在函数期间创建的值中借用,而该值的寿命在函数返回时结束。我们无法从函数返回DecodedRecords<'a>,因为它想要借用一个局部变量。

从这里去哪里?我最简单的答案是执行csv进行的相同分割。拥有阅读器的一部分(结构体或值),以及是迭代器并从阅读器中借用的一部分(结构体或值)。

也许csv板条箱有一个拥有解码器,它拥有正在处理的读取器。如果是这样,您可以使用它来解决借贷麻烦。


2
这个答案基于 @bluss 的回答和在 irc.mozilla.org 上 #rust 的帮助。
从代码中并不明显的一个问题导致了刚才显示的最终错误,这与 csv::Reader::decode 的定义有关(参见它的源代码)。它需要 &'a mut self,这个问题的解释在这个答案中有所涉及。这实际上将阅读器的生命周期限制在调用它的块内部。解决这个问题的方法是将函数分成两部分(因为我无法控制函数的定义,如前一个答案链接所建议的那样)。我需要一个在 main 函数内有效的阅读器生命周期,以便将阅读器传递到 search 函数中。请参见下面的代码(它肯定可以更清晰):
fn population_count<'a, I>(iter: I, city: &'a str)
    -> Box<Iterator<Item=Result<PopulationCount,csv::Error>> + 'a>
    where I: IntoIterator<Item=Result<Row,csv::Error>>,
          I::IntoIter: 'a,
{
    Box::new(iter.into_iter().filter_map(move |row| {
        let row = match row {
            Ok(row) => row,
            Err(err) => return Some(Err(err)),
        };

        match row.population {
            None => None,
            Some(count) if row.city == city => {
                Some(Ok(PopulationCount {
                    city: row.city,
                    country: row.country,
                    count: count,
                }))
            },
            _ => None,
        }
    }))
}

fn get_reader<P: AsRef<Path>>(file_path: &Option<P>)
    -> Result<csv::Reader<Box<io::Read>>, CliError>
{
    let input: Box<io::Read> = match *file_path {
        None => Box::new(io::stdin()),
        Some(ref file_path) => Box::new(try!(fs::File::open(file_path))),
    };

    Ok(csv::Reader::from_reader(input))
}

fn search<'a>(reader: &'a mut csv::Reader<Box<io::Read>>, city: &'a str)
    -> Box<Iterator<Item=Result<PopulationCount, csv::Error>> + 'a>
{
    population_count(reader.decode::<Row>(), city)
}

fn main() {
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|err| err.exit());

    let reader = get_reader(&args.arg_data_path);
    let mut reader = match reader {
        Err(err) => fatal!("{}", err),
        Ok(reader) => reader,
    };

    let populations = search(&mut reader, &args.arg_city);
    let mut found = false;
    for pop in populations {
        found = true;
        match pop {
            Err(err) => fatal!("fatal !! {}", err),
            Ok(pop) => println!("{}, {}: {}", pop.city, pop.country, pop.count),
        }
    }

    if !(found || args.flag_quiet) {
        fatal!("{}", CliError::NotFound);
    }
}

我在努力让这个东西工作时学到了很多,并对编译器错误有了更深的理解。现在清楚的是,如果这是C语言,上面的最后一个错误实际上可能导致段错误,这将会更难调试。我还意识到,从预计算的向量转换为迭代器需要更深入的思考,必须考虑内存何时进出作用域;我不能仅仅改变几个函数调用和返回类型就结束了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接