Tim Morgan
Tim Morgan

Reputation: 1184

How to return a vector of boxed traits using map (functional rather than imperative)

I'm new to Rust, and I'm only just beginning to grasp some core concepts.

In my case, I need to return a vector of boxed traits. I'd like to do it in functional programming style, but I can't get it to compile.

Can someone explain why the below code works when I use a for loop (imperative style), but doesn't work when using a functional programming style using an iterator (filter_map in this case)?

Note: this is a contrived example and has been reduced to the simplest form I could write in order to demonstrate my misunderstanding.

#![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()
}

The function get_things_using_for_loop() compiles, while the function get_things_using_iterator() does not.

Here is the error:

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`

Upvotes: 3

Views: 1006

Answers (2)

Lukas Kalbertodt
Lukas Kalbertodt

Reputation: 88736

This has actually nothing to do with those two programming styles, but is a mere coincidence of your code. In your first function you explicitly state the type of the vector:

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

In your second function you never mention that you want a vector full of Box<Thing>. You only have an if-else construct, whose cases return:

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

The compiler sees the first case and assumes: "OK, the programmer wants to return Option<Box<Foo>>". But then in the next case you have an Option<Box<Bar>>. Now the compiler is confused. But it won't search the whole type space to find a trait that is implemented by both types (Foo and Bar). Also: there could be multiple traits which are all implemented by both, Foo and Bar. My point: you have to tell the compiler something about Thing!

To do so, you have multiple choices, for example:

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

Just adding as Box<Thing> to the first case is already enough and the compiler is happy.

Upvotes: 5

Shepmaster
Shepmaster

Reputation: 430921

This has nothing to do with functional vs. imperative. Just pure types:

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
    };
}

What type is returned from the if/else? The first branch says it's an Option<Box<Foo>>. The second says it's Option<Box<Bar>>. Those aren't the same type. If you coerce the types:

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

It will all work.

You can also specify the type of the value:

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

Or, like you said, specify the type by annotating the closure:

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

Upvotes: 7

Related Questions