Default implementation in protocol bodies

Hello evolution,

This topic has been discussed before, but I wanted to bring it back up. Hopefully we've all been in a situation where we need to add default implementation for protocols, but the only way to do so is through extensions. The GenericsManifesto on apple/swift includes a document about the potential for this feature here: swift/GenericsManifesto.md at main · apple/swift · GitHub .

protocol Bag {
  associatedtype Element : Equatable
  func contains(element: Element) -> Bool
  
  func containsAll<S: Sequence where Sequence.Iterator.Element == Element>(elements: S) -> Bool {
    for x in elements {
      if contains(x) { return true }
    }
    return false
  }
}

struct IntBag : Bag {
  typealias Element = Int
  func contains(element: Int) -> Bool { ... }
  
  // okay: containsAll requirement is satisfied by Bag's default implementation
}

Is something like this still feasible for Swift 5? If so, what are some opinions on this addition? Obviously we would still have to support default implementation through extensions, but this would add some very needed sugar for Swift. I saw on some other threads that the implementation is possibly something holding this back. I'm no expert, but couldn't the type checker theoretically synthesize an extension with the same function declaration during semantic analysis? I was playing around with this earlier, and it seemed like the easy way out.

  • Alejandro

There's a star in the manifesto, meaning that this is something that is (or was) considered among the top features to be implemented.

I don't think there's any opposition to it; I'd conclude that it doesn't exist because there hasn't been the resources to make it exist. At this rate, it seems very unlikely to be done by Swift 5 or maybe even 6.

There is already a proposal on GitHub which should be the first step towards that functionality.

From my point of view we should go that route first by introducing the necessary default keyword to default implementations. This isn't only a visual addition to the existing functionality. In fact the default keyword should tell the compiler to check if all requirements are met for your extension to be a truly default implementation. This avoids issues with typos and also tells the developer information about dynamic/static dispatch.

Then, if we got that I think default implementations inside protocol bodies will become simple sugar.

What do you think @Erica_Sadun? Can we push your proposal for Swift 5?

2 Likes

Sugar perhaps, but important sugar - protocol requirements which also have default implementations behave differently to non-required members (the former are dynamically dispatched, the latter are not). At the moment, both must be written side-by-side in protocol extensions.

I would actually recommend requiring default implementations to be written in the protocol declaration, to make the difference more obvious. It would be source-breaking, but I really think this is a "bad" part of the language which needs changing, and all you'd have to do to migrate would be to move the method body.

For those who don't know, I'm referring to this:

protocol MyProto {
  func a()
}
extension MyProto {
  func a() { print("Default a()") }
  func b() { print("Default b()") }
}

struct MyStruct: MyProto {
  func a() { print("MyStruct a()") }
  func b() { print("MyStruct b()") }
}

func test(_ object: MyProto) {
  object.a(); object.b()
}

test(MyStruct())
// Output:
// MyStruct a()
// Default b()

MyStruct can override a() in a generic context, because a() is a requirement of MyProto and hence gets dynamically-dispatched. However, calling b() on some random instance of type MyProto will always invoke the default implementation, because non-requirements behave differently.

I agree with that, I would expect default to be required in extensions (assuming after Erica's proposal was accepted) and then after the sugar was introduced in the protocol body.

// After Erica's proposal
protocol P {
  func foo()
}

extension P {
  default func foo() { ... }
  func bar() { ... }
}

// After extra sugar
protocol P {
  default func foo() { ... }
}

extension P {
  func bar() { ... }
}

default already tells the reader about dynamic / static dispatch.

2 Likes