Calling default implementation of protocols

If you define a simple protocol like:
protocol Foo {
    func testPrint()
}
And than you provide a default implementation for testPrint() method in protocol extensions like:
extension Foo {
    func testPrint() {
        print("Protocol extension call")
    }
}
You aren't allowed to call the fault implementation from the structure eg.
struct Bar: Foo {
    func testPrint() {
        self.testPrint()
        print("Call from struct")
    }
}
This is some sort of limitation as often happens that a default implementation is providing a major part of implementation and only one, simple line is changed in actual struct implementation. If you're using classes you can achieve this by creating a base class and calling a method on super. If you consider structs, there's no such possibility and you always have to write a whole implementation from scratch in each structure which conforms to the protocol.
You can use composition by creating nested structure, but it's neither logical nor clean... It's rather a hack...
struct Bar: Foo {
    func testPrint() {
        // Calling default implementation
        struct Dummy : Foo {}
        let dummy = Dummy()
        dummy.testPrint()
        print("Call from struct")
    }
}

2 Likes

I do think this would indeed be a feature worth having, but it would need design. There's also a simple workaround for now: put the default implementation in another method or a free function.

Jordan

···

On Dec 8, 2015, at 9:43, Mateusz Zajac via swift-evolution <swift-evolution@swift.org> wrote:

If you define a simple protocol like:
protocol Foo {
    func testPrint()
}
And than you provide a default implementation for testPrint() method in protocol extensions like:
extension Foo {
    func testPrint() {
        print("Protocol extension call")
    }
}
You aren't allowed to call the fault implementation from the structure eg.
struct Bar: Foo {
    func testPrint() {
        self.testPrint()
        print("Call from struct")
    }
}
This is some sort of limitation as often happens that a default implementation is providing a major part of implementation and only one, simple line is changed in actual struct implementation. If you're using classes you can achieve this by creating a base class and calling a method on super. If you consider structs, there's no such possibility and you always have to write a whole implementation from scratch in each structure which conforms to the protocol.
You can use composition by creating nested structure, but it's neither logical nor clean... It's rather a hack...
struct Bar: Foo {
    func testPrint() {
        // Calling default implementation
        struct Dummy : Foo {}
        let dummy = Dummy()
        dummy.testPrint()
        print("Call from struct")
    }
}

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I totally agree.

Yes this limitation only applies to methods declared in the protocol, that are always dynamically dispatched.

For functions that are only declared in the protocol extension, but not in the protocol itself, you can use the static dispatch:

    protocol P {
        func dyn()
    }

    extension P {
        func dyn() {
            print("P.dyn")
        }
        func ext() {
            print("P.ext")
        }
    }

    struct C : P {
        func dyn() {
            // No way to call P.dyn because of dynamic dispatch
            print("C.dyn")
        }
        func ext() {
            (self as P).ext()
            print("C.ext")
        }
    }

    C().ext() // prints P.Ext, C.ext

Gwendal Roué

···

Le 8 déc. 2015 à 18:43, Mateusz Zajac via swift-evolution <swift-evolution@swift.org> a écrit :

If you define a simple protocol like:
protocol Foo {
    func testPrint()
}
And than you provide a default implementation for testPrint() method in protocol extensions like:
extension Foo {
    func testPrint() {
        print("Protocol extension call")
    }
}
You aren't allowed to call the fault implementation from the structure eg.
struct Bar: Foo {
    func testPrint() {
        self.testPrint()
        print("Call from struct")
    }
}
This is some sort of limitation as often happens that a default implementation is providing a major part of implementation and only one, simple line is changed in actual struct implementation. If you're using classes you can achieve this by creating a base class and calling a method on super. If you consider structs, there's no such possibility and you always have to write a whole implementation from scratch in each structure which conforms to the protocol.
You can use composition by creating nested structure, but it's neither logical nor clean... It's rather a hack...
struct Bar: Foo {
    func testPrint() {
        // Calling default implementation
        struct Dummy : Foo {}
        let dummy = Dummy()
        dummy.testPrint()
        print("Call from struct")
    }
}

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I really love the workaround with another method used as a super method - looks really clean and easy to use compared to nested struct.

Mateusz

···

Dnia 09.12.2015 o godz. 00:58 Jordan Rose <jordan_rose@apple.com> napisał(a):

I do think this would indeed be a feature worth having, but it would need design. There's also a simple workaround for now: put the default implementation in another method or a free function.

Jordan

On Dec 8, 2015, at 9:43, Mateusz Zajac via swift-evolution <swift-evolution@swift.org> wrote:

If you define a simple protocol like:
protocol Foo {
    func testPrint()
}
And than you provide a default implementation for testPrint() method in protocol extensions like:
extension Foo {
    func testPrint() {
        print("Protocol extension call")
    }
}
You aren't allowed to call the fault implementation from the structure eg.
struct Bar: Foo {
    func testPrint() {
        self.testPrint()
        print("Call from struct")
    }
}
This is some sort of limitation as often happens that a default implementation is providing a major part of implementation and only one, simple line is changed in actual struct implementation. If you're using classes you can achieve this by creating a base class and calling a method on super. If you consider structs, there's no such possibility and you always have to write a whole implementation from scratch in each structure which conforms to the protocol.
You can use composition by creating nested structure, but it's neither logical nor clean... It's rather a hack...
struct Bar: Foo {
    func testPrint() {
        // Calling default implementation
        struct Dummy : Foo {}
        let dummy = Dummy()
        dummy.testPrint()
        print("Call from struct")
    }
}

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I have recently come across a similar need and thought of a naive solution:

protocol Foo {
	func x() -> Int
}

extension Foo {
	func x() -> Int {
		return 44
	}
}

struct Bar: Foo {
	func x() -> Int {
		if something { return 0 }
		// Otherwise fallback to default implementation:
		return Foo.x(self)()
	}
}

Which in theory should work, but seems not to be possible due to [SR-75] Referencing a protocol function crashes the compiler · Issue #42697 · apple/swift · GitHub.

Alternatively, this could be something like self.Foo.x(), but that seems kind of like C++.

The real-world motivation for this is here:

protocol FilterBase {
	func isFooVisible(_ foo: Foo) -> Bool
	func filteredFoos(_ foos: [Foo]) -> [Foo]
}

The default implementation of filteredFoos(_:) is obvious - it just applies isFooVisible(_:) as the filter. Which is OK for most implementations, but in some cases, you want to include some optimizations, like:

	func filteredFoos(_ foos: [Foo]) -> [Foo] {
		if self.dateRange.isInfinite {
			return foos
		}

		// Return the default implementation.
	}

I've run into this issue on several occasions and eventually, the only solution to this is to do this:

extension Foo {
	func defaultX() -> Int {
		return 44
	}

	func x() -> Int {
		return self.defaultX()
	}
}

Yes, it's a solution, but IMHO it's not a pretty solution. @Slava_Pestov mentioned in the linked ticket that my original assumption that Foo.f(self)() should work and the ticket is mentioned as in progress. May I ask what exactly is the issue here and if there is an estimate when this might work? Does it depend on something else?

Of course, if anyone has any suggestions how to solve this better, I'm all ears. Thanks.

2 Likes

Foo.x(self)() would be equivalent to self.x(), dynamically dispatching to the protocol method implementation for the concrete type of self. It would not call the protocol extension implementation in your example.

3 Likes

Oh, right. Then it was truly naive from me. It would probably require to allow something like Foo.static.x to reference statically typed implementation or a method. Which is a way bigger feat than the original intention as it would unlikely be just tied to protocols and would unleash a whole new beast...

protocol Foo {
    func testPrint()
}

extension Foo {
    func testPrint() {
        print("Protocol extension call")
    }
}

class Bar {}

extension Bar: Foo {
    func testPrint() {
        (self as Foo).testPrint() // Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffeed7bdff8)
        print("Call from struct")
    }

I think this should work because I'm explicitly saying as Foo, which have it's own implementation. What I get is Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffeed7bdff8). Is that intended behaviour?

1 Like

(self as Foo).testPrint() will not call the default implementation, because you have provided an implementation of testPrint as part of the Bar: Foo conformance.

1 Like

Well if you could choose the implementation this way, it would also solve diamond inheritance problem. Right now each solution of that problem is hacky.

You can start a pitch thread on #evolution:pitches if you want (it's basically [SR-117] Calling default implementation of protocols · Issue #42739 · apple/swift · GitHub)

1 Like