Prevent type from being held across await point

I have a bunch of different types that have a method that can asynchronously wait for an event:

impl A {
    async fn event(&mut self) -> Event { /*...*/ }
}

All the objects stem from a single Connection which receives all the events for all the objects and dispatches them accordingly:

impl Connection {
    /// Runs `fut` to completion. Will dispatch events to objects
    async fn run(&mut self, fut: impl Future) { /*...*/ }
}

The order in which events are handled is important, so when the Connection receives an event, it waits for its respective object to be listening before dispatching it and moving on to the next event. The issue here is that if an object exists but the user did not call object.event().await on it, the connection will wait forever for an event listener that will never come. An alternative solution would be to drop events that have no listeners, but this would lead to subtle issues and would be particularly problematic with multithreading.

I realized that what this can be boiled down to is that object cannot exist by itself across an await point, unless object.event().await has been called. Is there any kind of trick I can use here to get the compiler to warn or error if this was done? The only thing close to this I know of is the lint emitted when a RefCell is held across an await point. Alternatively, is there an entirely different approach I could use using async (not callbacks)?

Can you show us the signature for the function where you call object.event().await?

object.event().await could be called from any function, anywhere.

Got it. I was thinking that you could use the typestate pattern to identify whether the event method has been called.

Why do multiple objects exist with the requirement that each has to be called in a specific order?

The fact that these are async methods doesn't matter. I think this design resembles temporal coupling, which is a nasty one.

So the broader advice is, you probably guess it...

But anyway, while removing the temporal coupling from these objects may be difficult or impossible at this point, a simple fix would be to return an error when a method is called out of order. At least that way the condition is caught at runtime.

These objects are part of an existing protocol - the protocol has objects, each object has events it can receive, and the ordering of these events is meaningful for some groups of objects.

It's not necessarily that object.event().await needs to be called in a specific order - these can happen in whatever order - but the awaits need to return in a specific order.

How would this prevent the object being held across an await point?

No, it certainly would not help with that.

Have you considered using channels as an alternative?

1 Like

Use channels how? I send events to each of the objects using channels currently.