tokio::signal::unix::Signal::recv vs poll_recv

Why are there the two (public) methods

recv returns a Future, so I could just call .recv().poll(cx) instead of .poll_recv(cx), right? Why do functions like poll_recv exist in the public API? Is this because polling a future may require pinning? Or is this because creating and working with the future is overhead in some cases?

Purely asking out of curiosity here and to understand async Rust better; I don't have a particular problem (I don't think I will ever need the poll_recv function unless I make my own runtime I think).

.recv() returns a future which calls .poll_recv(cx) when polled. See https://docs.rs/tokio/latest/src/tokio/signal/mod.rs.html#86-99. Either can be implemented in terms of the other, but having both is more convenient. .recv() is more useful in async functions, while .poll_recv(cx) is more useful when manually implementing Future.

I wonder if this is really something that should be (generally) done in an API, or if it wouldn't be better to have macros, for example, which can do the conversion where needed (at the caller side).

The reason why I'm asking: Does that mean when I implement my own async functions and methods, that I should, whenever easily possible, provide two variants, one taking a Context and returning a Poll, and another function/method that returns a Future? Or when should I consider to provide such dual-faced API myself?

The poll_recv method exists primarily so that it can be used in tokio_stream::wrappers::SignalStream. If only the recv method existed, then SignalStream would have to use the same implementation strategy as tokio_stream::wrappers::BroadcastStream, which is much more complicated.

In Tokio, we do not implement the Stream trait for any of our types because we don't want the futures crate in our public API. Instead we are waiting for the standard library to provide a Stream trait in std. Tokio generally provides a poll_* method whenever the type is going to implement Stream once it is added to std so that tokio_stream can use it to provide a wrapper. (There are technical reasons that broadcast doesn't provide a poll method.)

For most other libraries, they would not care as much about stability as Tokio and just implement Stream directly. This gives both the poll method via the trait, but also an async fn via StreamExt::next for free.

1 Like