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.

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.