Search code examples
rusttraits

How to annotate a struct member that is a function that returns an object that implements Read and Write


I'm implementing a small service that accepts commands over TCP and relays it to device that also accepts commands over TCP.

I wrote the entire thing then went to test it and thought that the best way would be to use dependency injection so that I can provide some stream representing the device to test against that.

I am having trouble annotating the struct member that produces the stream.

How should I go about annotating a struct member that is a function that returns a type that implements Read and Write?

This is a rough sketch of the situation.

struct Handle {
    // I want to describe a function that returns a object that implements Read and Write
    stream_factory: Result<Box<dyn Read + Write>, Box<dyn std::error::Error>>
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let tcp_factory = || std::net::TcpStream::connect("127.0.0.1:1");
    let handle_a = Handle {
        stream_factory: tcp_factory
    }
    
    let file_factory = || std::file::File::options()
    .read(true)
    .write(true)
    .open("foo.txt");
    let handle_b = Handle {
        stream_factory: tcp_factory
    }
    
    Ok(())
}

Solution

  • You can either box the a trait that has Read and Write as a supertrait

    use std::io::{Read, Write};
    use std::net::TcpStream;
    
    trait ReadWrite: Read + Write {}
    impl ReadWrite for TcpStream {}
    
    struct Handle {
        // You will need to use Box<Fn() -> ...>
        // if you want to capture data in the factory function
        stream_factory: fn() -> Result<Box<dyn ReadWrite>, std::io::Error>,
    }
    
    fn main() {
        let tcp_factory = || {
            TcpStream::connect("127.0.0.1:1")
                .map(|x| Box::new(x) as Box<dyn ReadWrite>)
        };
        let handle_a = Handle {
            stream_factory: tcp_factory,
        };
    }
    

    Or you could use generics, though this may not work in your case if you need to pass different Read+Write types to the same place.

    struct Handle<T> 
    where T : Read + Write {
        stream_factory: fn() -> Result<T, std::io::Error>,
    }
    
    fn main() {
        let tcp_factory = || {
            TcpStream::connect("127.0.0.1:1")
        };
        let handle_a = Handle {
            stream_factory: tcp_factory,
        };
    }