Reputation: 73
I am trying to build a math library and I have a trait Matrix<T>
that specific implementations (struct
) will implement. But I want to provide as much functionality within the Matrix
trait. For instance, by defining Index<(usize,usize), Output=T>
I am able to access all fields of the matrix. This makes it easy to implement functionality such as Add
, Sub
or Display
.
But when I try something like this:
trait Matrix<T> : Display {
fn fmt(...) {...}
}
Rust does not recognize fmt
as an implementation of Display
. So when I do
struct MatrixImpl{}
impl Matrix for MatrixImpl{}
It wants me to define fmt
for MatrixImpl
even though Matrix
already provides that. This is a bit baffling to me. Also impl Display for Matrix
doesn't work, because now Rust wants to resort to trait objects, i.e. impl Display for dyn Matrix
even though that won't work because Matrix
is not gonna be a trait object due to all the generics in it. And even if it was, it's not zero cost abstraction. Of course then impl<T,M:Matrix<T>> Display for M
also won't work lol. It's a mess.
I understand in some variants of this problem, this could cause issues, but I do not understand why Rust doesn't simply use the fmt
function on Matrix
and uses whatever more specific method is available to override that. Like sure, there could be conflicts. But these are very trivial to resolve because any method implemented on a trait are just defaults
, meaning if ever anything more specific is in conflict, you just pick the more specific implementation.
What I want is to have a couple very fundamental operations like new
, row_count
, indexing
, etc. be implemented by every implementation of Matrix
, but then I want all the dozens of default implementations and behaviour to automatically be available to all implementors. If a specific implementation, like TensorFlow has more efficient variants, they can override them. But when you implement your own matrix, at least you won't be faced with having to provide 100 implementations for all the common traits shared by all matrices. But instead, you can gradually improve it as you override and specialize default behaviour.
Upvotes: 0
Views: 181
Reputation: 12812
The one thing you can't do in Rust is use Display::fmt
if Display
is implemented, otherwise use something else. You are required to disambiguate, at some point, which one you want to use.
But you can make it extremely simple for implementers and users of your trait to use one or the other. What I've done is adapt the idea of Path::display
to work on your trait. (playground)
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)
}
}
Upvotes: 0