FutureExt trait and trait object

Hi,
I'm migrating to new futures - in couple of places in old solution I used trait objects (as different path of program returned future resolving to same results, but actually being of different type). And I hit interesting problem demonstrated by following code:

use futures::{future::{ready}, prelude::*};// 0.3.4
use tokio; // 0.2.11

#[tokio::main]
async fn main() {
    let f: Box<dyn Future<Output=u32>> = Box::new(ready(1));
    
    let f = f.map(|v| {
    println!("Got value {}", v);
    v+1
    }
    );
    
    //Alternate approach 
    // let f = async {
    //     f.await + 1
    // };
    
    
    let x = f.await;
    println!("DONE {:?}", x);
    
}

Code available also here.
Codefailed to compile with this error:

error: the `map` method cannot be invoked on a trait object
   --> src/main.rs:8:15
    |
8   |     let f = f.map(|v| {
    |               ^^^
    | 
   ::: /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.4/src/future/future/mod.rs:111:15
    |
111 |         Self: Sized,
    |               ----- this has a `Sized` requirement
    |
help: another candidate was found in the following trait, perhaps add a `use` for it:
    |
1   | use futures_util::future::future::FutureExt;
    |

I was quite confused by this error - FutureExt does have this Sized requirement, but I thought that trait object is Sized?
Actually I've solved this problem (with help of alternate approach, which is commented in the code). It's enough to add Unpin to trait object definition like this:

let f: Box<dyn Future<Output=u32>+Unpin> = Box::new(ready(1));

But this still leaves me bit confused, so I have couple of questions:

  1. How Unpin relates to Sized in this case?

  2. I'm still bit struggling with Pin and Unpin in new futures. Is there some more detailed tutorial /explanation - I've read through pin module in std documentation - theoretically it loosk fine, but still have problem to apply it to practical cases.

  3. Does this mean that is I'll have future which is not Unpin, I cannot use it in trait object because I'll not be able to use methods from FutureExt neither use await?

  4. As I said I used future trait objects in my past project, with new futures is it still good option? Or is there something better ( and do not look at sample code, because it's simplified, in real code there are more complex cases - like match where each branch returns different futures) ?

Thanks a lot
Ivan

1 Like

If I understand correctly, this is due to the following implementation:

impl<F> Future for Box<F>
where
    F: Unpin + Future + ?Sized, 

That is, Box<F>: Future for unsized types, such as the dyn Future, will implement Future only if the inner type is also Unpin.

With Futures, instead of Box::new you must use Box::pin

Basically the issue is that since Box<dyn Future> is not itself a future, it decided to dereference the box and call map on dyn Future directly, which fails as map takes the future by value and therefore requires it to be sized.

As you noticed, it turns out that Box<dyn Future + Unpin> does implement Future, so in that case map is called on the box, and of course the box itself is sized. Thus the issue went away.

As @kornel noted, you can use Box::pin instead, which you can use to create a Pin<Box<dyn Future>> without the Unpin requirement. This is because an ordinary box allows you to take the inner value out, but a Pin<Box<...>> does not allow this kind of access.

2 Likes

Thanks a lot to all - Using Pin<Box<dyn Future as type for future trait objects solved my issues.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.