Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attribute macros #51

Open
chancancode opened this issue Oct 25, 2022 · 0 comments
Open

Attribute macros #51

chancancode opened this issue Oct 25, 2022 · 0 comments

Comments

@chancancode
Copy link

chancancode commented Oct 25, 2022

There is a problem I kept running into with async functions with something like Axum/Hyper.

With the async function syntax, it is very easy for its output Future to accidentally not be Send because the compiler thinks you are holding onto something that is not Send across an await point. One example would be a (synchronous/blocking) MutexGuard.

For example:

pub struct Foo<T: Send + Clone + Default> {
    shared: Arc<Mutex<T>>,
}

impl<T: Send + Clone + Default> Foo<T> {
    pub async fn this_is_correctly_send(&self) -> T {
        let thing = self.shared.lock().unwrap().clone();
        sleep(Duration::from_secs(1)).await;
        thing
    }
    
    pub async fn this_is_also_send(&self) -> T {
        let thing = {
            let mut guard = self.shared.lock().unwrap();
            mem::replace(guard.deref_mut(), T::default())
        };
        sleep(Duration::from_secs(1)).await;
        thing
    }
    
    pub async fn this_is_accidentally_not_send(&self) -> T {
        let mut guard = self.shared.lock().unwrap();
        let thing = mem::replace(guard.deref_mut(), T::default());

        // As of now, whether this drop is here makes no difference to the compiler
        // Eventually, -Zdrop-tracking will change that
        drop(guard);

        sleep(Duration::from_secs(1)).await;
        thing
    }
}

Some related background information:

This matters because the !Send-ness can propagate up very far and eventually when mounting the handler to something like the Axum router, it causes a cryptic error and takes a long time to trace back to where the problem is.

To guard against these kind of issues, I can do something like this (a bit simplified):

impl Foo {
    async fn foo() {
        // ...
    }

    #[allow(dead_code)]
    fn __foo_must_be_send(&self) {
        #![allow(unused_must_use)]
    
        fn it_sends<T: Send>(it: T) -> T {
            it
        }
    
        it_sends(self.foo());
    }
}

This works but is pretty distracting. It would be nice to be able to do something like:

impl Foo {
    #[assert_impl(Send)]
    async fn foo() {
        // ...
    }
}

While looking for something that does that, this crate came up. Does something like that seem like a good fit for this crate, or are there some reasons why it is not possible/not a good fit?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant