Newtypes and math ops for Copy vs. non-Copy inner types

I'm trying to unify the use of math ops on newtypes which may or may not be Copy (or trying to avoid having to implement the same function twice as a + b * c and &a + &b * &c).

Implementing ops like Add for Copy newtypes is easy and you can get away with just doing it for owned types. On the other hand, for non-Copy types you need to do it for &Self and both RHS or &RHS.

I think implementing Deref on the newtype simplifies this some through the tuple field access, but may make the newtype too easily coerced to its inner type. Which leaves AsRef/Borrow or a custom inner function as the best bet for unifying things:

trait NewtypeInner<'a> {
    type Inner;
    fn inner(&'a self) -> Self::Inner;
}

impl<'a, T: Copy> NewtypeInner<'a> for Newtype<T> {
    type Inner = T;
    fn inner(&'a self) -> Self::Inner { self.0 }
}

And perhaps from there it's just a matter of carefully implementing the right Newtypes with NewtypeInner::Inner types which have the right implementations? But I seem to be running into lifetime issues (playground link)

Am I on the right track? Does anyone have any insights or wisdom to share on this?

Or maybe I'm coming at this backwards and should just do everything as &a + &b * &c. But I worry that would prevent any optimization around Copy types.

Not really. Locals and temporaries (places) are already represented in LLVM as if they were stack-allocated variables with an address and are manipulated (read and written) by their address.

1 Like

Have you looked at rug? There the distinction between these operations is important. It gives you some control over whether or where an allocation occurs.

That's kind of the answer I was hoping for. They'll ultimately just be used in &self methods on the final owning struct.

Copy vs. Reference Comparison - Looks like this confirms it. I guess something I've read about Copy types gave me the wrong impression.

It seems the only real difference is that Copy types let the outer scope keep access, as in the classic:

let a = 1.0;
do_something(a);
println!("{}", a);

Also this thread.