Closure in the return type for a Rust function [du

2019-07-26 20:43发布

This question already has an answer here:

I wrote the following Rust program to print out only command-line arguments that are integers. It works perfectly:

use std::env;
fn main() {
    for i in env::args().filter_map(|arg| arg.parse::<i32>().ok()) {
        println!("{}", i);
    }
}

I then attempted to re-write the program to abstract the filter into a function. This version does not compile.

use std::env::Args;
use std::env;
use std::iter::FilterMap;
// Version 2
fn main() {
    for i in nums(&env::args()) {
        println!("{}", i);
    }
}

fn nums<F: Fn(String) -> Option<i32>>(args: &Args) -> FilterMap<Args,F> {
    args.filter_map(|arg| arg.parse::<i32>().ok())
}

It produces the following compilation errors:

   Compiling iterator_return_type v0.1.0 (file:///Users/gabriel/AllProjects/SentimentAnalysis/iterator_return_type)
error[E0282]: type annotations needed
  --> src/main.rs:16:9
   |
16 |     for i in nums(&env::args()) {
   |         ^ cannot infer type for `_`

error: the type of this value must be known in this context
  --> src/main.rs:22:27
   |
22 |     args.filter_map(|arg| arg.parse::<i32>().ok())
   |                           ^^^^^^^^^^^^^^^^^^

error[E0308]: mismatched types
  --> src/main.rs:22:21
   |
22 |     args.filter_map(|arg| arg.parse::<i32>().ok())
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter, found closure
   |
   = note: expected type `F`
              found type `[closure@src/main.rs:22:21: 22:50]`

error: aborting due to previous error(s)

error: Could not compile `iterator_return_type`.

What I find particularly confusing is the final compilation error. I do not understand how else I might specify a closure type.

Thanks!

标签: rust
1条回答
一纸荒年 Trace。
2楼-- · 2019-07-26 21:09

impl Trait and Box<Trait> solution can be applied to both iteratorsand closures, both of them are just traits! The difference is you have to use them in the closure case.

If you want to use impl Trait, then your code will look like this (note that Args should be passed by value):

#![feature(conservative_impl_trait)]

use std::env::Args;
use std::env;
use std::iter::FilterMap;

fn main() {
    for i in nums(env::args()) {
        println!("{}", i);
    }
}

fn nums(args: Args) -> FilterMap<Args, impl FnMut(String) -> Option<i32>> {
    args.filter_map(|arg| arg.parse::<i32>().ok())
}

However, you usually need not expose the detail of the iterator type; therefore you can do it like this way:

fn nums(args: Args) -> impl Iterator<Item = i32> {
    args.filter_map(|arg| arg.parse::<i32>().ok())
}

What if you want to use stable Rust? Unfortunately you'll have to use boxing for now.

fn nums(args: Args) -> Box<Iterator<Item = i32>> {
    Box::new(args.filter_map(|arg| arg.parse::<i32>().ok()))
}

Why can't you describe a full type of closures, despite that you can describe an iterator like Zip<Drain<'a, i32>, IntoIter<&'b str>>? There are two reasons:

  • Closure types are anonymous by nature; you'll have to anonymize (impl Fn()) or box (Box<Fn()>) them if you want to return them.
  • The interface for closure traits is unstable; you can't implement them (impl Fn() for YourType { .. }) stably.

Then why doesn't your code work? The reason is:

  • If you want to pass closures to a function, the caller decides its type. In this case you can write fn foo<T: Fn()>() { .. }.
  • If you want to pass closures from a function, the callee decides its type. In this case you'll have to use impl Trait.

RFC 1951 will change this distinction. You will be able to use impl Trait in both cases.

查看更多
登录 后发表回答