Factory Function in Rust


#1

Hi All,

I’m trying to write a function that would take in a bool and based on its value, return an object. I’m using bool for simplicity but I plan on using an enum to add more types later.

Playground Code

use std::net::TcpStream;
use std::fs::File;
//any other structs that implement io::read and io::write

trait Transmit: std::marker::Sync + std::marker::Send {
    fn transmit(&self, String);
}

struct Comm<S> {
    stream: Option<S>,
}

impl<S: 'static + std::marker::Send + std::marker::Sync> Comm<S>
where
    S: std::io::Read + std::io::Write,
{
    fn new(s: S) -> Self {
        Comm { stream: Some(s) }
    }
}

impl<S: 'static + std::marker::Send + std::marker::Sync> Transmit for Comm<S>
where
    S: std::io::Read + std::io::Write,
{
    fn transmit(&mut self, buf: &str) {
        //What to do here?
        self.stream.unwrap().write_all(buf.as_bytes());
    }
}

//what can be the return type of Comm here?
//all i want to do is return an object that satisfies
//the traits std::io::Read and std::io::Write
fn build(dst:&str, f: bool) -> Comm/*<?>*/ {
    if f {
        let _f = File::create(dst).unwrap();
        let _fs = Comm::<File>::new(_f);
    } else {
        let _t = TcpStream::connect(dst).unwrap();
        let _ts = Comm::<TcpStream>::new(_t);
    }
}

fn main() {
    //what is the type of _vec here?
    let _vec = Vec::new();
    _vec.push(build("google.com",false));
    _vec.push(build("./abc.txt",true));
    
    for _s in _vec {
        _s.transmit("hello");
    }
}

What am I doing wrong here? What am I missing for build’s return type? Is there a better way to achieve this?


#2

Erase the object’s type by creating a trait object. (rust book: first edition, second edition)

Trait objects do not have a statically known size, so you must hide them behind a pointer. You can do this by returning a Box.

use ::std::io::prelude::*;
use ::std::io;

// Marker trait. This is necessary due to a limitation
//  of trait objects that they cannot have more than one
//  trait (except for Send and Sync), so you cannot
//  write Box<Read + Write>
pub trait ReadWrite: Read + Write {}
impl<T: Read + Write> ReadWrite for T {}

fn factory(flag: bool) -> Box<ReadWrite> {
    match flag {
        true => Box::new(::std::fs::File::create("lol.txt").unwrap()),
        false => Box::new(io::Cursor::new(vec![])),
    }
}

Also, since this is the next problem that everybody always runs into, here’s the magical incantation for returning a Box that needs to borrow something with a lifetime 'a: (Trait + 'a reads as: a Trait object that does not outlive lifetime 'a)

Box<Trait + 'a>

#3

I let out a little laugh at that. :slight_smile:


#4

Here’s a fleshed out example of what @ExpHP had above, plus a few changes to your other code there: playground

You can also use an enum if you want to avoid boxing and you’ll have control over the types of Read + Write impls you’ll use. It’s more boilerplate as you have to impl Read and Write for the enum by delegating to its variants, but may be worth it if boxing is an issue. Playground


#5

Thanks to both. That worked and helped me clear my understanding as well.