How does this constructor work?


#1

Given the following constructor:

pub struct TBufferedWriter {
    wbuf: Vec<u8>,
    writer: Box<Write + Send>,
}

impl TBufferedWriter {
    /// Create a `TBufferedWriter` with default-sized internal write buffer
    /// that wraps a `writer`.
    pub fn new<W>(writer: W) -> TBufferedWriter
        where W: 'static + Write + Send
    {
        let writer = Box::new(writer) as Box<Write + Send>;
        TBufferedWriter::new_with_capacity(DEFAULT_WBUFFER_CAPACITY, writer)
    }

    /// Create a `TBufferedWriter` with an internal write buffer of size
    /// `capacity` that wraps a `writer`.
    pub fn with_capacity<W>(capacity: usize, writer: W) -> TBufferedWriter
        where W: 'static + Write + Send
    {
        let writer = Box::new(writer) as Box<Write + Send>;
        TBufferedWriter::new_with_capacity(capacity, writer)
    }

    fn new_with_capacity(capacity: usize, writer: Box<Write + Send>) -> TBufferedWriter
    {
        TBufferedWriter {
            wbuf: Vec::with_capacity(capacity),
            writer: writer,
        }
    }
}

How do both these constructor calls work?!

#[cfg(test)]
mod tests_new {
    use std::io::{Read, Write};
    use std::net::TcpStream;
    use std::thread;

    use super::*;
    use ::transport::{TPassThruTransport, TRead, TWrite, TTransport};
    use ::transport::mock::{TMockReader, TMockWriter};

    #[test]
    fn test() {
        let w0 = TMockWriter::with_capacity(40);
        let w0 = TBufferedWriter::new(Box::new(w0) as Box<Write + Send>);

        let w1 = TMockWriter::with_capacity(40);
        let w1 = TBufferedWriter::new(w1);
    }
}

#2

Box implements Write if the inner type does. Is that what you were wondering?


#3

I assume you mean the two calls to TBufferedWriter::new(). It is written generically to accept anything that implements Write + Send + 'static.

The first uses a trait object Box<Write + Send>, which can contain any type that implements Write (and is Send), and uses dynamic dispatch to call the methods of Write. The second uses TMockWriter directly, which will result in the compiler specializing new() for this type and doing static dispatch.

However, in this case new() immediately creates another Box, so both cases are eventually using dynamic dispatch. It seems that the first case results in a double indirection, which is unnecessary and should be avoided.


#4

Yeah, I’d look at the option that @Nemo157 shared at How can I define a constructor that takes a trait object or the bare type itself?, where you can get specialization and no indirection when using the naked type.


#5

Kinda. I was curious if the compiler unpacked the boxed type and then repacked it into the box. It looks like (as seen in the response below) it simply reboxes it again, introducing another level of indirection.

Note that I’m not surprised by the reboxing: it’s kinda what I imagined was happening; I just hoped otherwise!


#6

Thanks @birkenfeld!