0

I'm facing compiler errors with dynamic traits in a scenario where I either have a std::net::TcpStream or an native_tls::TlsStream<TcpStream>. The idea for my algorithm is the following:

  1. create TCP stream in var "tcp"
  2. if scheme is https => replace same var with new TlsStream<TcpStream>.
  3. Write and receive data on stream.

The important traits I need are std::io::Read and std::io::Write. Therefore I thought, I just model my "tcp" var as Box<dyn Read + Write>. But no matter how I tried, I always got compilation errors. Below you can see a minimal example, that doesn't compile.

use std::net::{IpAddr, Ipv4Addr, TcpStream};
use std::io::{Write as IoWrite, Read as IoRead};
use native_tls::TlsConnector;

fn main() {
    let use_https = true;
    // IP of "example.com".
    let ip_addr = std::net::IpAddr::V4(Ipv4Addr::new(85, 13, 155, 159));
    let port = if use_https { 443 } else { 80 };
    let tcp_stream = TcpStream::connect((ip_addr, port)).unwrap();
    let tcp_stream = maybe_connect_to_tls(tcp_stream, use_https);
}

fn maybe_connect_to_tls(tcp: TcpStream, use_https: bool) -> Box<dyn IoRead + IoWrite> {
    if use_https {
        let tls = TlsConnector::new().unwrap();
        let tcp = tls.connect("example.com", tcp).unwrap();
        Box::new(tcp)
    } else {
        Box::new(tcp)
    }
}

Playground link

This is the error I get when I try to compile it:

error[E0225]: only auto traits can be used as additional traits in a trait object
  --> src/main.rs:14:78
   |
14 | fn maybe_connect_to_tls(tcp: TcpStream, use_https: bool) -> Box<dyn IoRead + IoWrite> {
   |                                                                     ------   ^^^^^^^ additional non-auto trait
   |                                                                     |
   |                                                                     first non-auto trait
   |
   = help: consider creating a new trait with all of these as super-traits and using that trait here instead: `trait NewTrait: std::io::Read + std::io::Write {}`
   = note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>

I also tried maybe_connect_to_tls<T>(tcp: TcpStream, use_https: bool) -> Box<T> where T: IoWrite + IoRead + ?Sized but whatever I try leads to other compilation failures..

Any idea how I can solve this? I want a common stream object and it should be invisible to the application what the specific implementation is.

Elias Holzmann
  • 3,216
  • 2
  • 17
  • 33
phip1611
  • 5,460
  • 4
  • 30
  • 57

1 Answers1

2

For Rust internal reasons, your trait objects can contain no more than one non-auto trait. However, you can simply create a trait which depends on the Read and Write traits:

use std::net::{IpAddr, Ipv4Addr, TcpStream};
use std::io::{Write as IoWrite, Read as IoRead};
use native_tls::TlsConnector;

trait ReadAndWrite: IoRead + IoWrite {
}

impl <T: IoRead + IoWrite> ReadAndWrite for T {
}

fn main() {
    let use_https = true;
    // IP of "example.com".
    let ip_addr = std::net::IpAddr::V4(Ipv4Addr::new(85, 13, 155, 159));
    let port = if use_https { 443 } else { 80 };
    let tcp_stream = TcpStream::connect((ip_addr, port)).unwrap();
    let tcp_stream = maybe_connect_to_tls(tcp_stream, use_https);
}

fn maybe_connect_to_tls(tcp: TcpStream, use_https: bool) -> Box<dyn ReadAndWrite> {
    if use_https {
        let tls = TlsConnector::new().unwrap();
        let tcp = tls.connect("example.com", tcp).unwrap();
        Box::new(tcp)
    } else {
        Box::new(tcp)
    }
}

Playground link

Elias Holzmann
  • 3,216
  • 2
  • 17
  • 33