Boxing a Generic Trait Object with Trait type parameters


#1

Please refer to the code below (on playground):

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

trait OuterTrait<R: Read, T: InnerTrait> {}
trait InnerTrait {}

struct Inner {}
impl InnerTrait for Inner {}

#[allow(dead_code)]
struct Outer<R: Read, T: InnerTrait> {
    inner: T,
    reader: R,
}

impl<R: Read, T: InnerTrait> Outer<R, T> {
    fn new(reader: R, inner: T) -> Self {
        Self {
            reader: reader,
            inner: inner,
        }
    }
}

impl<R: Read, T: InnerTrait> OuterTrait<R, T> for Outer<R, T> {}

#[allow(unused_variables)]
fn main() {
    let payload = "Hello World";
    let cursor = Cursor::new(payload.as_bytes());
    let test = Outer::new(cursor, Inner {});

    // This works but isn't what I want
    let test_box: Box<OuterTrait<Cursor<&[u8]>, Inner>> = Box::new(test);

    // Doesn't work but what I would like to acheive... if possible?
    // let test_box: Box<OuterTrait<Read, InnerTrait>> = Box::new(test);
}

I am trying to implement some generic code that calls some methods on an OuterTrait object and as the caller, I don’t generally care about the Read and InnerTrait type parameters although implementors of OuterTrait would have to.

Is there any other way I could implement this? If you need more concrete code implementation, I could add more context.

Thanks in advance.


#2

Correct me if I’m wrong, but how I am reading this you don’t actually want type parameters on your Outer struct. You need those type parameters if you want static dispatch but this requires that you know the type at runtime. Would something like this help?

struct Outer {
    inner: Box<Read>,
    reader: Box<InnerTrait>,
}

Edit: I wrote some code, then I wrote some other code. I believe the later code is more aligned with your target. Feel free to disregard the following:

https://play.integer32.com/?gist=029237eb3392fa0a10e42c83b2f3ab47&version=undefined

Note that in the linked code, I still wrap the Outer struct in a box. I doubt that you want to do that. Don’t use the Box<OuterTrait> unless there are other structs also implementing OuterTrait.


#3

The suggestion by @roSievers will make the following line work:
let test_box: Box<OuterTrait<Box<Read>, Box<InnerTrait>>> = Box::new(test);

Your confusion is very understandable. Rust adds Sized bounds to everything, so:
impl<R: Read, T: InnerTrait> OuterTrait<R, T> for Outer {}
Actually means:
impl<R: Read + Sized, T: InnerTrait + Sized> OuterTrait<R, T> for Outer {}
This is convenient since almost everything is Sized, but trait objects are not Sized and you have hit the corner case where this blows up in your face.


#4

I thought about it some more, maybe you want to resolve it this way?

https://play.integer32.com/?gist=cdcafffff2774575ec867261422fe881&version=undefined

Again, the types aren’t quite as you left them here I leave type parameters on the Outer<R, T> struct but drop them from the OuterTrait. From how you are using the trait, I looks like you don’t actually want the OuterTrait to have any parameters.


#5

Another option could be to keep the type params on Outer as-is but then add impl<T: InnerTrait> InnerTrait for Box<T> {}. This way callers can pick static or dynamic dispatch as they see fit.


#6

Thanks guys for the suggestions.

I have implemented something that seem to work: https://github.com/lawliet89/trait-obj, although I’m not really sure if this is the best way to do it.

You can see my use case from the code in the repository. Basically, I wanted to implement a generic validator for some CSV files.

@leodasvacas Thanks for the explanation. I was guessing that the implicit Sized was to be blamed although the compiler error message (below) wasn’t particularly helpful.

error[E0277]: the trait bound `Outer<std::io::Cursor<&[u8]>, Inner>: OuterTrait<std::io::Read, InnerTrait>` is not satisfied