在 Rust 中实现一个特征的特征?

问题描述 投票:0回答:1

我正在尝试构建一个数学库,并且我有一个特征

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 traits
1个回答
0
投票

在 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)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.