Trait bounds: exclude trait


#1

Hello everyone !
On my new playings with Rust I was fiddling with traits and I got struck by a question that I didn’t find documented/asked anywhere: Is it possible to use trait bounds to specify that a <T> should not implement a trait?

I was thinking this as a way of making a couple of functions (that do serializing) to refuse to accept certain internal types that are marked with this trait.

Any ideas?

Thanks for reading :smile:


#2

Not possible. This is called “negative trait bounds,” and is unstable because the current implementation is buggy.


#3

There is an RFC in progress for Mutually exclusive traits along these lines.


#4

Depending on exactly what your goal is, you might want to look at OIBITS (like the Send and Sync traits). They are not usable on the stable compiler yet and there are a few weird edge cases that haven’t been worked out, but for simple things they work fine.

They are a good fit if almost everything is (let’s call it) Serializable, but anything that contains a non-Serializable type should also be considered non-Serializable unless specifically marked otherwise.


#5

I think you might be mixing terminology; you’re talking about being able to write a negative impl, i.e. unimplement a defaulted trait (aka OIBIT) for a type, like impl !Send for X {}, which is quite possibly something that would solve @luxvio problem (create a defaulted trait, and then unimplement for the internal types; I suspect that the OIBIT RFC @wthrowe linked and http://huonw.github.io/blog/2015/05/defaulting-to-thread-safety/#witchcraft may be the best documentation for this atm).

Negative traits bounds are a different thing, being able to write (hypothetical syntax) fn foo<T: !X>(...) meaning that one can call foo with any type as long as it doesn’t implement X. This has been RFC’d, with quite a bit of discussion. That specific feature has some big downsides, such as making it (strictly speaking) a backward incompatible change to add an implementation for a trait.


Ways to avoid implicit Copy? or warn/error when it happens?
#6

Amazing, thanks for the answers @notriddle, @cuviper, @wthrowe and @huon ! :sunny:

What I was thinking about was pretty much making an empty trait {} on the library I’m working on; and make impl -of that empty trait- on the internal structs that are serializable/deserializable (for debugging and caching reasons), but on the binary side (a REST API) make functions (like make json response kinds) refuse to accept structs marked with that trait (I already use where to ask for certain kinds of T) for security/privacy reasons. For now, ATM I’m doin ok with a big warning stating not to serialize (for json output in REST API) internals structs no matter how tempting it is, rather adapt its content to another structs.

I’ll sit and give it more thought reading what you gave me and see if I can come up with an idea. Ty :heart:


#7

There is a way to do this on nightly, with the optin_default_traits feature.

Say you want to specify T does not implement A, then you can do this:

#![feature(optin_builtin_traits)]

trait A {}

trait NotA {}
impl NotA for .. {}
// this is incorrect (see wthrowe's reply):
// impl !NotA for A {}
impl <T: A> !NotA for T {}

Now, the trait NotA is implemented by default for everything except A, and the compiler knows that nothing can implement both A and NotA. So, you can use it as you would expect !A to work.

I have used this in dimensioned to overload multiplication in a flexible way (i.e. you can multiply either by scalars or by other dimensioned things).

Edit: Corrected impl of !NotA


#8

That code does not do what you think it does.

What you’ve actually defined is a trait that is implemented for everything that does not contain the trait object A, or a reference to one, etc. (by default, other things can implement it explicitly). Trait objects are usually not something you want to deal with unless you’re doing polymorphism stuff.


#9

You’re right; I’m tired. The line should be

impl <T: A> !NotA for T {}

Edit: Your example with fixed impl.


#10

I believe that’s as close as you can get with current Rust, but it’s still not quite right. You now can’t have types that are both A and NotA, but you’re likely to end up with types that are neither A nor NotA: example.


#11

Looking into it some more, it’s actually worse than that; it doesn’t work even for struct X; because the compiler doesn’t know that X doesn’t implement A for this line

impl <T: A> !NotA for T {}

so we’re stuck in some circular reasoning. For this to be useful, you have to impl NotA explicitly for everything that you want to declare isn’t A, which is pretty terrible.

Where I’ve used this, it’s been for a generic struct rather than a trait, like so: http://is.gd/RVc65r. This has the advantage that it works, but the distinct disadvantage that it’s not the answer to @luxvio’s question.