[Solved] Is it possible to run async code in a trait method (with StdFuture/async/await)?

#1

I would like to run async code within trait methods, but I run into some trouble:

  1. async fn does not seem to be supported for trait methods
  2. -> impl StdFuture neither
  3. next attempt is returning a boxed dyn StdFuture from the method, but the compiler complains that the returned value of an async block is not Unpin, and thus it won’t run on tokio::run_async… I don’t understand why the block is not Unpin since it holds no references whatsoever…

Ideally I want to be able to use await! in trait methods so I can wait on inner futures without blocking the caller.

Small code example + error:

// enable the await! macro, async support, and the new std::Futures api.
//
#![feature(await_macro, async_await, futures_api)]

use std::future::Future as StdFuture;
use std::marker::Unpin;

fn main()
{
	let mut d = Dog{};
	tokio::run_async( d.handle( Food{} ) );
}

struct Dog {}
struct Food {}

impl Handler<Food> for Dog
{
	type Result = Box< dyn StdFuture<Output=()> + Send + Unpin >;

	fn handle( &mut self, _msg: Food ) -> Self::Result
	{
		Box::new( async { println!( "hi" ); } )
	}
}

The compiler error is:

error[E0277]: the trait bound `std::future::GenFuture<[static generator@src/main.rs:57:19: 57:40 _]>: std::marker::Unpin` is not satisfied in `impl std::future::Future`
  --> src/main.rs:57:3
   |
57 |         Box::new( async { println!( "hi" ); } )
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within `impl std::future::Future`, the trait `std::marker::Unpin` is not implemented for `std::future::GenFuture<[static generator@src/main.rs:57:19: 57:40 _]>`
   |
   = help: the following implementations were found:
             <std::future::GenFuture<T> as std::marker::Unpin>
   = note: required because it appears within the type `impl std::future::Future`
   = note: required for the cast to the object type `dyn std::future::Future<Output=()> + std::marker::Send + std::marker::Unpin`

Cargo.toml has:

[dependencies]
	futures   = "0.1.25"
	tokio = { version = "0.1.15", features = [ "async-await-preview" ] }

	# Only needs to be explicitly imported if you want to make use of
	# the machinery to convert promises back and forth between 0.1 and 0.3
	# (which is built into the provided await! macro only otherwise):
	tokio-async-await = "0.1.5"

What am I missing?

#2

I found one solution with alot of boilerplate… It’s to convert to an old style future before returning from the handle method. Haven’t yet managed by converting after returning from the method…

impl Handler<Food> for Dog
{
	type Result = Box< OldFuture<Item=(), Error=()> + Send>;

	fn handle( &mut self, _msg: Food ) -> Self::Result { Box::new( backward( async 
	{ 
		println!( "hi {}", await!( test() ) ); 
		Ok(()) 

	}))}
}

async fn test() -> u8
{
	5
}



// converts from a new style Future to an old style one:
pub fn backward< I,E >(f: impl StdFuture<Output=std::result::Result<I,E>>) -> impl OldFuture<Item=I, Error=E>
{
	compat::backward::Compat::new(f)
}

I can even convert it back to a new style future on the outside, but I can’t seem to return a new style future from the method:

use tokio_async_await::compat::forward::IntoAwaitable;

let mut d = Dog{};

let f = async move
{
	await!( d.handle( Food{} ).into_awaitable() );
};

tokio::run_async( f );
#3

async fn and -> impl Trait in traits is still a while away. You can use -> Pin<Box<dyn Future>> which can be created with Box::pin(async { ... }) in the meantime.

#4

@Nemo157 Thanks alot! That works. Do you know if I would be able to use &self in such an async block? The compiler seems to insist that it be 'static…

#5

You can by using something like fn foo(&self) -> Pin<Box<dyn Future + '_>> ('_ is the “elision” lifetime that comes from the &self in methods).

#6

I can’t quite get it to work. It works fine with an impl Struct. You can write an async method on the struct, and await that from within an async block in main (you can’t spawn it on a tokio runtime directly, and the async block in main needs to own the object). There is no need to use '_ in that case.

However when using a trait method, the compiler always demands that the lifetime be static, which I don’t know how to do. Adding '_ doesn’t really seem to help anything here. This is a simple example with the error message:

// enable the await! macro, async support, and the new std::Futures api.
//
#![feature(await_macro, async_await, futures_api)]

// only needed to manually implement a std future:
//
#![feature(arbitrary_self_types)]

use std::future::Future as StdFuture;
use std::pin::Pin;

fn main()
{
	tokio::run_async( async
	{
		let name = "Max".to_string();
		let kind = "meat".to_string();
		let d    = Dog{ name };

		await!( d.eat( Food{ kind } ) )
	});
}


trait Eat
{
	fn eat( &self, food: Food ) -> Pin< Box< dyn StdFuture< Output = () > + Send > >;
}

struct Food { kind: String }
struct Dog  { name: String }


impl Eat for Dog
{
	fn eat( &self, food: Food ) -> Pin< Box< dyn StdFuture< Output = () > + Send > >
	{
		Box::pin
		(
			async move
			{
				println!( "hi {}"      , self.name );
				println!( "eating {:?}", food.kind );
			}
		)
	}
}

The error:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:44:4
   |
44 |               {
   |  _____________^
45 | |                 println!( "hi {}"      , self.name );
46 | |                 println!( "eating {:?}", food.kind );
47 | |             }
   | |_____________^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 39:2...
  --> src/main.rs:39:2
   |
39 |       fn eat( &self, food: Food ) -> Pin< Box< dyn StdFuture< Output = () > + Send > >
   |  _____^
40 | |     {
41 | |         Box::pin
42 | |         (
...  |
48 | |         )
49 | |     }
   | |_____^
   = note: ...so that the types are compatible:
           expected &Dog
              found &Dog
   = note: but, the lifetime must be valid for the static lifetime...
   = note: ...so that the expression is assignable:
           expected std::pin::Pin<std::boxed::Box<(dyn std::future::Future<Output=()> + std::marker::Send + 'static)>>
              found std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output=()> + std::marker::Send>>
#7

It appears to work for me (playground).

To explain a bit further, trait objects when used in a Box default to requiring a 'static lifetime, i.e. the signature

fn eat(&self, food: Food) -> Pin<Box<dyn StdFuture<Output = ()> + Send>>

is really

fn eat(&self, food: Food) -> Pin<Box<dyn StdFuture<Output = ()> + Send + 'static>>

but what you want is to allow the returned trait object to borrow from the self borrow passed into the function

fn eat<'self>(&'self self, food: Food) -> Pin<Box<dyn StdFuture<Output = ()> + Send + 'self>>

The '_ lifetime is then just a shorthand to avoid having to explicitly declare this 'self lifetime.

#8

OK! Thanks so much. In all my trials I had at some point added and then removed the '_ on the trait definition. Now that that’s there, it works.

When it’s missing rustc doesn’t tell you that the method signatures not match. It gives the error as in my last post, which is kind of confusing.

Cool, time to get to work now that that’s solved!

#9

I remember why I removed it. I used an associated type for the return so that the implementor of the trait can define the result type, but that doesn’t play well with '_… It does work with a named lifetime though.