Allow uncallable method impls to be omitted #1699

Open
wants to merge 2 commits into from

5 participants

@canndrew

For your consideration.

Rendered

@Stebalien
Stebalien commented Aug 3, 2016 edited

Note: This has implications beyond !. It would also allow one to write:

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

impl<T> Foo for [T] {}

Currently, this can't even be implemented. This is a bit ridiculous considering the fact that the following impl is fine (because T may be sized):

impl<T: ?Sized> Foo for T {
    fn foo(self) where Self: Sized {}
}
@canndrew

@Stebalien Interesting, I never thought of cases like that. I was really only thinking of cases where a method takes an uninhabited type.

@withoutboats

@Stebalien's example seems orthogonal from the RFC to me. In @Stebalien's case, it would be a type error to call that method because a constraint was not met, whereas in the RFC the method is well typed, but will never execute.

@Stebalien's example seems like something that should be allowed, but like the previous RFC, I am not convinced that implementing traits for ! is a frequent enough activity that it deserves any special sugar or consideration.

@nrc nrc added the T-lang label Aug 4, 2016
@nrc
nrc commented Aug 4, 2016

@withoutboats it doesn't seem orthogonal to this RFC to me. This RFC states that if a method can't be called, then you don't have to implement it. The example in the RFC and @Stebalien's example are orthogonal reasons for a method being un-callable, but the end result is the same - can't be called => don't need to impl.

@nrc
nrc commented Aug 4, 2016

Or to be clear, the examples are different by the rules of the RFC, but not by the spirit.

@withoutboats
withoutboats commented Aug 4, 2016 edited

@nrc In its motivation and detailed design, this RFC discusses methods which are uncallable because they take an uninhabited argument. I don't think we can consider an RFC which proposes any property for all "methods which can't be called," since that is not a clearly defined set of cases.

For example, here is another case where a method is uncallable. Does this RFC include this in its scope?

A dependency defines and exports this struct and trait:

struct Foo {
    _secret: ()
}

trait Bar {
    fn bar(&self, foo: Foo) -> Foo;
}

The library defines no public constructor for Foo, and contains no functions which take a T: Bar or a Bar trait object as arguments.

When I implement Bar for my own types, the method bar will never be called, because I cannot construct a Foo or pass my type to that library. Moreover, I cannot provide a definition which doesn't diverge, because I cannot construct a Foo.

This is an "uncallable method," should I be allowed not to write it? I don't think so (that opens a huge can of backcompat problems for libraries!), but more importantly, I don't think this is in scope for this RFC.

@Stebalien

@withoutboats that's only uncallable in safe/sane code. However, I didn't read the detailed design so I simply assumed that the examples given in the motivation/summary were just that, examples.

Given that being able to simply implement my examples is orthogonal to being able to implement them without explicitly defining the uncallable methods, I've filed a separate issue rust-lang/rust#35369.

Also, there's a big drawback that hasn't been mentioned: a type can go from uninhabited to inhabited without changing any public type definition:

struct TBD(!);

trait MyTrait {
    // For now, this always panics...
    fn foo(&self) -> TBD;
}

I don't really see how this could be useful outside of prototyping but it's still an issue.

@withoutboats

@withoutboats that's only uncallable in safe/sane code.

True, you can construct a Foo through mem::uninitialized.

Also, there's a big drawback that hasn't been mentioned: a type can go from uninhabited to inhabited without changing any public type definition

Also true! I don't think we should make this a breaking change (and I don't think it should be visible to users who don't read the source that TBD is uninhabited).

@nikomatsakis

Sorry, more context would have been good. =) rust-lang/rust#20021 was an issue opened with respect to where-clauses -- sometimes, in a specific impl, you can tell that a given fn will never be called because its where-clauses cannot be satisfied. This also comes up for trait objects.

Example:

trait Foo { fn bar() where Self: Copy; }
impl Foo for Box<i32> { fn bar() { panic!() } }

Nobody could call x.bar() in the case where x: Box<i32>.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment