Hi everyone,
I've been playing around a bit with rust, and reading the code of nd-array. I'm trying to implement some forward auto-differentiation library and I am now stuck on a design question: What would be a good way to use std::ops
traits with objects that allocates.
To illustrate my case, let's say we are implementing a very simple library to manipulate vectors of f64
and some element-wise operation, say addition. We also want our library to handle "views" that is not only vectors that can own their data, but vectors that can be backed by a &[f64]
, maybe because we want to extract parts of a vector, or maybe because we can receive some read only vectors from a C FFI (my case). My current implementation is something like:
use std::ops;
use std::borrow::*;
struct AlgVector<T : Borrow<[f64]>>(T);
impl<T> AlgVector<T>
where
T: Borrow<[f64]>
{
pub fn to_owning(&self) -> AlgVector<Vec<f64>> {
AlgVector(self.0.borrow().to_owned())
}
pub fn view<'a>(&'a self) -> AlgVector<&'a [f64]> {
AlgVector(self.0.borrow())
}
}
impl<L, R> ops::Add<AlgVector<R>> for AlgVector<L>
where
L: BorrowMut<[f64]>,
R: Borrow<[f64]>,
{
type Output = Self;
fn add(mut self, rhs: AlgVector<R>) -> Self {
self.0.borrow_mut().iter_mut().zip(rhs.0.borrow()).for_each(|(selfx,rhsx)| *selfx += rhsx);
self
}
}
which I like because it is quite generic, small, and doesn't hide allocations, but it forces the user to write things like x.to_owning() + y.view()
which aren't so readable.
ndarray
's approach is much easier to use but there are so many levels of nested types that it seems a bit much to implement and understand, and the error messages become quite complex.
So my question is: have you ever faced the same issue ? How would you have an implementation for ops that doesn't allocate every time while still being nice to use ? Would it even be possible to have something that is optimal in its allocations and doesn't require the user to manage them ?
Thanks for reading