Future cannot be unpinned

I am trying to build a discord bot in rust using the serenity.rs library. I have been trying to implement a custom command handler using a HashMap. All of that worked so far, however when I try to execute the command, its giving me an error dyn std::future::Future<Output = ()> cannot be unpinned. I have tried researching this on google and even asked chatgpt for help, but none of it worked. Here is a link to my code in the playground.

When you're making dyn Future, you need to use either Box::pin(future) or pin!(future) macro.

1 Like

I dont quite understand where I need to use them exactly.

1 Like

You currently have

type CommandFunction = Box<
    dyn Fn(&Context, &Message) -> Box<dyn Future<Output = ()>> + Send + Sync
>;

Change this to

use std::pin::Pin;

type CommandFunction = Box<
    dyn Fn(&Context, &Message) -> Pin<Box<dyn Future<Output = ()>>> + Send + Sync
>;

Then to satisfy that type, where you have

event_handler.register_command("ping".to_string(), Box::new(|_: &Context, _: &Message| {

you will need to replace Box::new() with Box::pin() (which creates a Pin<Box<T>> from T).

By the way, Pin<Box<dyn Future... is such a common pattern that there is a type alias for it: futures::future::BoxFuture.

3 Likes

That does resolve the issue of not being able to await the future, but creates the problem of it not being able to be shared between threads safely, specifically in the "message" function in the EventHandler.

No, it doesn't create the problem, it reveals it. This is an important thing to understand: the compiler will not tell you “everything” wrong with your program. It can't; once one type error is encountered, it can't just guess what you really meant and continue. Do not assume that new error messages appearing mean that you're not making forward progress; it can just as often mean that you've solved the serious problem and now have lesser problems.

What's “it” here? Please quote the compiler errors in full; precision is critical.

One thing I neglected to mention in my previous post: you need + Send on the future too. (But that can't be the error you refer to because that error is missing Sync, not missing Send.)

Pin<Box<dyn Future<Output = ()> + Send>>

This is also built into BoxFuture, so using BoxFuture is a really good idea since it means you can avoid accidentally forgetting any of the pieces.

7 Likes

I apologize for my wording. I understand that the compiler cannot guess what I want and I misspoke.
By "it" I mean the future created by the async block of the message function. It is hard for me to deduce the exact issue from the compiler output. I really love rust's compiler output for synchronous programming, because it can easily guide you to help or straight up give you the solution, but this error seems very cryptic to a newbie

The MutexGuard in std isn't Send because it's UB to release the lock on a different thread that it was created on (on some platforms, e.g. if using posix pthreads).

You're holding a lock:

let commands = self.commands.lock().unwrap();

and you can't hold that lock across an await. You probably shouldn't (use an async lock), either — that would mean that at most one command can be running at a time.

Instead, make it possible to use the Command without holding the lock. To do this, put the functions in Arc instead of Box, which you can clone and use the clone without holding the lock.

4 Likes

By functions I assume you mean the HashMap, which is of type

commands: Arc<Mutex<HashMap<String, CommandFunction>>>

so I dont really understand. If you, however, mean the function, then changing it from

type CommandFunction = Box<dyn Fn(Context, Message) -> BoxFuture<'static, ()> + Send + Sync>;

to

type CommandFunction = Arc<dyn Fn(Context, Message) -> BoxFuture<'static, ()> + Send + Sync>;

then that didnt do it for me.

The last line of code is correct, but you have to clone the function and drop the lock. That is, instead of

let commands = self.commands.lock().unwrap();
if let Some(func) = commands.get(command_name) {

make it

let func = self.commands.lock().unwrap().get(command_name).cloned();
if let Some(func) = func {

That .cloned() is Option::cloned() which will turn the Option<&Arc<dyn Fn... you get from get() into Option<Arc<dyn Fn..., which isn't a reference and so is independent of the lock. And by getting rid of the commands variable, the lock is unlocked (the lock guard is dropped) as soon as the statement ends, so that it isn't still around when we later .await.

3 Likes

Thank you all for the wonderful help. I know what topics I need to brush up on :sweat_smile: and I hope you are having a wonderful day.

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.