rustiotraits

Using common part of supertrait and subtrait in struct


I am developing an interface that can create connections using TCPStream or SerialPort from this library.

I want to have a member of a struct that implements the traits std::io::Read and std::io::Write, which both of these connections provide.

SerialPort has trait SerialPort which is Send + std::io::Read + std::io::Write link

I have came up with this code:

trait ReadWriteIo: Read + Write {}

impl ReadWriteIo for TcpStream {}
impl ReadWriteIo for Box<dyn SerialPort> {}

struct ServerInterface {
    connection: Box<dyn ReadWriteIo>,
    // some other members
}

impl ServerInterface {
    fn new(use_tcp: bool) -> Result<ServerInterface> {
        let parser = MspParser::new();

        if use_tcp {
            let connection = TcpStream::connect("example:8080")?;

            return Ok(ServerInterface {
                connection: Box::new(connection),
                // other members
            });
        }

        // creates Box<dyn SerialPort>
        let connection = serialport::new("/dev/ttyUSB0", DEFAULT_BAUD_RATE)
            .timeout(time::Duration::from_millis(10))
            .open()?;

        Ok(ServerInterface {
            // this gives error
            // rustc: mismatched types
            // expected struct `Box<(dyn ReadWriteIo + 'static)>`
            // found struct `Box<dyn SerialPort>`
            connection,
        })
    }
}

From what I understand, my issue is that the compiler does not want to accept that Box<dyn SerialPort> can be Box<dyn ReadWriteIo>, which is essentially its subtrait.

Is it possible to achieve this? Or maybe should I switch to an enum? Using traits would be nicer for me as I am only using Read and Write, so no other changes in the code are necessary.


Solution

  • The easiest way will be to wrap the Box<dyn SerialPort> (that impls ReadWriteIo with Box and convert that to Box<dyn ReadWriteIo>:

    trait ReadWriteIo: Read + Write {}
    
    impl ReadWriteIo for TcpStream {}
    impl ReadWriteIo for Box<dyn SerialPort> {}
    
    struct ServerInterface {
        connection: Box<dyn ReadWriteIo>,
        // some other members
    }
    
    impl ServerInterface {
        fn new(use_tcp: bool) -> Result<ServerInterface> {
            let parser = MspParser::new();
    
            if use_tcp {
                let connection = TcpStream::connect("example:8080")?;
    
                return Ok(ServerInterface {
                    connection: Box::new(connection),
                    // other members
                });
            }
    
            // creates Box<dyn SerialPort>
            let connection = serialport::new("/dev/ttyUSB0", DEFAULT_BAUD_RATE)
                .timeout(time::Duration::from_millis(10))
                .open()?;
    
            Ok(ServerInterface {
                connection: Box::new(connection),
            })
        }
    }
    

    This will be slightly less efficient since it will involve double allocation, indirection and dynamic dispatch, but it should be fine most of the times.