This question already has answers here:
Dynamically select a function to call without intermediate variables
(2 answers)
Closed 10 months ago.
I am trying to create a method that takes an Option<impl Fn() -> ()> as a parameter, and, if this parameter is Some(func), stores func as a closure, but, if it is None, stores some default function as the closure. Specifically, my code is as follows:
fn foo()
{
println!("HI");
}
fn bar(baz: Option<impl Fn () -> ()>)
{
let func = match baz {
Some(func) => func,
//None => foo,
None => unimplemented!()
};
func();
}
pub fn main() {
bar(Some(foo));
}
As-is, the code compiles, which indicates to me that foo is indeed something that should work with impl Fn () -> (). However, if I swap out the None arm of my match with the commented out None arm to set foo as my "default implentation", I get the following error:
error[E0308]: `match` arms have incompatible types
--> <source>:10:17
|
6 | fn bar(baz: Option<impl Fn () -> ()>)
| ---------------- this type parameter
7 | {
8 | let func = match baz {
| ________________-
9 | | Some(func) => func,
| | ---- this is found to be of type `impl Fn() -> ()`
10 | | None => foo,
| | ^^^ expected type parameter `impl Fn() -> ()`, found fn item
11 | | //None => unimplemented!()
12 | | };
| |_____- `match` arms have incompatible types
|
= note: expected type `impl Fn() -> ()`
found fn item `fn() {foo}`
error: aborting due to previous error
As far as I can tell from the error, it is saying that foo does not qualify as impl Fn() -> () within the function (though it did when passed in as a parameter). What am I missing here that causes this, and is there a way to accomplish what I want here (take an optional closure as a parameter, and use a default function as a closure if none provided)?
All the arms of a match statement must resolve to the exact same type.
With that in mind, let's replace impl Fn() -> () (which is also the same as impl Fn() BTW) with the generics that it represents:
fn bar<T>(baz: Option<T>) where T: Fn() {
// ...
}
impl Trait (in a function parameter) is syntactic sugar for regular generics, so this is "what the compiler sees".
Then let's look at the match:
fn bar<T>(baz: Option<T>) where T: Fn() {
let f = match baz {
Some(f) => f,
None => foo,
};
}
This can't work, because the type of foo is a special thing called a "function item" (essentially a unique type), but the type of f is a generic parameter T: it could be any type imaginable (as long as it implements Fn()). Clearly, these might be different, so Rust rejects the match statement as "having incompatible types".
What you're looking for is a trait object (though this comes at the cost of a heap allocation and vtable dispatch):
fn bar(baz: Option<impl Fn()>) {
let f: Box<dyn Fn()> = match baz {
Some(f) => Box::new(f),
None => Box::new(foo),
};
f();
}
Or, you can call the function immediately without storing in a variable:
fn bar(baz: Option<impl Fn()>) {
match baz {
Some(f) => f(),
None => foo(),
};
}
This is likely to be faster than the dyn Fn() approach above, though of course, if you are later storing it in a struct, this will not work.
Related
This question already has an answer here:
Return Result<Box<dyn Trait>> in a match
(1 answer)
Closed 6 months ago.
I have an operation that I'd like to err out on invalid input, otherwise create one of two structs which implement a trait. I'm sure this isn't a shortcoming of the compiler but rather my poor understanding of the type system and I'm looking for some input. This is a simplified version of my code but the core issue remains.
pub trait Action { ... }
pub struct TaxonomySpotSearch { ... }
impl Action for TaxonomySpotSearch { ... }
pub struct SavedSearch { ... }
impl Action for SavedSearch { ... }
fn do_something() -> Result<i32, ArgError> {
let foo: Box<dyn Action> = match Some(1) {
Some(0) => Ok(Box::new(TaxonomySpotSearch::new())),
Some(1) => Ok(Box::new(SavedSpotSearch::new())),
_ => Err(ArgError::NoSubCommand)
}?;
Ok(...)
}
However, I'm getting the classic E0308 "match arms have incompatible types" error where the compiler is inferring the type of foo as the return value of the first match arm.
error[E0308]: `match` arms have incompatible types
--> src/main.rs:127:20
|
125 | let foo: Box<dyn Action> = match Some(1) {
| ________________________________-
126 | | Some(0) => Ok(Box::new(TaxonomySpotSearch::new())),
| | --------------------------------------- this is found to be of type `Result<Box<TaxonomySpotSearch>, _>`
127 | | Some(1) => Ok(Box::new(SavedSpotSearch::new())),
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `TaxonomySpotSearch`, found struct `SavedSpotSearch`
128 | | _ => Err(ArgError::NoSubCommand)
129 | | }?;
| |_____- `match` arms have incompatible types
|
= note: expected enum `Result<Box<TaxonomySpotSearch>, _>`
found enum `Result<Box<SavedSpotSearch>, _>`
Am I doing something wrong or is this really a limitation of the type system? I would expect the addition of the explicit type annotation to resolve this, yet here I am.
The Rust compiler doesn't understand that you want them both to be Box<dyn Action>, it takes them as the real type they are.
You need to tell that to the Rust compiler explicitely:
pub trait Action {}
pub struct TaxonomySpotSearch {}
impl Action for TaxonomySpotSearch {}
pub struct SavedSearch {}
impl Action for SavedSearch {}
enum ArgError {
NoSubCommand,
}
fn do_something() -> Result<(), ArgError> {
let foo: Box<dyn Action> = match Some(1) {
Some(0) => Ok(Box::new(TaxonomySpotSearch {}) as Box<dyn Action>),
Some(1) => Ok(Box::new(SavedSearch {}) as Box<dyn Action>),
_ => Err(ArgError::NoSubCommand),
}?;
Ok(())
}
I want: for a trait, e.g. Foo, any fn(T1,T2)->() should implement it, and I don't care whether T1 or T2 is a reference or not.
trait Foo {
fn hello_world(&self) -> String {
"hello world".into()
}
}
impl<T1, T2> Foo for fn(T1, T2) -> () {}
fn a_dummy_sample_function(_: i32, _:i32) {}
fn main() {
let fn_ptr : fn(i32, i32)->() = a_dummy_sample_function;
(fn_ptr).hello_world();
}
So far, it works well.
If I change the type of first argument to &i32,
trait Foo {
fn hello_world(&self) -> String {
"hello world".into()
}
}
impl<T1, T2> Foo for fn(T1, T2) -> () {}
fn foo_imp(_: &i32, _:i32) {}
fn main() {
let fn_ptr : fn(&i32, i32)->() = foo_imp;
fn_ptr.hello_world();
}
Then, I've got the following compilation errors.
--> src/main.rs:12:12
|
12 | fn_ptr.hello_world();
| ^^^^^^^^^^^ method not found in `for<'r> fn(&'r i32, i32)`
|
= note: `fn_ptr` is a function, perhaps you wish to call it
= help: items from traits can only be used if the trait is implemented and in scope
note: `Foo` defines an item `hello_world`, perhaps you need to implement it
I can fix the compilation error as below
impl<T1, T2> Foo for for<'a> fn(&'a T1, T2) -> () {}
But it is not what I want, I want any function pointer with any types of two arguments whether it is a reference or not.
I have this simple struct with 2 Hashsets:
pub struct IpAddresses {
pub ipv4s: HashSet<String>,
pub ipv6s: HashSet<String>,
}
and then a simple function which is supposed to provide an iterator to one of the sets:
pub fn shared2(&self, ipv6: bool) -> impl Iterator<Item = IpAddr> + '_ {
if ipv6 {
self
.ipv6s
.iter()
.filter_map(|a| IpAddr::from_str(a).ok())
} else {
self
.ipv4s
.iter()
.filter_map(|a| IpAddr::from_str(a).ok())
}
}
I get the following error with the suggestion to use box:
error[E0308]: `if` and `else` have incompatible types
--> src/models/ip_address.rs:131:13
|
125 | / if ipv6 {
126 | | self
| _|_____________-
127 | | | .ipv6s
128 | | | .iter()
129 | | | .filter_map(|a| IpAddr::from_str(a).ok())
| |_|_____________________________________________________- expected because of this
130 | | } else {
131 | / | self
132 | | | .ipv4s
133 | | | .iter()
134 | | | .filter_map(|a| IpAddr::from_str(a).ok())
| |_|_____________________________________________________^ expected closure, found a different closure
135 | | }
| |_________- `if` and `else` have incompatible types
|
= note: expected type `FilterMap<std::collections::hash_set::Iter<'_, _>, [closure#src/models/ip_address.rs:129:25: 129:53]>`
found struct `FilterMap<std::collections::hash_set::Iter<'_, _>, [closure#src/models/ip_address.rs:134:25: 134:53]>`
= note: no two closures, even if identical, have the same type
= help: consider boxing your closure and/or using it as a trait object
help: you could change the return type to be a boxed trait object
|
122 | pub fn shared2(&self, ipv6: bool) -> Box<dyn Iterator<Item = IpAddr> + '_> {
| ~~~~~~~ +
help: if you change the return type to expect trait objects, box the returned expressions
|
126 ~ Box::new(self
127 | .shared_ipv6s
128 | .iter()
129 ~ .filter_map(|a| IpAddr::from_str(a).ok()))
130 | } else {
131 ~ Box::new(self
Interestingly enough if I copy paste one of the wings into a function, the compiler works fine without any error or need for a Box:
fn list_shared<'a>(&'a self, items: &'a HashSet<String>) -> impl Iterator<Item = IpAddr> + 'a {
items
.iter()
.filter_map(|a| IpAddr::from_str(a).ok())
}
pub fn shared<'a>(&'a self, ipv6: bool) -> impl Iterator<Item = IpAddr> + 'a {
if ipv6 {
self.list_shared(&self.ipv6s)
} else {
self.list_shared(&self.ipv4s)
}
}
As you can see this is a copy-paste of the inner block. Why is this happening? How are those 2 identical blocks not identical in the first instance but just putting them inside a function made them identical?
Each closure gets its own, anonymous type. Even though the closures have the same call signature, and even if neither of them borrows anything so no lifetimes are part of the signature, these types are not the same!
Therefore, the generic <F> in the returned FilterMap struct has a different type in each if branch, leading to the error message about trying to return incompatible types.
Note that -> impl Iterator tells the compiler that you're returning some type that implements Iterator, but it has to be statically the same type every time, determined at compile time.
When you extract the filter_map call to a separate function, there is only one closure, hence one type returned from that function. And since this is the same type for both if branches, the problem goes away.
It also goes away if you assign the closure to a variable, because then it's the same type in both cases as well:
pub fn shared2(&self, ipv6: bool) -> impl Iterator<Item = IpAddr> + '_ {
let from_str = |a: &String| IpAddr::from_str(a).ok();
if ipv6 {
self
.ipv6s
.iter()
.filter_map(from_str)
} else {
self
.ipv4s
.iter()
.filter_map(from_str)
}
}
impl Iterator<Item=IpAddr> is saying 'this function will return a static type that is determined at runtime that conforms to Iterator<Item=IpAddr>.
It is not the same as Box<dyn Iterator<Item=IpAddr> which means any type that conforms to Iterator<Item=IpAddr>.
The reason it dosent work is because each each one of |a| IpAddr::from_str(a).ok()) are diffrent types that are generated by the compiler, and filter_map rerurns a struct that has the iterator of the type FilterMap<I, F>, with F being the type of the function.
You can see the same issue if you move your closures to named functions
fn ipv6_iter(a: &String) -> Option<IpAddr> {
IpAddr::from_str(a).ok()
}
fn ipv4_iter(a: &String) -> Option<IpAddr> {
IpAddr::from_str(a).ok()
}
impl IpAddresses {
pub fn shared2(&self, ipv6: bool) -> impl Iterator<Item = IpAddr> + '_ {
if ipv6 {
self
.ipv6s
.iter()
.filter_map( ipv6_iter)
} else {
self
.ipv4s
.iter()
.filter_map( ipv4_iter)
}
}
}
In your first branch returns a FilterMap<IpAddresses, ipv6_iter>, but the else branch returns a FilterMap<IpAddresses, ipv4_iter>.
By moving the logic into list_shared, both filter_maps use the same anonymous function to do the mapping, therefore have the same FilterMap type.
Same as using the same static function in each filter_map
fn ip_iter(s: &String) -> Option<IpAddr> {
IpAddr::from_str(s).ok()
}
impl IpAddresses {
pub fn shared2(&self, ipv6: bool) -> impl Iterator<Item = IpAddr> + '_ {
if ipv6 {
self
.ipv6s
.iter()
.filter_map( ip_iter)
} else {
self
.ipv4s
.iter()
.filter_map( ip_iter)
}
}
}
So each branch returns a FilterMap<IpAddresses, ip_iter>, therefore impl Iterator<Item=IpAddr> has a single type to resolve to.
In my tests I had a helper function that runs a given method on differently configured objects, with a simplified version looking like this:
fn run_method<F>(f: F)
where
F: Fn(&Foo),
{
let to_test = vec![0i32];
to_test
.iter()
.map(|param| {
let foo = Foo(*param);
f(&foo);
})
.for_each(drop);
}
// run_method(Foo::run);
This worked fine until I added references to the tested struct, making it "lifetime-annotated" (for lack of a proper term, I mean Foo<'a>).
Now I get an error indicating, I think, that Rust doesn't want to accept a Foo::method as a function that can be used with any given lifetime (i.e. F: for<'a> Fn(&Foo<'a>)), as required by the closure:
error[E0631]: type mismatch in function arguments
--> src/main.rs:54:5
|
3 | fn run(&self) {
| ------------- found signature of `for<'r> fn(&'r Foo<'_>) -> _`
...
54 | run_method(Foo::run);
| ^^^^^^^^^^ expected signature of `for<'r, 's> fn(&'r Foo<'s>) -> _`
|
note: required by `run_method`
--> src/main.rs:44:1
|
44 | fn run_method<F>(f: F) where F: Fn(&Foo) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0271]: type mismatch resolving `for<'r, 's> <for<'t0> fn(&'t0 Foo<'_>) {Foo::<'_>::run} as std::ops::FnOnce<(&'r Foo<'s>,)>>::Output == ()`
--> src/main.rs:54:5
|
54 | run_method(Foo::run);
| ^^^^^^^^^^ expected bound lifetime parameter, found concrete lifetime
|
note: required by `run_method`
--> src/main.rs:44:1
|
44 | fn run_method<F>(f: F) where F: Fn(&Foo) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I can work around the problem by avoiding closures (though I don't really understand how 'a gets constrained to be local to run_method - isn't the lifetime parameter supposed to be chosen by the caller?):
fn run_method<'a, F>(f: F)
where
F: Fn(&Foo<'a>),
{
let to_test = vec![&0i32];
for param in to_test {
let foo = Foo(param);
f(&foo);
}
}
Can I fix this without rewriting? If not - is there a reason why this shouldn't work?
Complete code:
struct Foo<'a>(&'a i32);
impl<'a> Foo<'a> {
fn run(&self) {
println!("Hello {}", self.0);
}
}
fn run_method<F>(f: F)
where
F: Fn(&Foo),
{
//same as F: for<'a> Fn(&Foo<'a>) {
let to_test = vec![0i32];
to_test
.iter()
.map(|param| {
let foo = Foo(param);
f(&foo);
})
.for_each(drop);
}
fn main() {
run_method(Foo::run);
}
// This works:
// fn run_method<'a, F>(f: F)
// where
// F: Fn(&Foo<'a>),
// {
// let to_test = vec![&0i32];
// for param in to_test {
// let foo = Foo(param);
// f(&foo);
// }
// }
// The lifetime-less version:
// struct Foo(i32);
// impl Foo {
// fn run(&self) {
// println!("Hello {}", self.0);
// }
// }
//
// fn run_parser_method<F>(f: F)
// where
// F: Fn(&Foo),
// {
// let to_test = vec![0i32];
// to_test
// .iter()
// .map(|param| {
// let foo = Foo(*param);
// f(&foo);
// })
// .for_each(drop);
// }
//
// fn main() {
// run_parser_method(Foo::run);
// }
playground
An overview of other questions about the same error code:
Expected bound lifetime parameter, found concrete lifetime is about mismatch between trait definition and implementation (trait { fn handle<'a>(); } vs impl<'a> { fn handle() {} })
Function references: expected bound lifetime parameter , found concrete lifetime [E0271] as well as Expected bound lifetime parameter, found concrete lifetime [E0271] is about a closure |args| {...} without explicit type annotations (|args: &[&str]|) not being accepted as a Fn(&[&str]) -> (); the answers don't explain why (the latter hints that it was not implemented in 2015)
Type mismatch "bound lifetime parameter" vs "concrete lifetime" when filling a collection from a closure is again about a closure without explicit type annotations specifying that it accepts a reference (let mut insert = |k| seq.insert(k); (1..10).cycle().take_while(insert)), which masks a more useful "borrowed data cannot be stored outside of its closure" error.
I can work around the problem by avoiding closures (though I don't really understand how 'a gets constrained to be local to run_method - isn't the lifetime parameter supposed to be chosen by the caller?)
It is. But when you rewrite it without closures, you have also put the reference inside the vec! invocation, so it is no longer constructed at runtime. Instead, the compiler can infer that to_test has type Vec<&'static i32>, and as 'static outlives any caller-chosen lifetime, there's no violation.
This fails to compile as you expect:
let to_test = vec![0i32];
for param in to_test.iter() {
let foo = Foo(param);
f(&foo);
}
is there a reason why this shouldn't work?
Yes, because the type of run is constrained by the type of Foo. More specifically, you have a lifetime decided by the caller (implicitly, in the type for Foo), so you have to construct a Foo of that lifetime to invoke the given run reference on it.
Can I fix this without rewriting?
That depends.
If all your test values are literals, you can make 'static references.
If you're able and willing to rewrite run, it's possible to make it unconstrained by the type of Foo
impl<'a> Foo<'a> {
fn run<'s>(_self: &Foo<'s>) {
println!("Hello {}", _self.0);
}
}
But then it doesn't need to go in an impl block at all.
I think the solution provided in the comments is your best bet, because given that you don't care about your concrete Foo<'a> type, there's no need to give the method reference for that type.
I've been experimenting with impl Trait and I came across this error when building a recursive function:
error[E0308]: if and else have incompatible types
--> src/main.rs:16:5
|
16 | / if logic {
17 | | one(false)
18 | | } else {
19 | | two()
20 | | }
| |_____^ expected opaque type, found a different opaque type
|
= note: expected type `impl Meow` (opaque type)
found type `impl Meow` (opaque type)
Here's the code to reproduce (Rust playground link):
trait Meow {
fn meow();
}
struct Cat(u64);
impl Meow for Cat {
fn meow() {}
}
fn one(gate: bool) -> impl Meow {
if gate {
one(false)
} else {
two()
}
}
fn two() -> impl Meow {
Cat(42)
}
fn main() {
let _ = one(true);
}
I haven't been able to find documentation about this particular issue and I find it odd that the compiler returns an error that roughly says "these two identical things are different".
Is there a way I can support the impl Trait syntax whilst doing this kind of recusion, please?
Disclaimer: this answer assumes that the reader understands that -> impl Trait requires a single type to be returned; see this question for returning different types.
Opacity
One of the core principles of Rust is that type-checking is entirely driven by the interface of functions, types, etc... and the implementation is ignored.
With regard to -> impl Trait functionality, this manifests by the language treating each -> impl Trait as an opaque type, solely identified by the function it comes from.
As a result, you can call the same function twice:
use std::fmt::Debug;
fn cat(name: &str) -> impl Debug { format!("Meow {}", name) }
fn meow(g: bool) -> impl Debug {
if g {
cat("Mario")
} else {
cat("Luigi")
}
}
fn main() {
println!("{:?}", meow(true));
}
But you cannot call different functions, even when they return the same type, if at least one is hidden behind -> impl Trait:
use std::fmt::Debug;
fn mario() -> impl Debug { "Meow Mario" }
fn luigi() -> &'static str { "Meow Luigi" }
fn meow(g: bool) -> impl Debug {
if g {
mario()
} else {
luigi()
}
}
fn main() {
println!("{:?}", meow(true));
}
Yields:
error[E0308]: if and else have incompatible types
--> src/main.rs:8:9
|
8 | / if g {
9 | | mario()
10 | | } else {
11 | | luigi()
12 | | }
| |_________^ expected opaque type, found &str
|
= note: expected type `impl std::fmt::Debug`
found type `&str`
And with two hidden behind -> impl Trait:
use std::fmt::Debug;
fn mario() -> impl Debug { "Meow Mario" }
fn luigi() -> impl Debug { "Meow Luigi" }
fn meow(g: bool) -> impl Debug {
if g {
mario()
} else {
luigi()
}
}
fn main() {
println!("{:?}", meow(true));
}
Yields the same error message than you got:
error[E0308]: if and else have incompatible types
--> src/main.rs:8:5
|
8 | / if g {
9 | | mario()
10 | | } else {
11 | | luigi()
12 | | }
| |_____^ expected opaque type, found a different opaque type
|
= note: expected type `impl std::fmt::Debug` (opaque type)
found type `impl std::fmt::Debug` (opaque type)
Interaction with Recursion
None.
The language does not special-case recursion here, and therefore does not realize that, in the case presented in the question, there is only ever one type involved. Instead, it notices fn one(...) -> impl Meow and fn two(...) -> impl Meow and concludes that those are different opaque types and therefore compile-time unification is impossible.
It may be reasonable to submit a RFC to tweak this aspect, either by arguing on the point of view of recursion, or by arguing on the point of view of module-level visibility; this is beyond the scope of this answer.
Work around
The only possibility is to ensure that the type is unique, and this requires naming it. Once you have captured the type in a name, you can consistently apply it everywhere it needs to match.
I'll refer you to #Anders' answer for his clever work-around.
I think an ideal compiler would accept your code, but the current language doesn’t allow for the recursive reasoning that would be needed to figure out that the types are actually the same in this case. You can work around this missing feature by abstracting over the impl Meow type with a type variable:
fn one_template<T: Meow>(gate: bool, two: impl FnOnce() -> T) -> T {
if gate {
one_template(false, two)
} else {
two()
}
}
fn one(gate: bool) -> impl Meow {
one_template(gate, two)
}
Rust playground link