背景
我有一种情况,我想抽象两种不同的操作模式Sparse
和Dense
。我选择的是一个编译时决定。
与这些模式正交,我有许多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)
/使该程序进行编译以解决上述意图? Generalization
是Sized
(即使将T: Generalization + Sized
添加到BlackBox
)?不是BlackBox
(唯一使用ojit_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
!”那应该是有道理的。因为没有规则禁止在
OperatorType
是str
的地方添加一种新的变体,所以说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.