Async fn ptr - one type is more general than the other

use tokio::sync::{mpsc, oneshot};

type Responder<T> = oneshot::Sender<T>;

pub struct Actor<T, D> {
    pub data: D,
    receiver: mpsc::Receiver<T>,
}

impl<T, D> Actor<T, D> {
    fn new(receiver: mpsc::Receiver<T>, data: D) -> Self {
        Self { 
            receiver,
            data
        }
    }
}

#[derive(Clone)]
pub struct ActorHandle<T> {
    sender: mpsc::Sender<T>
}

impl<T> ActorHandle<T>
where
    T : std::marker::Send + 'static
{
    pub async fn new<F, Fut, D>(buffer_size: usize, handle_actor_msg_fn: F, data: D) -> Self
    where 
        F: FnMut(T, &mut D) -> Fut + std::marker::Send + 'static,
        Fut: std::future::Future<Output=bool>,
        D : std::marker::Send + 'static
    {
        let (sender, receiver) = mpsc::channel(buffer_size);
        let actor = Actor::new(receiver, data);
        
        tokio::spawn(async move {run_actor::<T, D, F, Fut>(actor, handle_actor_msg_fn).await});
        
        Self {
            sender
        }
    }
}

async fn run_actor<T, D, F, Fut>(mut actor: Actor<T, D>, mut handle_actor_msg_fn: F) 
where 
    F: FnMut(T, &mut D) -> Fut,
    Fut: std::future::Future<Output=bool>
{
    tracing::info!("Starting comm actor");
    while let Some(msg) = actor.receiver.recv().await {
        if !handle_actor_msg_fn(msg, &mut actor.data).await {
            break;
        }
    }
    tracing::info!("Closing comm actor");
}

#[derive(Clone)]
pub enum ActorMessage {
}

struct Data {
}

#[derive(Clone)]
pub struct Comm {
    pub actor: ActorHandle<ActorMessage>,
}

impl Comm {
    pub async fn new(actor_buffer_size: usize) -> Self {
        Self { 
            actor: ActorHandle::new(actor_buffer_size, handle_msg, Data {}).await
        }
    }
}

async fn handle_msg(msg: ActorMessage, data: &mut Data) -> bool {
    false
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .init();

    
}
[package]
name = "main-test"
version = "0.1.0"
edition = "2021"

[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
error[E0308]: mismatched types
  --> tests/main-test/src/main.rs:74:20
   |
74 |             actor: ActorHandle::new(actor_buffer_size, handle_msg, Data {}).await
   |                    ^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected trait `for<'a> <for<'a> fn(ActorMessage, &'a mut Data) -> impl Future<Output = bool> {handle_msg} as FnMut<(ActorMessage, &'a mut Data)>>`
              found trait `for<'a> <for<'a> fn(ActorMessage, &'a mut Data) -> impl Future<Output = bool> {handle_msg} as FnMut<(ActorMessage, &'a mut Data)>>`

The idea is to pass a handling function into the actor in order to define the handling code. For some reason I don't understand, it does not compile and the error does not make sense to me (expected trait and found are the same). What am I missing :laughing:? Thanks!

Please copy and paste the problematic code as a minimal reproducible example in a code block, rather than sharing it as a screenshot. The syntax for this is:

```
<Your code pasted here>
```

We can't copy the code for a screenshot and screenreaders can't properly interpret it.

1 Like

Done.

2 Likes

You can't use <F, Fut> if you want the future to be able to use the &mut D, because Fut is constrained to be exactly one future type, not a type parameterized by the lifetimes of the references passed in. You need to use a trait alias which lets you avoid declaring a Fut type parameter entirely, such as async_fn_traits::AsyncFnMut2.

You still may have trouble satisfying this bound on the calling side — the Rust compiler doesn't always know how to assign the right type to move |msg, data| async move { ... }, and in my experience, passing &mut references into such closures is especially problematic. (This is why “async closures” are desirable, so the compiler can think about the function and the async block as a unit which must be internally consistent, but that language feature is not stable yet.)

async_fn_traits
Thanks, I found that while searching for async fn parameters, but thought I won't need an extra crate for that. Seems like I didn't make any particular mistake, though, which is a good thing I guess. Will give the the other crate a try.

It's not that you need the crate; it's that you need the trait. If you'd rather avoid the dependency, it's easy to define the same trait yourself.