#Async Function Taking Generic Argument Does Not Implement Send

20 messages · Page 1 of 1 (latest)

fluid stump
#

I'm writing a simple server that handles commands.
The original implementation was

struct Server<T, F> 
where
  F: for<'a> AsyncFn4<
    &'a mut T,
    &'a str,
    &'a UdpSocket,
    &'a SocketAddr,
    Output = Result<()>,
{
  context: T,
  socket: UdpSocket,
  handler: F,
}
...
// main loop pub async fn run(mut self)
let res = (self.handler)(&mut self.context, msg, &self.socket, origin).await;

Now I want to spawn a new task when I receive a message. And I want to modify the server such that

pub struct Context<T> {
  pub data: Arc<Mutex<T>>,
  pub socket: Arc<UdpSocket>,
  pub origin: SocketAddr,
}
pub struct Server<T, F> 
where
  F: for<'a> AsyncFn2<
    &'a Context<T>,
    &'a str,
    Output = Result<()>,
{
  data: Arc<Mutex<T>>,
  socket: Arc<UdpSocket>,
  handler: F,
}
...
// main loop
tokio::spawn(async {
  let context = Context {
    data: self.data.clone(),
    socket: self.socket.clone(),
    origin,
  };
  let res = (self.handler)(&context, msg).await;
});

This however errors out.

future cannot be sent between threads safely
within impl Future<Output = ()>, the trait Send is not implemented for <F as AsyncFn2<&Context<T>, &str>>::OutputFuture
And it seems no matter how I try to specify Send, it still fails.

atomic portal
#

I assume self is a Server?

What type is AsyncFn2?

fluid stump
atomic portal
#

I think the bound is F: Send + for<'a> AsyncFn2<...>?

#

Oh wait

#

No, you want the bound on the output

#

<F as AsyncFn2<...>>::OutputFuture: Send

#

(this would probably be easier and look better if you did it "by hand" instead of using a crate for it tbh)

fluid stump
fluid stump
# atomic portal (this would probably be easier and look better if you did it "by hand" instead o...

Taking it by hand, I end up with

impl<T, F, Fut> Server<T, F, Fut>
where
    T: Send + Sync,
    F: for<'a> Fn(&'a Context<T>, &'a str) -> Fut + Send + Sync + 'static,
    Fut: Future<Output = Result<()>> + Send,
...
tokio::spawn(async {
  let res = self.handler(&context, &msg).await;
});

Where I then error out with

the parameter type T may not live long enough
...so that the type impl Future<Output = ()> will meet its required lifetime bounds...
...so that the reference type &Context<T> does not outlive the data it points at
And it recommends that I add the explicit lifetime bound T: 'static but that's a pretty heavy hammer

atomic portal
#

tokio::spawn kinda requires that, though

#

As in, it's in the trait bounds of that function

fluid stump
#

It requires the function to be 'static but I don't see why the arguments need to be. I mean, it's not complaining about &Context outliving the future, it's explicitly complaining about the T inside it.

atomic portal
#

Because when you call an async function, this doesn't actually do anything. It just returns a future that stores the arguments, I think

#

(I know it does that. I'm not sure this is the specific error)

#

The future does work when it's .poll() ed, which Tokio handles for you

fluid stump
#

Right I understand that much. I think the more frustrated I get, the less thinking I'm willing to do 😛
But here, Context owns T (actually an Arc<Mutex<T>> which really isn't ownership but as long as &Context is valid then its contents should be as well,) so I'm unsure why it's questioning its lifetime or more specifically I'm unsure how to tell it that it lives long enough when it's owned by Context.

fluid stump
#

Well, 'static does seem to make it work for now so I guess I can't complain

#

ah, but I get the return of error from last week 😛

fluid stump
#

So if I go crazy here and try to match the bounds of tokio::spawn exactly, I get

impl<T, F, Fut> Server<T, F, Fut>
where
    T: Send + Sync + 'static,
    F: for<'a> AsyncFn3<
        &'a Context<T>,
        &'a str,
        &'a str,
        Output = Result<()>,
    > + Copy + Send + Sync + 'static,
    for<'a> <F as AsyncFn3<
        &'a Context<T>,
        &'a str,
        &'a str,
    >>::OutputFuture: Send + Sync +'static,
    for<'a> <F as AsyncFn3<
        &'a Context<T>,
        &'a str,
        &'a str,
    >>::Output: Send + Sync + 'static,

Which not only still fails when spawning with

mismatched types
expected type Send
found type for<'a> Send
But I also now get
higher-ranked lifetime error
could not prove for<'a> <F as AsyncFn3<&'a Context<T>, &'a str, &'a str>>::OutputFuture: 'static