Simulate exception in async Rust
WARNING: This may be a bad practice. The point of this article is just to share my idea.
Exception is a popular feature in many programming languages. Unlike Rust where errors are considered as a return value of the Err
case of Result
, in these languages errors are thrown like panic
. The caller may catch the errors and process them similar to catch_unwind
in Rust. Different people may have different preferences on the error handling style, but panic-unwind is always considered a bad practice and should be avoided if possible. So using exception-like error handling in Rust is impossible.
Well, it is possible with async Rust, but we need a little trick. The main idea here is to stop polling the future after an exception is thrown.
First, we define an exception context, which stores the thrown exception if any.
1 |
|
For the function that may throw the exception, it needs to accept an argument with type &ExceptionContext
. If there is already a context-like type, you can consider embedding this struct into it.
Now, we try to implement the throw function. When an exception is thrown, the exception field will be set, and the code after the throw should not be executed. We can do this by core::future::pending().await
.
1 | impl ExceptionContext { |
In an async function with ExceptionContext
, if you want to throw an exception, just use the throw
method then await the return value.
1 | async fn send(ctx: &ExceptionContext, msg: &str, connected: bool) -> usize { |
Now, how should we execute the function with exception? We need to prepare a future that wraps the async function result. It polls the inner future until either getting Ready
or an exception is set in the context.
1 | pin_project_lite::pin_project! { // pin-project-lite = "0.2" |
Then we can implement a catching method that accepts an async function that requires an exception context and returns a Catching
future.
1 | impl ExceptionContext { |
Now we can execute a block with exceptions like this.
1 |
|
You can find the complete code here. I also made a crate to provide a general implementation of
ExceptionContext
and a Sync
version.
There are pros and cons of this approach. The biggest pro is that it has no panic-unwind. You can use panic = abort
while doing exception-style programming. However, there are the following cons.
- Only supports
async
function. - The future will be canceled if an exception occurs, which can be problematic.
- Exception-style is widely considered bad, especially in the Rust community, so even we can do it without panic-unwind it may still be a bad practice.
This is just my idea, whether to use it is up to you.