Can't figure out how to spawn a future with async trait functions

I have read about MVP part 2 send bounds and associated return types here However, I am still not able to get my sample to work in the context of what is being suggested. I am working on general purpose stock exchange connector that can establish and maintain connectivity to an arbitrary stock exchange using generics, traits, and data model that are all getting injected at the construction time and I have a code sample that I can't get to work.

The general idea is that I have client and service socket, example only shows a service socket and before the stream goes into service loop it needs to pass a handshake routine which is exchange specific, hence injected as a generic trait, lets call it ProtocolInitHandler. I am unable to compile my code because it generates an error suggesting that my spawned future is not a Send and I am trying to understand how to make it send given materials in the MVP article.

Playground sample

Actual project reference where I stumbled into the issue: links

You aren't using the feature described in that blog post that makes it possible to constrain the returned future to be Send

Playground

#![allow(incomplete_features)]
#![feature(async_fn_in_trait)]
#![feature(associated_type_bounds)]
#![feature(return_type_notation)]

use bytes::BytesMut;
use tokio::{
    io::AsyncReadExt,
    net::{
        tcp::{OwnedReadHalf, OwnedWriteHalf},
        TcpListener,
    },
};

trait ProtocolInitHandler: Sync + Send + 'static {
    async fn login_sequence(&self);
}
struct SvcProtocolInitHandler;
impl SvcProtocolInitHandler {
    fn new() -> Self {
        Self
    }
}
impl ProtocolInitHandler for SvcProtocolInitHandler {
    async fn login_sequence(&self) {
        println!("login sequence do something");
    }
}

struct Svc<HANDLER>
where
    HANDLER: ProtocolInitHandler,
{
    reader: OwnedReadHalf,
    writer: OwnedWriteHalf,
    phantom: std::marker::PhantomData<HANDLER>,
}
impl<HANDLER> Svc<HANDLER>
where
    HANDLER: ProtocolInitHandler<login_sequence(): Send>,
{
    async fn bind(addr: &str, handler: HANDLER) {
        tokio::spawn({
            let addr = addr.to_owned();
            async move {
                let listener = TcpListener::bind(addr).await.unwrap();
                println!("pending accept");
                let (stream, _) = listener.accept().await.unwrap();
                let (read, write) = stream.into_split();
                let svc = Self {
                    reader: read,
                    writer: write,
                    phantom: std::marker::PhantomData,
                };
                Self::service_loop(svc, handler).await;
            }
        })
        .await
        .unwrap();
    }
    async fn service_loop(mut svc: Svc<HANDLER>, handler: HANDLER) {
        println!("start login sequence");
        handler.login_sequence().await; // ---> REMOVING THIS LINE PASSES compiler
        println!("start service loop");
        loop {
            let mut buf = BytesMut::new();
            let n = svc.reader.read_buf(&mut buf);
            println!("read {:?} bytes", n);
        }
    }
}

#[tokio::main]
async fn main() {
    let addr = "0.0.0.0:8080";
    let handler = SvcProtocolInitHandler::new();
    // Fails to compile if you don't specify the type parameter on Svc
    Svc::<SvcProtocolInitHandler>::bind(addr, handler).await;
}

For whatever reason type inference falls over when an associated return type bound is used (in fairness the feature is marked as incomplete) so you have to specify Svc's type parameter too

Thanks for the help, a couple of followup questions:

  1. My understanding from this blog is that async is getting stabilized sometime in 2023, however it is not clear if "associated type bounds" are part of async 2023 stabilization initiative. Below are two excerpts from the blog that appear contrarian.

Blockquote RFC 2289, "associated type bounds", introduced a compact notation to address this problem. That RFC is not part of this MVP

vs

Blockquote We explored a number of solutions and concluded that associated return types (ARTs) are a flexible and reasonably ergonomic building block, which makes them a great fit for an MVP.

  1. If "associated type bounds" are not part of 2013 delivery then my question is how would I apply a suggested workaround from the blog that appears to be in scope of 2023. The workaround suggests to use a generic type, which is a starting point in my sample..
async fn do_health_check_par<HC>(hc: HC)
where
    HC: HealthCheck + Send + 'static,
    HC::check(): Send, // <-- associated return type

How would I do above in my sample? Below does is what I was trying to do initially and it fails with the following error.

impl<HANDLER> Svc<HANDLER>
where
    HANDLER: ProtocolInitHandler,
    HANDLER::login_sequence(): Send,  // -->  DOES NOT WORK
    // HANDLER: ProtocolInitHandler<login_sequence(): Send>,  // --> WORKS, thanks for the hint on enabling this feature.
{
error[E0214]: parenthesized type parameters may only be used with a `Fn` trait
  --> network/async/examples/trait_experiment.rs:41:14
   |
41 |     HANDLER::login_sequence(): Send,
   |              ^^^^^^^^^^^^^^^^ only `Fn` traits may use parentheses

error[E0220]: associated type `login_sequence` not found for `HANDLER`
  --> network/async/examples/trait_experiment.rs:41:14
   |
41 |     HANDLER::login_sequence(): Send,
   |              ^^^^^^^^^^^^^^ associated type `login_sequence` not found

I was also confused by that, but it looks like the initial PR that implemented the associated return types feature only implemented the associated type bounds part (possibly because it was easier to implement for some reason)

Since the feature is purely experimental at the moment, you shouldn't read too much into the current state of the implementation.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.