如何使用map(功能而非命令性)返回盒装特征的向量

时间:2016-09-13 23:08:59

标签: rust traits

我是Rust的新手,我只是刚刚开始掌握一些核心概念。

在我的情况下,我需要返回一个盒装特征的向量。我想在函数式编程风格中这样做,但我无法编译它。

有人可以解释为什么下面的代码在我使用for循环(命令式样式)时有效,但在使用迭代器(在这种情况下为filter_map)使用函数式编程样式时不起作用?

注意:这是一个人为的例子,并且已经简化为我能写的最简单的形式,以证明我的误解。

#![allow(unused_variables)]

struct Foo {}
struct Bar {}

trait Thing {}

impl Thing for Foo {}
impl Thing for Bar {}

fn main() {
    let things = get_things_using_for_loop(); // works!
    let things = get_things_using_iterator(); // doesn't work! :-(
}

fn get_things_using_for_loop() -> Vec<Box<Thing>> {
    let mut things: Vec<Box<Thing>> = vec![];
    for t in ["foo".to_string(), "bar".to_string()].iter() {
        if t == "foo" {
            things.push(Box::new(Foo {}))
        } else if t == "bar" {
            things.push(Box::new(Bar {}))
        }
    }
    things
}

fn get_things_using_iterator() -> Vec<Box<Thing>> {
    ["foo".to_string(), "bar".to_string()]
        .iter()
        .filter_map(|t| {
            if t == "foo" {
                Some(Box::new(Foo {}))
            } else if t == "bar" {
                Some(Box::new(Bar {}))
            } else {
                None
            }
        })
        .collect()
}

函数get_things_using_for_loop()编译,而函数get_things_using_iterator()则不编译。

这是错误:

error: mismatched types [--explain E0308]
  --> <anon>:23:31
   |>
23 |>                 Some(Box::new(Bar {}))
   |>                               ^^^^^^ expected struct `Foo`, found struct `Bar`
note: expected type `Foo`
note:    found type `Bar`

2 个答案:

答案 0 :(得分:6)

这与功能与命令无关。只是纯粹的类型:

struct Foo {}
struct Bar {}

trait Thing {}

impl Thing for Foo {}
impl Thing for Bar {}

fn main() {
    let t = "quux";

    if t == "foo" {
        Some(Box::new(Foo {}))
    } else if t == "bar" {
        Some(Box::new(Bar {}))
    } else {
        None
    };
}

if / else返回的是哪种类型?第一个分支表示它是Option<Box<Foo>>。第二个是Option<Box<Bar>>。那些不是同一类型。如果您强制执行以下类型:

if t == "foo" {
    Some(Box::new(Foo {}) as Box<Thing>)
} else if t == "bar" {
    Some(Box::new(Bar {}) as Box<Thing>)
} else {
    None
};

一切都会奏效。

您还可以指定值的类型:

let x: Option<Box<Thing>> = if t == "foo" {
    Some(Box::new(Foo {}))
} else if t == "bar" {
    Some(Box::new(Bar {}))
} else {
    None
};

或者,就像你说的那样,通过注释闭包来指定类型:

.filter_map(|t| -> Option<Box<Thing>> {

答案 1 :(得分:5)

这实际上与这两种编程风格无关,但仅仅是你的代码的巧合。在第一个函数中,您明确说明了向量的类型:

let mut things: Vec<Box<Thing>> = vec![];

在你的第二个函数中,你永远不会提到你想要一个满载Box<Thing>的向量。您只有一个if-else构造,其案例返回:

  • Some(Box::new(Foo {}))
  • Some(Box::new(Bar {}))
  • None

编译器看到第一种情况并假设:“好的,程序员想要返回Option<Box<Foo>>”。但是在接下来的例子中你有一个Option<Box<Bar>>。现在编译器很困惑。但它不会搜索整个类型空间来查找由两种类型(FooBar)实现的特征。另外:可能有多个特征都由FooBar实现。我的观点:你告诉编译器关于Thing的一些事情!

为此,您有多种选择,例如:

if t == "foo" {
    Some(Box::new(Foo {}) as Box<Thing>)
} else { /* ... */ }

只需将as Box<Thing>添加到第一个案例已经足够了,编译器很满意。