1

I want to avoid unimplementing a given auto trait for composite types containing a type for which the given auto trait is unimplemented.

I have this auto trait for anything that isn't ():

pub auto trait NotVoid {}
impl !NotVoid for () {}

I use this to mitigate conflicting implementations for the following trait; a Maybe trait which works similarly to Option but statically.

pub trait Maybe<T>
    where
        T: ?Sized {}
// This implementation conflicts with...
impl<T> Maybe<T> for T
where
    T: ?Sized {}
// ...this one:
impl<T> Maybe<T> for ()
where
    T: ?Sized {}

by doing this:

pub trait Maybe<T>
    where
        T: ?Sized {}
impl<T> Maybe<T> for T
where
    T: ?Sized {}
impl<T> Maybe<T> for ()
where
    T: NotVoid + ?Sized {}

which works fine. However, i've found this problem where if i have a composite type which is composed of (), it does not implement NotVoid, even though it's not an actual (). This makes sense in the case of auto traits such as Sync, Send and Copy, but this is not what i want to achieve with my NotVoid trait. For example:

struct A
{
    nothing: ()
}

A does not implement NotVoid because it contains a (), even though it should (according to what i want NotVoid to do). How can i achieve a negative implementation of NotVoid only for (), but not for A and other composite types like it?

2
  • You'll have to explicitly impl NotVoid for A {}, for which maybe you'd like to provide a derive macro. But yeah, auto traits are structural. Commented Apr 10, 2024 at 19:17
  • Sorry, that's just how auto trait was designed. A composite type only implements the auto trait if all its contained types implement it. Commented Apr 10, 2024 at 19:17

1 Answer 1

2

You don't want an auto trait. Auto traits' very purpose in life is to prevent implementation when one of the fields does not implement the auto trait.

What you want is negative implementation, which is equivalent (in its full form) to negative bounds. That is, you want either a trait that is implemented for everything but not (), or a way to restrict a trait implementation (a bound) so that it will not apply to some type (condition).

Negative impls do exist in Rust in some form, and in fact they're often bundled together with auto traits (you will see you need two unstable features for your auto trait: one for the trait itself (auto_traits), and another for its impl (negative_impls)).

However, negative impls as implemented in Rust affect coherence, but they do not allow overlap. That is, while the fact that you have a negative impl for () will mean you can have two impls, one for all other types and one for NotVoid and they won't overlap (as you observed), you are not allowed to make negative and positive impls overlap, just like you are not allowed to make two positive impls overlap, and the compiler will yell at you if you try.

In fact, you are requesting something that it is a combination of negative impls and specialization, two notoriously difficult to implement features and which soundness holes have been found in both of their implementations during the years. So don't expect it to happen anytime soon.

It might be still better than the state of auto traits, because the current plan is to never stabilize them, because of their effects on the ecosystem.

In conclusion, what you're doing may be fun, but please don't use it in any production code you write.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.