Error with ? use in Tokio TCP Server

Hey Everyone,

I'm trying my hand at writing a TCP server with Tokio. I'm attempting to put some of the functionality into a lib instead of writing it all in the binary itself. here's the current relevant portion of the code in the lib: Basic Tokio TCP server in Rust Lib. - Pastebin.com

I'm getting an error on line 30 and 36 in the pastebin code regarding the ?. The error for both is:

the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`) cannot use the `?` operator in an async block that returns `()`

Now I've seen this before and usually I just need to have a Result return on the function, which I do in this case. And Tokio's documentation has the same thing but I'm getting the error. I'm not sure why. Maybe it's because I'm in the scope of the loop and it's not able to bubble up through it? I messed around a bit with the match arms trying to add OK(()) returns thinking although the error was pointing to those ?'s maybe adding returns to each relevant match arm would work but it didn't do anything. Is it because I'm in a lib instead of a bin for Rust that changes the way this logic behaves? I wouldn't think so but this is the first time I'm trying to make a lib.

Additionally, I have a UDP server written already in the lib that uses basically the same framework, same function return type, and that has the ? working just fine. Granted it's in a single loop and not spawning another tokio thread so there is admittedly less complexity with that portion: Basic Tokio UDP Impl. - Pastebin.com.

Does anyone have any guidance on the ? error?

Thanks

it's because you are spawning a new task so the ? operator inside it refers to the task's closure which does not return a result

Ahhh ok...so i need to either handle the error in place or find a way to propagate the error back through the task...which normally I would get the result of the task by a joinhandle i think? but i'm not sure if that'll work in this situation because the server is supposed to be listening forever.

Although, at this point in the code we're inside the forever loop already...and this should be just fired off when a connection is made. hmm...

currently, the return type of async blocks can only be inferred, there's no syntax to explicitly annotate an async block. typically, the inference is based on the tail position expression and early return statements. but in your code, there's no early return, and the last expression of the async block is an infinite loop (without break), which has the "never" type.

many ways to work around the problem:

  • you can create a variable binding to the async block, which can have type annotation.

    • on nightly, you can use #[feature(impl_trait_in_bindings)]
    let task: impl Future<Output = Result<(), Error>> = async move {
        //...
    };
    tokio::spawn(task);
    
    • on stable, you can only use trait objects, which means you have to Box it, unfortunately
    let task: Pin<Box<dyn Future<Output = Result<(), Error>>>> = Box::pin(async move {
        //...
    });
    tokio::spawn(task);
    
  • use an async closure (which supports type annotation, and immediately call it) instead of async block:

    tokio::spawn(async move || -> Result<(), Error> {
        //...
    }());
    
  • insert a "dummy" (meaning, dead code) return value with type annotation to guide the inference, e.g.

    • after the infinite loop
    tokio::spawn(async move {
        //...
        loop {
            //...
        }
        #[allow(unreachable_code)]
        Ok::<_, Error>(())
    });
    
    • use a false early return:
    tokio::spawn(async move {
        #[allow(unreachable_code)]
        if false {
            return Ok::<_, Error>(());
        }
        //...
        loop {
            //...
        }
    });
    

So I wanted to give the closure a try but I'm getting a mismatched types error.

mismatched types
expected `async` closure body `{async closure body@tudp-lib/src/lib.rs:78:52: 116:14}` (`Result<(), std::io::Error>`)
   found `async` closure body `{async closure body@tudp-lib/src/lib.rs:78:52: 116:14}` (`()`)
no two async blocks, even if identical, have the same type
consider pinning your async block and casting it to a trait object

That suggested using a Pin. I don't know what that is so I didn't want to use it at first but I tried your Pin line instead and got a similar error:

expected `{async block@tudp-lib/src/lib.rs:79:83: 79:93}` to be a future that resolves to `Result<(), Error>`, but it resolves to `()`
  expected enum `Result<(), std::io::Error>`
found unit type `()`
required for the cast from `Pin<Box<{async block@tudp-lib/src/lib.rs:79:83: 79:93}>>` to `Pin<Box<dyn Future<Output = Result<(), std::io::Error>>>>`

Both talk about the task resolving to a () instead of (),Error...I think i'm going to go back a few steps...I feel like I've fundamentally missed something about spawning with tokio.

I took a closer look at your code, and it turns out the server loop does have a break statement, which I overlooked before, at this line:

    if stream_ready.is_readable() {
        match socket.try_read(&mut buf) {
            Ok(0) => break,
    //...

this break makes the loop to have the type (), not the "never" type I thought it was. so my prevous answer is not accurate. the annotation of async closure is not wrong, but in addition, you need to really return a value of the correct type. basically, you have two options:

  • change the break statement so the loop actually returns the correct type:
        match socket.try_read(&mut buf) {
            Ok(0) => break Ok::<(), Error>(()),

  • add a (NOT dummy) return value after the loop, ignore the type of loop:
loop {
    //...
}
Ok::<(), Error>(())

note, if you already annotated the type, e.g. as a variable binding or async closure return type, then you don't need to specify it for the value, just Ok(()) is enough.

  • e.g. wrap spawn in a an inference helping function if you follow this pattern a lot
    fn my_spawn<F, O>(mu: F) -> JoinHandle<Result<O, Error>>
    where
        F: Future<Output = Result<O, Error>> + Send + 'static,
        Result<O, Error>: Send + 'static,
    {
        spawn(mu)
    }
    // ...
                my_spawn(async move {
                    // ...
                             Ok(0) => break Ok(()),