Sanity check this impl of a Future

Here is some code I have written.

pub struct WW_Promise_Waiter<T> {
    data: RefCell<Option<T>>,}

impl<T> Future for WW_Promise_Waiter<T> {
    type Output = T;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut v: Option<T> = None;
        std::mem::swap(self.data.borrow_mut().deref_mut(), &mut v);
        match v {
            None => Poll::Pending,
            Some(x) => Poll::Ready(x),}}}

Is there anything dangerous I should be aware of regarding the above code?

The intended usage is that this is a Future waiting for a message. When the message arrives, it'll be thrown into the RefCell<Option<T>>, and the poll call will return Ready. Until then, it will return Pending.

Feedback / criticism welcome.

Can you just call take after the borrow_mut? What purpose does the std::mem::swap serve?

Oh, and the answer to your question is, it is not thread-safe.

If you write impl Future for yourself, it's you who need to notify the runtime that some event is happened and the poll should be called. Good runtimes don't busy-poll its tasks as it would overly consume the CPU resource. Instead, each impl Future notify the runtime using wakers obtained via cx.waker().clone() when some event happens which potentially advance the future's state.

Side note: if you only take and set the value, Cell<Option<T>> would be better than the RefCell. Also, it's generally a good idea to run cargo fmt once before sharing some code.

1 Like

This is not necessarily a problem; it's fine for futures to be !Sync. (A multi-threaded executor might work only with Sync futures, but single-threaded executors do exist and shouldn't need that constraint.)

Is


pub enum Cos_Msg_Waiter_Err {
    Timeout,}

pub struct Cos_Msg_Waiter<T> {
    data: RefCell<Option<Result<T, Cos_Msg_Waiter_Err>>>,
    waker: RefCell<Option<Waker>>,}

impl<T> Cos_Msg_Waiter<T> {
    pub fn recv_msg(&mut self, t: T) {
        *self.data.borrow_mut() = Some(Ok(t));
        if let Some(waker) = &self.waker.borrow().deref() {
            waker.clone().wake()}}}

impl<T> Future for Cos_Msg_Waiter<T> {
    type Output = Result<T, Cos_Msg_Waiter_Err>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut v = None;
        std::mem::swap(self.data.borrow_mut().deref_mut(), &mut v);
        match v {
            None => {
                *self.waker.borrow_mut() = Some(cx.waker().clone());
                Poll::Pending}
            Some(x) => Poll::Ready(x),}}}

what you were suggesting ?

Conceptually, yes. But you may need to split the waker from the impl Future part since the future should be owned by the .await caller while the waker may need to be owned by some external callback mechanism, or the sender.

Sorry, I don't understand what you're saying (with regards to 'splitting') . Can you provide a use case where the above breaks ?

In particular, we don't "get" a waker until the poll is called and we return Pending.

For this reason, I don't see how to split this.

Hmm, 'split' may not be a sufficient description. Usually this kind of constructs are implemented as separate Sender and Receiver structs and some Inner struct shared between them using refcounts. You can check the structure of the oneshot channel implementation of the futures crate, but since you don't need thread-safety actual implementation can be much simpler.

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.