Awaiting when handling an Error

I'm messing around with async/await on Rust 1.39 and I ran into an interesting problem. dyn std::error::Error isn't Send, which means it can't be in scope when I await. I'm curious how I could best arrange my code to avoid this problem.

I would like to write code like this, where I intercept the parse error and call an async function before propagating it:

fn parse_query(query: &str) -> Result<(u16, u16), Box<dyn Error>> {
    // ...
}

async fn handle_client(socket: TcpStream) -> Result<(), Box<dyn Error>> {
    let mut client = Framed::new(socket, LinesCodec::new_with_max_length(1024));
    let query = match client.next().await {
        Some(Ok(q)) => q,
        _ => return Err(IdentError::NoQuery.into()),
    };
    let (local_port, remote_port) = match parse_query(&query) {
        Ok((l, p)) => (l, p),
        _ => {
            let response = format!("{} : ERROR : INVALID-PORT\r", query);
            client.send(response).await?;
            return Err(IdentError::InvalidPort.into());
        }
    };
    Ok(())
}

This creates a huge compilation error but the key is at the top:

100 |     F: Future<Output = ()> + 'static + Send,
    |                                        ---- required by this bound in `tokio::executor::spawn`
    |
    = help: the trait `std::marker::Send` is not implemented for `dyn std::error::Error`

Instead I seem forced to do something pretty extreme to make sure that the Error is completely out of scope.

    let failed;
    {
        let parsed = parse_query(&query);
        failed = parsed.is_err();
        if !failed {
            let (local_port, remote_port) = parsed.unwrap();
            println!("I have ports {} and {}", local_port, remote_port);
            // ... continued processing
        }
        // drop error here
    }
    if failed {
        let response = format!("{} : ERROR : INVALID-PORT\r", query);
        client.send(response).await?;
        return Err(IdentError::InvalidPort.into());
    }

Does anyone have any good ideas how to make this tidy?
EDIT: Particularly if I did want to propagate the original error, which means I can't even drop it.

1 Like

How about using Box<dyn Error + Send>

I've tried it but I'm struggling with the errors that it produces. Below is the function that returns the Error in question. The first problem was my use of Err(IdentError::InvalidPort.into()), which I seem to have fixed by calling Box::new explicitly (as below)

fn parse_query(query: &str) -> Result<(u16, u16), Box<dyn Error + Send>> {
    let ports: Vec<&str> = query.split(",").map(|s| s.trim()).collect();
    if ports.len() != 2 {
        return Err(Box::new(IdentError::InvalidPort));
    }
    Ok((ports[0].parse()?, ports[1].parse()?))
}

However I have problems with the parse() results. I feel like I've broken some of the From/Into magic that the stdlib provides but I can't work out what to do about it. :thinking:

error[E0271]: type mismatch resolving `<u16 as std::str::FromStr>::Err == std::boxed::Box<dyn std::error::Error + std::marker::Send>`
  --> src/main.rs:78:18
   |
78 |     Ok((ports[0].parse()?, ports[1].parse()?))
   |                  ^^^^^ expected struct `std::num::ParseIntError`, found struct `std::boxed::Box`
   |
   = note: expected type `std::num::ParseIntError`
              found type `std::boxed::Box<dyn std::error::Error + std::marker::Send>`

error[E0271]: type mismatch resolving `<u16 as std::str::FromStr>::Err == std::boxed::Box<dyn std::error::Error + std::marker::Send>`
  --> src/main.rs:78:37
   |
78 |     Ok((ports[0].parse()?, ports[1].parse()?))
   |                                     ^^^^^ expected struct `std::num::ParseIntError`, found struct `std::boxed::Box`
   |
   = note: expected type `std::num::ParseIntError`
              found type `std::boxed::Box<dyn std::error::Error + std::marker::Send>`

Ah, try using Box<dyn Error + Send + Sync> instead?

1 Like

How about that, it works perfectly! And I can use the code I originally intended. Thanks! :smiley:

The fixed function:

fn parse_query(query: &str) -> Result<(u16, u16), Box<dyn Error + Send + Sync>> {
    let ports: Vec<&str> = query.split(",").map(|s| s.trim()).collect();
    if ports.len() != 2 {
        return Err(IdentError::InvalidPort.into());
    }
    Ok((ports[0].parse()?, ports[1].parse()?))
}
1 Like

Yes, curiously there is no impl for the Error + Send case: https://doc.rust-lang.org/1.39.0/src/std/error.rs.html#233-271 (playground); at first glance it does look like an oversight so I'll investigate further and if it is, get the impl added.

2 Likes

For any future readers, I stumbled across a broader explanation of this problem in the async book: https://rust-lang.github.io/async-book/07_workarounds/04_send_approximation.html

One simple solution to this would be to drop the Rc before the .await , but unfortunately that does not work today. In order to successfully work around this issue, you may have to introduce a block scope encapsulating any non- Send variables.

Yes indeed. :smiley: Glad we were able to make it Send this time.