I remember once seeing a trick to emulate a negative trait bound. Using a Boolean trait and some other machinery. But I forgot how to do it.
I want to be able to
struct Struct<T>(T);
impl Struct<()> {
fn foo1(&self) -> u8 { 1 }
}
impl<T> Struct<T> {
fn foo2(&self) -> String { String::new() }
}
fn bar<T>(s1: Struct<()>, s2: Struct<T>) {
s1.foo1(); // s1 should only call foo1() but not foo2()
s2.foo2(); // s2 should only call foo2() but not foo1()
}
With the above, Struct<T> impl catches all, and overrides Struct<()> impl. If specificity is provided s1: Struct<()> I would like to restrict available methods to the associated impl. I figure if I can impl EmptyTuple for the type (), maybe I can emulate restricting impl<T: !EmptyTuple> Struct<T> somehow.
I don't want to let Struct<()> have foo2 and I don't want every other type for Struct<T> have the foo1 method.
Struct<()>::foo1(todo!()) compiles Struct<()>::foo2(todo!()) should not compile
Struct<T>::foo1(todo!()) should not compile Struct<T>::foo2(todo!()) compiles
I once saw a trick vaguely akin to impl<T: IsType> Struct<T> {} and impl<T: !IsType> Struct<T> {} to accomplish this. Without the actual unstable negative impl sytax.
@vague While technically that works for the example I gave. I have underestimated the requirements by over-simplifying the example. Given this code, that "erases" the type in a struct member, it no longer works the same.
struct Struct<T>(T);
pub trait NotForUnit {
fn foo(&self) {}
}
impl<T> NotForUnit for Struct<T> {
fn foo(&self) {
println!("not for ()")
}
}
impl Struct<()> {
pub fn foo(&self) {
println!("() only")
}
}
struct Store<T>(Struct<T>);
impl<T> Store<T> {
fn bar(&self) {
self.0.foo();
}
}
fn main {
let store = Store(Struct(()));
store.bar(); // prints not for ()
}
Though I might still make use of it in cases where I can specify an exact type in a struct member.
@quinedot I believe so because I remember associated types being used to limit to either or cases. I'll have a deeper look at the playground code and verify. Doesn't compile on nightly but is probably because typos due to being typed on mobile?
I pulled it out of my bookmarks (hence the old edition); the compile error is demonstrating the guard. See the comments on what to do to make it compile.
The playground I linked above, when applied directly to your OP, may look like so. It has the downside of needing to provide an implementation of Permission for any T you want to work though, so it's not really much better than a normal trait bound (it just guarantees that won't happen for things you explicitly Deny).
Not sure if there's a way around that without specialization.
You're right, this requires specialization to allow an additional fallback catch-all implementation
impl<T> Permission for T {
type Execute = Allow;
}
conflicting implementations of trait Permission for type ()
When I looked at your earlier playground example, I came up with the same solution you just posted. But got stuck on an important problem:
fn bar<T: Permission<Execute = Allow>>(s1: Struct<()>, s2: Struct<T>, s3: Struct<u8>) {
s1.foo1(); // compiles as intended
s2.foo2(); // compiles as intended
s3.foo2(); // the trait bound `u8: Permission` is not satisfied
}
I won't be able to deal with types like Struct<u8>. I think the logic I need, that is the explicit exclusion of one or more types from the universe of all types, is something the compiler can't do right now.
With the current solution I have to explicitly "whitelist" types to be allowed into the generic impl for foo2. Removing the constraint where T: Permission<Execute = Allow> will open the door back for s1.foo2(); to be callable. Whitelist vs blacklist problem.