How can i return a generic type?

i would like to add tls support using tokio-tls to the following function, and i want it to be generic, when i changed the return type from Result<TcpStream> to Result<T> in this code

    async fn _connect_timeout<T>(&self) -> Result<T>
    where
        T: AsyncRead + AsyncWrite + Unpin,
    {
        let stream = timeout(
            self.connect_timeout,
            TcpStream::connect(self.url.socket_addrs(|| None)?[0])
        ).await??;

        Ok(stream)
    }

i received this error

31 |         let mut stream = self._connect_timeout().await?;
   |             ---------- consider giving `stream` a type
...
39 |         stream.write_all(req.as_bytes()).await?;
   |         ^^^^^^ cannot infer type


error[E0308]: mismatched types
  --> src/http/request.rs:79:12
   |
70 |     async fn _connect_timeout<T>(&self) -> Result<T>
   |                               - this type parameter
...
79 |         Ok(stream)
   |            ^^^^^^ expected type parameter `T`, found struct `tokio::net::tcp::stream::TcpStream`
   |

any ideas on how can i return a generic type?

You are trying to return a concrete type when your function expects you to return a generic type. Why do you want the type to be generic? Should the user be able to select the type when calling _connect_timeout::<T>()? Then you have to use T in the function body to create the object you want to return. You can implement a trait such as From<TcpStream> to construct a T inside the function:

 async fn _connect_timeout<T>(&self) -> Result<T>
    where
        T: AsyncRead + AsyncWrite + Unpin + From<TcpStream>,
    {
        let stream = T::from(timeout(
            self.connect_timeout,
            TcpStream::connect(self.url.socket_addrs(|| None)?[0])
        ).await??);

        Ok(stream)
    }

This could work but note that the user has to implement From<TcpStream> for any T they want to use.

i want _connect_timeout to be able to connect to eather http or https, without the caller having to worry about how it connect.

Basically the issue is that T is chosen by the caller, so if the caller chooses T to be some other type than TcpStream, it will fail to match up the types. You will probably either have to ask the user to provide you with the stream, or create your own trait with a method for connecting.

I am not well versed in tokio, so I can not give you a direct solution, but maybe you should implement the function once for each kind of stream you want to return, and then do what @alice said and declare your own trait with all of the functions you use in the _connect_timeout variants, then you can implement it for all of the different streams you are looking to return:

trait MyStream {
    fn connect<A: ToSocketAddr>(addr: A) -> Result<Self>;
    ...
}
impl MyStream for HttpStream { ... }
impl MyStream for HttpsStream { ... }
impl MyStream for TcpStream { ... }

 async fn _connect_timeout<T>(&self) -> Result<T>
    where
        T: AsyncRead + AsyncWrite + Unpin + MyStream,
    {
        let stream = timeout(
            self.connect_timeout,
            T::connect(self.url.socket_addrs(|| None)?[0])
        ).await??;

        Ok(stream)
    }

...
let stream = a._connect_timeout::<HttpsStream>()?;
2 Likes

From calling rustc with -Zteach, the output of that error will tell you:

given a type parameter T and a method foo:

trait Trait<T> { fn foo(&self) -> T; }

the only ways to implement method foo are:

  • constrain T with an explicit type:
impl Trait<String> for X {
    fn foo(&self) -> String { String::new() }
}
  • add a trait bound to T and call a method on that trait that returns Self:
impl<T: std::default::Default> Trait<T> for X {
    fn foo(&self) -> T { <T as std::default::Default>::default() }
}
  • change foo to return an argument of type T:
impl<T> Trait<T> for X {
    fn foo(&self, x: T) -> T { x }
}

and

for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

The solution in How can i return a generic type? is an appropriate one.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.