我正在尝试构建一个数学库,并且我有一个特征
Matrix<T>
,特定的实现(struct
)将实现。但我想在 Matrix
特征中提供尽可能多的功能。例如,通过定义 Index<(usize,usize), Output=T>
我可以访问矩阵的所有字段。这样可以轻松实现 Add
、Sub
或 Display
等功能。
但是当我尝试这样的事情时:
trait Matrix<T> : Display {
fn fmt(...) {...}
}
Rust 不将
fmt
识别为 Display
的实现。所以当我这样做时
struct MatrixImpl{}
impl Matrix for MatrixImpl{}
它希望我为
fmt
定义 MatrixImpl
,尽管 Matrix
已经提供了这一点。这让我有点困惑。而且 impl Display for Matrix
不起作用,因为现在 Rust 想求助于特征对象,即 impl Display for dyn Matrix
,即使这不起作用,因为 Matrix
由于其中的所有泛型而不会成为特征对象。即使是这样,它也不是零成本抽象。当然,那么impl<T,M:Matrix<T>> Display for M
也不起作用哈哈。真是一团糟。
我知道在这个问题的某些变体中,这可能会导致问题,但我不明白为什么 Rust 不简单地在
fmt
上使用 Matrix
函数,而是使用任何更具体的方法来覆盖它。当然,可能会发生冲突。但解决这些问题非常简单,因为在特征上实现的任何方法都只是defaults
,这意味着如果有任何更具体的内容发生冲突,您只需选择更具体的实现即可。
我想要的是让
new
、row_count
、indexing
等几个非常基本的操作由 Matrix
的每个实现来实现,但是我希望所有数十个默认实现和行为自动可供所有实施者使用。如果特定的实现(例如 TensorFlow)具有更高效的变体,他们可以覆盖它们。但是,当您实现自己的矩阵时,至少您不会面临必须为所有矩阵共享的所有共同特征提供 100 个实现的情况。但相反,您可以在覆盖和专门化默认行为时逐渐改进它。
在 Rust 中你不能做的一件事是,如果实现了
Display::fmt
,则使用 Display
,否则使用其他东西。在某些时候,您需要明确您想要使用哪一个。
但是你可以让你的特质的实施者和用户使用其中一种变得非常简单。我所做的就是调整
Path::display
的想法来适应你的特质。 (游乐场)
use std::fmt::{self, Display};
use std::marker::PhantomData;
pub trait Matrix<T> {
fn dimensions(&self) -> [usize; 2];
fn items<'a>(&'a self) -> impl Iterator<Item = &'a T> + 'a
where
T: 'a;
// Default impl that uses the other functions to print
fn fmt_matrix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
where
T: Display,
{
let [x, y] = self.dimensions();
let mut iter = self.items();
write!(f, "[")?;
for _row in 0..y {
write!(f, "[ ")?;
for item in iter.by_ref().take(x) {
write!(f, "{item} ")?;
}
writeln!(f, "]")?;
}
write!(f, "]")
}
// Function for conveniently retrieving an impl Display
fn display(&self) -> MatrixDisplay<'_, Self, T> {
MatrixDisplay(self, PhantomData)
}
}
// Wrapper struct whose only purpose is to forward Display::fmt to Matrix::fmt_matrix
pub struct MatrixDisplay<'a, M: ?Sized, T>(&'a M, PhantomData<T>);
impl<'a, M, T> Display for MatrixDisplay<'a, M, T>
where
M: Matrix<T> + ?Sized,
T: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_matrix(f)
}
}
// Example implementer that also implements Display
pub struct EmptyMatrix<T>(PhantomData<T>);
impl<T> Display for EmptyMatrix<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[]")
}
}
impl<T> Matrix<T> for EmptyMatrix<T> {
fn dimensions(&self) -> [usize; 2] {
[0, 0]
}
fn items<'a>(&'a self) -> impl Iterator<Item = &'a T> + 'a
where
T: 'a,
{
std::iter::empty()
}
// Overriding when the type implements Display is very simple
fn fmt_matrix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
where
T: Display,
{
self.fmt(f)
}
}