Search code examples
rustrust-tokio

How to create a polymorphic type for UnixStream and TcpStream


I have a struct called Connection that receives either a tokio::net::TcpStream or a tokio::net::UnixStream to talk to a remote server. Given that Rust doesn't have constructors I added a static new() method to my struct and perform an authentication handshake there before passing the instance of the stream to a new instance of Connection and returning that from new() to my end users.

My problem is how to create a temporary that can be set to either a UnixStream or a TcpStream so I can operate on it during the message exchange that makes the handshake. Since UnixStream and TcpStream don't have a common parent I'm at a loss on how to achieve this:

pub struct Configuration {
  tcp_socket: Option<SocketAddr>,
  unix_socket: Option<PathBuf>,
}

pub(crate) struct Connection {
  tcp: Option<TcpStream>,
  unix: Option<UnixStream>,
}

impl Connection {

  pub(crate) async fn new(configuration: &Configuration) -> Result<Connection, Box<dyn std::error::Error>> {
    let mut stream;
    if configuration.unix_socket().is_some() {
      stream = UnixStream::connect(configuration.unix_socket().unwrap()).await?;
    } else {
      stream = TcpStream::connect(configuration.tcp_socket().unwrap()).await?;
    }
    // Handshake code goes here...

    let conn = Connection {
      tcp: Some(stream),
      unix: None,
    };

    Ok(conn)
}

Solution

  • You can either use an enum like this:

    pub(crate) enum Connection{
      Tcp(TcpStream),
      Unix(UnixStream),
    }
    

    and match that everywhere you access it or implement a trait for both streams with the common functionality. This trait can be taken as a generic which would evaluate at compile time which version it is.

    Minimal Example:

    pub(crate) trait ExampleStream{
      fn connect<A>(address: A) -> Result<Self, Error>;
    }
    

    The enum option is closest to what you've written so far so I would suggest doing it that way.