Trait reference vs static dispatch


#1

What I’m trying to do is to basically write a tool that can take any thing that implements Read and based on its name wrap it in decompressor that also implements Read. This is simplified code:

trait Read {}

struct ExternalDecompressor1;
impl ExternalDecompressor1 { fn new<T: Read>(_r : T) -> ExternalDecompressor1 { ExternalDecompressor1 } }
impl Read for ExternalDecompressor1 {}

struct ExternalDecompressor2;
impl ExternalDecompressor2 { fn new<T: Read>(_r : T) -> ExternalDecompressor2 { ExternalDecompressor2 } }
impl Read for ExternalDecompressor2 {}

trait MyTrait {
    fn can_wrap(&self, x: &str) -> bool;
    fn wrap(&self, r: &Read) -> Box<Read>;
}

struct MyTraitImpl1;
impl MyTrait for MyTraitImpl1 {
    fn can_wrap(&self, x: &str) -> bool { x.ends_with(".gz") }
    fn wrap(&self, r: &Read) -> Box<Read> { unimplemented!() /* Box::new(ExternalDecompressor1::new(r)) */ }
}

struct MyTraitImpl2;
impl MyTrait for MyTraitImpl2 {
    fn can_wrap(&self, x: &str) -> bool { x.ends_with(".xz") }
    fn wrap(&self, r: &Read) -> Box<Read> { unimplemented!() /* Box::new(ExternalDecompressor2::new(r)) */ }
}

struct MyDynamicTraitUser {
    traits: Vec<Box<MyTrait>>,
}

impl MyDynamicTraitUser {
    fn new() -> MyDynamicTraitUser {
        MyDynamicTraitUser{ traits: vec![Box::new(MyTraitImpl1), Box::new(MyTraitImpl2)] }
    }

    fn usage(&self, s: &str, r: &Read) -> Box<Read> {
        self.traits.iter().find(|&u| u.can_wrap(s)).map(|ref u| u.wrap(r)).unwrap()
    }
}

fn main() {
}

I don’t have control over Extrnal Decompressors. I wan’t to have collection of possible handlers for different compression formats so I store them as boxed MyTraits. Finally r in MyTrait::wrap can’t be passed by value since it’s a trait and its size is not known. Which leads to my problem - on the one hand MyTraits get reference to Read but on the other ExtrnalDecompressors expect concrete implementation of Read passed by value.

I can maybe imagine that I can write additional struct that takes &Read and implements Read but would that be even possible? And if so would it be idiomatic?


#2

Passing by value vs passing by reference is actually a bit misleading in Rust. That’s closest analogy with other languages, but it doesn’t quite capture what Rust is about. References are an implementation detail related to borrowing, but the core of the issue is is moving ownership vs borrowing.

Your ExtrnalDecompressors need to own r, but you have only borrowed it. You need a reference that owns the value it references, and that is Box<Read>.


#3

What you are saying is that wrap (and usage) should take Box<Read> (like here)? If so that does not solve the main issue here as far as I can see. The problem is that ExternalDecompressors expect to receive some value of concrete type which implements Read, while I have access only to some form of reference to Read trait. Which means I have no way to know concrete type that Box<Read> contain.


#4

You can impl Read for Box<Read> and then pass Box<Read> to the decompressors. Box actually implements quite a few traits by proxying to the underlying type when that type implements the said interface, so I don’t think this pattern is unidiomatic.


#5

Sorry, Box<T> where T: Read. That will give you a box with the concrete type.

Box<T> is just a pointer, exactly like &T, and dereferencing (*foo) it will give you T.