Skip to content

Allow omission of unsatisfiable members in trait impls #2829

Open
@alercah

Description

@alercah

Currently, where bounds on trait methods are largely only useful for one purpose, which is to make traits object-safe. In particular, if we write trait Foo { fn foo(self) where Self: Sized; } rather than trait Foo: Sized { fn foo(self); }, then dyn Foo is a valid type, albeit one where we cannot call foo due to the Sized constraint. If the Sized constraint is directly on Foo, however, then since dyn Foo: !Sized, we get a constraint that dyn Foo: Sized, which is impossible.

Interestingly, this lets us make a value of type (dyn Foo) implementing a trait (Foo) but on which a method (foo) cannot be called due to its unsatisfied constraint (dyn Foo: Sized).

As far as I can tell, this is the only such example. If I try to implement it for another unsized type, I either get an error about foo not being implemented or get an error about the constraint being unsatisfied. But it's clearly not an inherent requirement that foo be implemented, because dyn Foo has no foo implementation, just a surface-level one.

Note that you can define a default implementation, and then you get a similar result, with the resulting method not being callable on unsized types like str. But then you have to provide a default implementation for all types, which is in many practical examples just going to be an "impossible" panic, but then you cannot force every implementor to override it, meaning that this impossible panic could leak into real code.

Thus, an idea of allowing an impossible constraint to be ignored. Here's an example borrowing a C++ syntax:

trait Foo {
    fn foo(self) where Self: Sized;
}

impl Foo for str {
    fn foo(self) where Self: Sized = delete;
}

In this example, the = delete tells the compiler that we deliberately want to not implement the function, and the compiler in this cause ought to see that str: !Sized and permit the impl. This should only be permitted where the compiler can confidently negatively reason, in the same way as when calculating impl overlap. Orphan rules make this viable in many cases.

I'm not sure that this feature is incredibly useful---the primary use case I can think of it is to allow specialization of things with additional trait constraints, but in that case you probably have a default implementation anyway. The other thing that comes to mind is trying to ease version migrations, to let you add non-default methods that are conditioned on the version of implementing types, but you'd still need to propagate the constraint which would largely undermine its utility.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions