Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do things equal to nested `impl Trait`?

Tags:

rust

function A which take a function B as parameter, again the function B take function C as parameter. I try the syntax like below, but this gives me an error:

fn a(b: impl Fn(impl Fn() -> ()) -> ()) -> () {
 // ...
}  
error[E0666]: nested `impl Trait` is not allowed
 --> src/main.rs:2:21
  |
2 |     fn a(b: impl Fn(impl Fn() -> ()) -> ()) -> () {
  |             --------^^^^^^^^^^^^^^^-------
  |             |       |
  |             |       nested `impl Trait` here
  |             outer `impl Trait`

For some reason, I can't use &dyn keyword:

fn a(b: impl Fn(&dyn Fn() -> ()) -> ()) -> () {
 // ...
} 

Are there another ways to do this?

And I don't know why nested impl Trait cause an error.

like image 270
周汉成 Avatar asked Oct 19 '18 03:10

周汉成


1 Answers

Nested impl Trait doesn't work because it hasn't been specified or implemented yet. Also, because it isn't very useful given how other parts of Rust work.

fn a(b: impl Fn(impl Fn() -> ()) -> ()) -> () can be written using full generic syntax as

fn a<B, C>(b: B) -> ()
where B: Fn(C) -> (),
      C: Fn() -> ()

So what does that mean? It means that B is something callable that takes an argument of something else callable. But the important thing here is in the concrete types. Specifically, B doesn't take any callable with a compatible signature, but specifically a C. What is C? Well, that's the issue. If you call a like this:

a(|f| f());

then C is the type of the lambda's parameter f, but that type is not known, since the parameter f's type cannot be deduced from usage alone. But suppose that wasn't a problem, what would the body of a look like?

fn a<B, C>(b: B) -> ()
where B: Fn(C) -> (),
      C: Fn() -> () {
  b(|| ())
}

Here we attempt to call b passing lambda. But the lambda's type is unnamed local lambda type, not C. Since C was passed in from the outside, it cannot be the type of a lambda local to a. Simply put, unless you pass in a C as an additional parameter to a, there is nothing that a has that it could pass to b.

What you apparently want is for B to be not a function object that can be called with some C, but with any C. You want it to be a polymorphic function object. Rust doesn't support compile-time polymorphic function objects (the equivalent in Haskell would be a forall a. a -> IO () or similar). It only supports runtime polymorphic function objects.

That's the job of dyn. Now you've said you can't use &dyn, because you want to pass the function object to another thread. So instead, use Box<dyn>:

fn a(b: impl Fn(Box<dyn Fn() -> ()>) -> ()) -> () {
 b(Box::new(|| println!("Hello")))
} 

fn main() {
    a(move |f| { f(); f(); });
}
like image 98
Sebastian Redl Avatar answered Oct 04 '22 09:10

Sebastian Redl