Keyword for protocol methods

please vote even if you left your comment below:

  • pro "implements"
  • against "implements"
  • neutral

0 voters

this is super annoying, and i've been beaten by this missing feature many times:

protocol Command {
    var commandTimeout: TimeInterval { get }
}

extension Command {
    var commandTimeout: TimeInterval { 1 }
}

struct SomeCommand: Command {
    var commandtTimeout: TimeInterval { // a typo here
        20
    }
}

why don't we have some keyword here, similar to "override". e.g.:

struct SomeCommand: Command {
    implements var commandtTimeout: TimeInterval { #compile time error
        20
    }
}
4 Likes

How about a unit test?

2 Likes

This is also useful for cleaning up code and not leaving behind unused protocol implementations. I definitely wish Swift had taken this approach from day 1 since as you mention it's a feature for overrides, but probably too late to change now.

6 Likes

We already have this internal/undocumented attribute:

@_implements(<Protocol>, <Requirement>)

I think it is a good idea to extend it a little bit for ease of use and make it an official part of the language. Both parameters are required today, but we could make them optional and upgrade it to a declaration modifier instead of an attribute. In that case, it becomes exactly what you are asking: An optional keyword to make sure a declaration matches a protocol, but it will do much more than that to carry its weight:

// From here: (The current implementation)
struct S: CustomStringConvertible {
    @_implements(CustomStringConvertible, description) var __hidden = "Something"
}
// We can get to here:
// 1) Drop `@_` and make it declaration modifier:
struct S: CustomStringConvertible {
    implements(CustomStringConvertible, description) var __hidden = "Something"
}
// 2) If used with one parameter and protocol has the parameter as 
//    a requirement, then infer that protocol as the first parameter:
struct S: CustomStringConvertible {
    implements(description) var __hidden = "Something"
    // Interpreted as:
    implements(CustomStringConvertible, description) var __hidden = "Something"
}
// 3) If used with no parameters, only accept the declaration if the 
//    declaration satisfies a requirement of an explicitly listed protocol:
struct S: CustomStringConvertible {
    implements var description = "Something" // Accepts this
    // Interpreted as:
    implements(CustomStringConvertible, description) var description = "Something"
    // This misspelled variant is rejected as not matching any of
    // the requirements of `CustomStringConvertible`:
    implements var descrription = "Something" 
}
3 Likes

I suggest we can also add the same feature to protocol extensions:

extension Command {
    implements var commandTimeout: TimeInterval { 1 }
}

If the name of original protocol method changes, it's quite difficult for authors of protocol extensions to notice that the default implementation no longer works.

12 Likes

Without commenting for or against this pitch, I think before adding this to the language we should discuss a few related cases (not saying these are important, just that they should participate in the design discussion):

  • Having a method with a name that matches a protocol requirement but does not implement it (may be useful for conforming to multiple protocols with similarly named requirements).
  • Allowing the protocol to force conformances to explicitly write implements (might be useful for making API evolution easier).

the above internal @_implements(<Protocol>, <Requirement>) will be no fit for this case but what's wrong with "implements" keyword in this case?

protocol Foo1 { var foo: Int { get } }
protocol Foo2 { var foo: Int { get } }

struct SomeFoo: Foo1, Foo2 {
    implements var foo: Int { 1 }
}

yep. once "implements" is in the language the next logical step would be to start issuing warnings + fixits so people actually use it, and possibly even make it a requirement in some future compiler versions.

I was suggesting we consider the case where you want to implement Foo1.foo but use the default implementation for Foo2.foo (for example). Again, these aren't counterpoints just other design considerations in the space.

i added the vote menu in the topic, please vote.

As an interesting cross-language comparison, Rust splits its extensions (impl blocks) into 2 kinds: inherent impl Type and for a protocol (trait) impl Trait for Type.

A protocol implementation block can only implement functions for that protocol, and must independently implement all functions for that protocol, such functions can only be used when one imports (uses) that protocol.

In contrast inherent implementation blocks can provide arbitrary functions which can be used with only a value, or by import of the type for static (non-self) functions, but such functions cannot satisfy protocol requirements.

5 Likes

I’m really hoping that something like this would be eventually introduced to the language. Unfortunately, because of todays absence of rules in the compiler around this, we have a lot of dead code, and, moreover, we had to forbid default implementations completely because of constantly reappearing bugs (someone changes name of the method in protocol, but doesn’t change implementation name cause default exists and compiler doesn’t complain).

I know it can be a burden for some, but I believe there is a way to make language safer without adding additional cognitive load :slight_smile:

5 Likes