Reputation: 1184
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
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
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