AsRef<T> conflicting with Into<T>?

Hello everyone. I'm trying to separately implement operators with owned and borrowed operands, like this:

impl<T> AddAssign<T> for Number where T: Into<Number> {...}
impl<T> AddAssign<T> for Number where T: AsRef<Number> {...}

However, the compiler says these definitions are conflicting. Is anyone able to explain what is going on here?
The full example on playground.

What happens when a type implements both AsRef<Number> and Into<Number>? Then Rust could pick either impl, but this breaks the rule that there must be at most 1 trait impl for each type! (ignoring the nightly only specialization feature)

3 Likes

Oh yeah, that's true. What I actually meant to do was something like this:

impl<T> AddAssign<T> for Number where T: Into<Number> {...}
impl<T> AddAssign<&T> for Number where T: AsRef<Number> {...}

But that does not work either. At least this time the compiler helpfully notes "downstream crates may implement trait std::convert::From<&_> for type Number". Is there any way to get around that? Something that would encode the intention of "if the argument is an owned value that can be trivially converted to Number, use this, otherwise use that"?

The context here is that I have multiple newtype wrappers encoding some constraints on the value (non-zeroness, finiteness, positiveness...), and use the type system to enforce correct constraints on operations (e.g. you can't divide by number that can be zero, you can't multiply infinity by negative number, etc.). However, implementing all the valid combinations without traits like above would be terribly tedious and error-prone.

So, I tried using a private trait instead of Into/AsRef for this, but I still get the same "downstream crates can implement it" error -_-
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fb82009e489a45eb7869e15be7f67f42

You can do

impl AddAssign<Number> for Number {...}
impl<T> AddAssign<&T> for Number where T: AsRef<Number> {...}

I don't think the Into is really worth it here.

1 Like

I ended up using a single generic type parametrized by "constraint" type, instead of multiple types. That works pretty well, but it's still a lot of boilerplate so I'll probably try to generate them with a procedural macro.

Another way to allieviate boilerplate without pulling proc macros is to use build scripts

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.