Type exclusion in trait implementation?


#1

Currently, "literal".to_string() uses Show to transform the value, so it had been consuming larger memory than "literal".into_string(). The difference is lessened, however, by the recent improvements of the formatter. Previously, a minimum of 128 bytes were needed. Now only 2^n bytes.

But I still think using a formatter like Show to convert a &'static str to String is a bit indirect, leading me to having a preference on into_string().

However, I found an interesting idea from this reddit post. I don’t know how this should be called, but the suggested method gives a way to negate some patterns when matching a type in impl, like this:

impl<T: Show + Str> ToString for T { * Convert &'static str directly */ }
impl<T: Show + !Str> ToString for T { /* Use format!(), as usual */ }

I heard C++ got a headache when dealing with multiple inheritance, but using the proposed way, it is guaranteed that only one implementation is matched to a type, so there is no need for a conflict resolution mechanism.

Would that be possible? Are there any caveats in this idea?


#2

I persinally would rather like to see support for specializations so one would write:

impl<T: Show + Str> ToString for T { * Convert &'static str directly */ }
impl<T: Show> ToString for T { /* Use format!(), as usual */ }

Because Show + Str is trivially more specific bound than Show it will be used where possible.

PS: Yes, I know that it will lead to some nasty ambiguities but so will method overloading that is currently possible with traits.


#3

What ambiguities are present? I believe that this would be considered a bug.


#4

It hardly can be considered a bug. Let’s look at this code:

struct X;
impl TraitA for X {
    fn foo() {}
}
impl TraitB for X {
    fn bar() {}
}

fn work() {
    let x = X{};
    x.foo()
}

Do we have guarantee that this code will always work? No. What you don’t see is that TraitB has default method foo() and call to x.foo() is ambiguous. This way originally correct code can be made invalid by adding default methods to existing traits.

My comment was that you could have generic specialization of a trait and when you add at some point another generic specialization it can lead to ambiguities for some types.


#5

@pepp: The problem is that whilst it’s obvious in this example that it’s more specific, that’s still not enough. There’s nothing to prevent someone from coming along and adding another implementation in another crate that’s even more specific. I mean, impl ToString for Sprocket is more specific. But if that’s allowed, you can’t tell what a given impl does and does not apply to with purely local knowledge, which is a very useful property of the current system.

I think negative trait bounds would be fantastic; you’d be able to apply exceptions to a general pattern whilst still maintaining the ability to just look at an impl's declaration and know what it does and does not apply to.


#6

There’s no foo in TraitB. I believe that if there was, x.foo() would be ambiguous, and you’d have to disambiguate with UFCS (exact syntax escapes me).

Also, if you’re adding default methods, you are changing the definition of the trait. Of course that can break stuff. This isn’t surprising. What you’re proposing is more like adding a new impl of a trait breaking existing uses; that isn’t OK.


#7

I agree that negative bounds are useful. I just feel that they are not enough.

The question is what would be consistency rules for specializations. Currently you can implement trait for a type only next to the trait declaration or next to the type. There is no guarantee that you do break compilation. The guarantee is that the implementations are used consistently in the entire crate.

I suspect that if you allow generic implementation without bounds only next to a trait declaration and allow generic implementations/specialization only next to the type or trait declarations then you should be as safe as with non-generic impls.


#8

Possibly related discussion here: https://github.com/rust-lang/rust/issues/7059