How store trait object with a generic param in my struct

I have a function that returns Box<dyn ArrayView1<T> + 'a>, a trait method specified as follows:

pub trait Array2<T: Debug + Display + Copy + Sized>: MutArrayView2<T> + Sized + Clone { ... }
    fn get_row<'a>(&'a self, row: usize) -> Box<dyn ArrayView1<T> + 'a>
    where
        Self: Sized;

I would like to host the function result in my struct. However, I want to do so while remaining generic over T (the type hosted in the array). The following struct does not compile.

#[derive(Debug)]
pub struct LogitData<'a, A: Sized> {
    pub x: DenseMatrix<f64>,
    pub y: Vec<BinaryTargetDataType>,
    findings: Option<LogitFindings<'a, A>>,
}
#[derive(Debug)]
pub struct LogitFindings<'a, A: Sized> {
    intercept: f64,
    coefficients: Box<dyn ArrayView1<A> + 'a>,
}

When I run:

        self.inner.findings = Some(LogitFindings {
            intercept: *lr.intercept().get((0, 0)),
            coefficients: Box::new(*(lr.coefficients().get_row(0))), // << the trait method
        });

I'm getting a couple of errors that are difficult to interpret.

  1. the size for values of type dyn ArrayView1<f64> cannot be known at compilation time the trait Sized is not implemented for dyn ArrayView1<f64>.

  2. mismatched types; expected struct Box<dyn ArrayView1<A>> found struct Box<dyn ArrayView1<f64>>

Does anyone have any guidance on how to think through this?

Addendum

Perhaps clear to most, but the first error does not depend on the A being generic. In other words, when I make A concrete using f64, I still get the "can't be sized at compilation time...".

Please provide a complete, minimal example. There are many dependencies in your code that aren't obvious to infer.

Hard to be sure without more context or the error output, but my guesses are that

  1. is due to what looks like an unnecessary reboxing
    -coefficients: Box::new(*(lr.coefficients().get_row(0))),
    +coefficients:            lr.coefficients().get_row(0),
    
  2. is because lr only implements or is bound to implement Array2<f64>, not Array2<A>
1 Like

On second thought, I can see that you are trying to take ownership of a trait object by-value, by dereferencing and then boxing the result. That only works for statically-sized types, because it's a by-value operation, and a trait object is dynamically-sized.

2 Likes

All good points.

@H2CO3 might be precisely the fundamental understanding that I was missing. I was hoping that I could read then copy the value into my struct in a reasonably generic way. Are you saying that it's never possible to take ownership of a <dyn TraitObject>?

(I understand there is type erasure, but remains nonetheless "retrievable" presumably to enable a full representation of the underlying type "at some point").

Ok, I'm bumping into the fact that the object that hosts the trait is borrowed. So, the following gives me a "does not live long enough" error when I try to cast the resturn value to coefficients: Box<dyn ArrayView1<f64>>

        self.inner.findings = Some(LogitFindings {
            intercept: *lr.intercept().get((0, 0)),
            coefficients: lr.coefficients().get_row(0),
        });

Yes.

1 Like

A dyn is always behind some pointer, and the only way, currently, to move it from one container to another is if the container types provide an operation to do so. For example, a Box<T> can be converted into an Rc<T> whether or not T is a dyn type, because there is a From implementation for that specific conversion.

1 Like

That means the caller of a function that returns dyn TraitObject only ever takes ownership of the Box... And by way of the Box responsibility for dropping the memory "come with". Yes?

The parent lr is a borrow. So there is no way to do anything other than copy the dyn TraitObject... not ever possible without a mechanism provided by the lib in use. Yes?

I'm not sure what you mean by that or whether this is a useful question to ask/distinction to make at all.

There's no "responsibility for dropping the memory", not any more than there would be had it been possible to return the trait object directly. Box owns its referent and drops it when the box itself is dropped; this has nothing to do with whether or not the referent is a trait object or any other type.

There's no need to copy anything. get_row() already returns a Box<dyn Trait>. Just don't try dereferencing and copying it, as per the 1st bulletpoint of @quinedot's answer above.

1 Like

If you know the erased type you can downcast it.

1 Like

To extract the dyn ArrayView1<f64> from a borrowed value of the linear regression finding (lr), I had to do the following:

let c: Box<dyn ArrayView1<f64>> = lr.coefficients().get_row(0);
let c: &dyn ArrayView1<f64> = c.as_ref();
let c: Vec<f64> = Array1::from_row(c);

The first two lines needed to be separated so that I could create a reference that lived long enough. The last two lines can be combined. I only have it as such, to better understand the type conversions.

Finally, I patched the lib to add the Array1::from_row() trait method so that I could clone the value.

 fn from_row(slice: &dyn ArrayView1<T>) -> Self {
     let ncols = slice.shape();
     Self::from_iterator(slice.iterator(0).cloned(), ncols)
 }

Thank you to everyone for the input. I would not have had the clarity needed to patch the external lib.