hyperium / tonic

A native gRPC client & server implementation with async/await support.
https://docs.rs/tonic
MIT License
10.02k stars 1.02k forks source link

Tonic v0.13 fails to build what worked on v0.12 #1895

Open bobrik opened 2 months ago

bobrik commented 2 months ago

I have the following function:

    async fn new_unix<P>(identity: String, path: P) -> Result<Self, Error>
    where
        P: AsRef<Path> + Send + Sync + Clone + 'static,
    {
        let connector = tower::service_fn(move |_| UnixStream::connect(path.clone()));

        let channel = Endpoint::try_from("http://[::]:0")
            .expect("empty addr should work")
            .connect_with_connector_lazy(connector);

        let inner = QsEdgeReadClient::new(channel).max_decoding_message_size(usize::MAX);

        Ok(Self { identity, inner })
    }

It works perfectly fine with these deps:

tonic = { version = "0.11" }
prost = { version = "0.12" }
prost-types = { version = "0.12" }

As soon as I bump them, errors happen:

error[E0277]: the trait bound `tokio::net::UnixStream: hyper::rt::io::Read` is not satisfied
   --> src/lib.rs:43:42
    |
43  |             .connect_with_connector_lazy(connector);
    |              --------------------------- ^^^^^^^^^ the trait `hyper::rt::io::Read` is not implemented for `tokio::net::UnixStream`
    |              |
    |              required by a bound introduced by this call
    |
    = help: the following other types implement trait `hyper::rt::io::Read`:
              &mut T
              Box<T>
              Pin<P>
              hyper::upgrade::Upgraded
              hyper_timeout::stream::TimeoutReader<R>
              hyper_timeout::stream::TimeoutStream<S>
              hyper_timeout::stream::TimeoutWriter<W>
              hyper_util::rt::tokio::TokioIo<T>
note: required by a bound in `Endpoint::connect_with_connector_lazy`
   --> /Users/ivan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tonic-0.12.2/src/transport/channel/endpoint.rs:392:22
    |
389 |     pub fn connect_with_connector_lazy<C>(&self, connector: C) -> Channel
    |            --------------------------- required by a bound in this associated function
...
392 |         C::Response: rt::Read + rt::Write + Send + Unpin,
    |                      ^^^^^^^^ required by this bound in `Endpoint::connect_with_connector_lazy`

error[E0277]: the trait bound `tokio::net::UnixStream: hyper::rt::io::Write` is not satisfied
   --> src/lib.rs:43:42
    |
43  |             .connect_with_connector_lazy(connector);
    |              --------------------------- ^^^^^^^^^ the trait `hyper::rt::io::Write` is not implemented for `tokio::net::UnixStream`
    |              |
    |              required by a bound introduced by this call
    |
    = help: the following other types implement trait `hyper::rt::io::Write`:
              &mut T
              Box<T>
              Pin<P>
              hyper::upgrade::Upgraded
              hyper_timeout::stream::TimeoutReader<R>
              hyper_timeout::stream::TimeoutStream<S>
              hyper_timeout::stream::TimeoutWriter<W>
              hyper_util::rt::tokio::TokioIo<T>
note: required by a bound in `Endpoint::connect_with_connector_lazy`
   --> /Users/ivan/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tonic-0.12.2/src/transport/channel/endpoint.rs:392:33
    |
389 |     pub fn connect_with_connector_lazy<C>(&self, connector: C) -> Channel
    |            --------------------------- required by a bound in this associated function
...
392 |         C::Response: rt::Read + rt::Write + Send + Unpin,
    |                                 ^^^^^^^^^ required by this bound in `Endpoint::connect_with_connector_lazy`

I tried my best to figure out a way to make this work based on the uds example:

        let channel = Endpoint::try_from("http://[::]:0")
            .expect("empty addr should work")
            .connect_with_connector(tower::service_fn(|_: Uri| async {
                Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(path).await?))
            }))
            .await?;
error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
  --> src/lib.rs:48:55
   |
48 |             .connect_with_connector(tower::service_fn(|_: Uri| async {
   |              ----------------------                   ^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
   |              |
   |              the requirement to implement `FnMut` derives from here
49 |                 Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(path).await?))
   |                                                                          ---- closure is `FnOnce` because it moves the variable `path` out of its environment
   |
   = note: required for `ServiceFn<{closure@src/lib.rs:48:55: 48:63}>` to implement `Service<Uri>`
        let channel = Endpoint::try_from("http://[::]:0")
            .expect("empty addr should work")
            .connect_with_connector(tower::service_fn(move |_: Uri| async {
                Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(path.clone()).await?))
            }))
            .await?;
error: lifetime may not live long enough
  --> src/lib.rs:48:69
   |
48 |               .connect_with_connector(tower::service_fn(move |_: Uri| async {
   |  _______________________________________________________-------------_^
   | |                                                       |           |
   | |                                                       |           return type of closure `{async block@src/lib.rs:48:69: 50:14}` contains a lifetime `'2`
   | |                                                       lifetime `'1` represents this closure's body
49 | |                 Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(path.clone()).await?))
50 | |             }))
   | |_____________^ returning this value requires that `'1` must outlive `'2`
   |
   = note: closure implements `Fn`, so references to captured variables can't escape the closure

The uds example in the repo conveniently avoids this problem by having path declared in the closure itself. It would be nice to have a more realistic example that takes the path from the outside.

bobrik commented 1 month ago

I think I figured out the lifetime issue: path needs to be cloned outside of async {}.

        let connector = tower::service_fn(move |_| {
            let path = path.clone();
            async { Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(path).await?)) }
        });