背景

我有一种情况,我想抽象两种不同的操作模式SparseDense。我选择的是一个编译时决定。

与这些模式正交,我有许多Kernels。两种模式之间内核的实现细节和签名不同,但是每种模式具有相同的内核。内核将在运行时根据模型文件确定。

现在,我想创建一个同时处理模式和内核的BlackBox

简化代码

我删除了其他内核和稀疏模式。

pub struct XKernel;

pub trait KernelDense {
    fn compute_dense(&self, vectors: &[f32]);
}

impl KernelDense for XKernel {
    fn compute_dense(&self, vectors: &[f32]) {}
}

pub trait KernelCompute<V> {
    fn just_compute_it(&self, vectors: &[V]);
}

impl KernelCompute<f32> for (dyn KernelDense + 'static) {
    fn just_compute_it(&self, v: &[f32]) {
        self.compute_dense(v);
    }
}

pub trait Generalization {
    type V: 'static;

    type OperatorType: KernelCompute<Self::V>;

    fn set_kernel(&self, x: Box<Self::OperatorType>);

    fn compute(&self, v: &[Self::V]);
}

pub struct DenseVariant {
    x: Box<KernelDense>,
}

impl Generalization for DenseVariant {
    type V = f32;
    type OperatorType = KernelDense;

    fn set_kernel(&self, x: Box<KernelDense>) {}

    fn compute(&self, v: &[Self::V]) {
        self.x.compute_dense(v);
    }
}

struct BlackBox<'a, T>
where
    T: Generalization,
{
    computer: T,
    elements: &'a [T::V],
}

impl<'a, T> BlackBox<'a, T>
where
    T: Generalization,
{
    fn runtime_pick_operator_and_compute(&mut self) {
        self.computer.set_kernel(Box::new(XKernel));
        let s = self.elements.as_ref();
        self.computer.compute(s);
    }
}

fn main() {
    // What I eventually want to do:
    // let black_box = BlackBox::<DenseVariant>::new();
    // black_box.runtime_pick_operator_and_compute();
}

Playground

上面的代码产生错误

error[E0277]: the size for values of type `(dyn KernelDense + 'static)` cannot be known at compilation time
  --> src/main.rs:35:6
   |
35 | impl Generalization for DenseVariant {
   |      ^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn KernelDense + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-sized>

我尝试添加大量的: Sized(例如,给BlackBox一个where T: Generalization + Sized,最终最终会产生不同的错误。

问题
  • 如何实现std::marker::Sized(dyn KernelDense + 'static)/使该程序进行编译以解决上述意图?
  • 为什么编译器甚至关心GeneralizationSized(即使将T: Generalization + Sized添加到BlackBox)?不是BlackBox(唯一使用oj​​it_code的代码)被化为Generalization(例如Generalization),然后明显具有DenseVariant的大小)?
  • 最佳答案

    该错误信息令人困惑。 ^^^(误导)指向Generalization,但实际错误是dyn KernelDense,即OperatorType。至少从Rust 1.50开始,您会得到更好的错误消息:

    error[E0277]: the size for values of type `(dyn KernelDense + 'static)` cannot be known at compilation time
      --> src/main.rs:42:9
       |
    24 |         type OperatorType: KernelCompute<Self::V>;
       |         ------------------------------------------ required by this bound in `Generalization::OperatorType`
    ...
    42 |         type OperatorType = KernelDense;
       |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
       |
       = help: the trait `Sized` is not implemented for `(dyn KernelDense + 'static)`
    
    因此,OperatorType是真正需要的Sized。除非您通过添加Sized来另外指定,否则关联类型like generic type parameters绑定(bind)了隐式?Sized:
    pub trait Generalization {
        ...
        type OperatorType: ?Sized + KernelCompute<Self::V>;
        ...
    }
    
    但是您会立即遇到另一个问题(playground):
    error[E0308]: mismatched types
      --> src/main.rs:59:47
       |
    59 |         self.computer.set_kernel(Box::new(XKernel));
       |                                           ^^^^^^^ expected associated type, found struct `XKernel`
       |
       = note: expected type `<T as Generalization>::OperatorType`
                  found type `XKernel`
    
    如果您在两行之间读了一点,那么基本上是编译器说的:“我该如何处理Box<XKernel>?我需要一个Box<T::OperatorType>,但我什至不知道什么是T!”
    那应该是有道理的。因为没有规则禁止在OperatorTypestr的地方添加一种新的变体,所以说impl:
    struct StringyVariant;
    
    impl Generalization for StringyVariant {
        type V = f32;
        type OperatorType = str;
    
        fn set_kernel(&self, x: Box<str>) {}
    
        fn compute(&self, v: &[f32]) {}
    }
    
    impl KernelCompute<f32> for str {
        fn just_compute_it(&self, vectors: &[f32]) {}
    }
    
    没有规则禁止这些Box<XKernel>,但是不可能将Box<str>强制转换为impl,因此BlackBox上的全部Box<XKernel>必须是错误的。它缺少一个要求:可以将Box<T::OperatorType>强制转换为impl的要求。
    在稳定的Rust中(从1.50开始),没有任何方法可以将此特征写为特征绑定(bind),因此您必须编写两个BlackBox<DenseVariant>(即,一个用于BlackBox<SparseVariant>和一个用于From),或者可能找到其他解决方法(例如,使用CoerceUnsized而不是强制)。
    但是,在每晚的Rust中,您可以通过绑定(bind)as _和额外的ojit_code来解决原始问题,以向编译器暗示它应强制执行有意义的操作:
    // at top of file
    #![feature(coerce_unsized)]
    
    use std::ops::CoerceUnsized;
    
    impl<'a, T> BlackBox<'a, T>
    where
        T: Generalization,
        Box<XKernel>: CoerceUnsized<Box<T::OperatorType>>,
    {
        fn runtime_pick_operator_and_compute(&mut self) {
            self.computer.set_kernel(Box::new(XKernel) as _);
            let s = self.elements.as_ref();
            self.computer.compute(s);
        }
    }
    
    Here it is in the playground.

    10-06 09:11