
Description
Timeouts are confusing. @spacejam recently wrote an example that contains the following piece of code:
stream
.read_to_end(&mut buf)
.timeout(Duration::from_secs(5))
.await?;
The problem here is that we need two ?
s after .await
and it's easy to forget that.
I think the confusing part is in that the .timeout()
combinator looks like it just transforms the future in a similar vein to .map()
or .and_then()
, but it really does not!
Instead, .timeout()
bubbles the result of the future so that its type becomes Result<Result<_, io::Error>, TimeoutError>
.
Perhaps it would be less confusing if timeout()
was a free-standing function in the future
module rather than a method on the time::Timeout
extension trait?
future::timeout(
stream.read_to_end(&mut buf),
Duration::from_secs(5),
)
.await??;
This timeout()
function would stand alongside ready()
, pending()
, and maybe some other convenience functions in the future
module.
Here's another idea. What if we had io::timeout()
function that resolves to a Result<_, io::Error>
instead of bubbling the results? Then we could write the following with a single ?
:
io::timeout(
stream.read_to_end(&mut buf),
Duration::from_secs(5),
)
.await?;
Now it's also more obvious that we're setting a timeout for an I/O operation and not for an arbitrary future.
In addition to that, perhaps we could delete the whole time
module? I'm not really a fan of it because it looks nothing like std::time
and I generally dislike extension traits like Timeout
.
Note that we already have a time-related function, task::sleep()
, which is not placed in the time
module so we probably shouldn't worry about grouping everything time-related into the time
module. I think it's okay if we have io::timeout()
and future::timeout()
.
Finally, here's a really conservative proposal. Let's remove the whole time
module and only have io::timeout()
. A more generic function for timeouts can then be left for later design work. I think I prefer this option the most.