Protocol can inherit from a concrete class

I want to make a protocol that can only be conformed to by subclasses of NSManagedObject. Without thinking, I did this

protocol EntityWithHref: NSManagedObject {
  var href: String? { get }
}

Surprisingly, this compiled (Xcode 10.2.1, Swift 4.2)! Only afterwards did I realize how odd this looks and indeed I can't find any documentation saying that this should be allowed.

It seems to behave the same as

protocol EntityWithHref where Self: NSManagedObject {
  var href: String? { get }
}

which makes more sense to me. But the only reference I can find to this is a thread where someone says the latter syntax is a hack and the former will be the right way at some point in the future.

How is this supposed to work? Is this documented somewhere?

This can have issues in Swift 4.x but it's fully implemented in Swift 5. EntityWithHref: NSManagedObject is just a convenient use to express EntityWithHref where Self: NSManagedObject.

This was also mentioned in the changelog (at SR-5581) and in Swift 5 release notes.

  • Protocols can now constrain their conforming types to those that subclass a given class. Two equivalent forms are supported:
protocol MyView: UIView { /*...*/ }
protocol MyView where Self: UIView { /*...*/ } 

Swift 4.2 accepted the second form, but it wasn’t fully implemented and could sometimes crash at compile time or runtime. (SR-5581) (38077232)

5 Likes

Ah, should have thought to check the release logs. Still kind of annoying that docs.swift.org is so slow to update, considering that it says "Swift 5" in the sidebar.

I'm a little concerned about this comment

it wasn't fully implemented and could sometimes crash at compile time or runtime [emphasis added]

When I pushed my whole dev team to switch from Objective-C to Swift, it was under the promise that Swift was stable and generally speaking if it compiles it runs (as long as you avoid !). Now I'm concerned that we're about to release an app with hidden runtime crashes because we're still on 4.2.

What circumstances would cause it to crash at runtime? Is it undefined behavior? Would unit tests guard against or would it crash randomly?

If you're compiling your App using Xcode 10.2 or newer then you already using Swift 5 runtime regardless the language mode your project is set to. I'm not sure if the app bundle will also contain the Swift 5 runtime, if it does then I assume it should be safe to use this feature as the runtime should not crash in that case. But I think @Slava_Pestov can answer this question better than me.

We try to not miscompile to invalid code, but sometimes bugs slip through. There is no correctness proof for the swift compiler. :)

Unit tests should be fine. But unless I misread your post you’re compiling your app with the Xcode 10.2 compiler in Swift 4.2 mode. This means you’re getting the latest bug fixes even though it’s an older language mode.

6 Likes

I wish this would be true on ABI stable platforms as well.

You are correct, we use the Swift 5 runtime for backward deployment regardless of language mode. However the feature discussed in this thread doesn’t rely on any runtime features (I guess the release note should have read “run time” and not “runtime” ;-) )

1 Like