SE-0319: Never as Identifiable

The review of SE-0319: Never as Identifiable, begins now and runs through July 13, 2021.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thank you for helping improve the Swift programming language and ecosystem.

Tom Doron
Review Manager

14 Likes

Hello, thanks for this proposal.

As the warning notes, the new conformance will be used to satisfy the protocol requirement. This difference shouldn't present an observable difference given that an instance of Never cannot be constructed.

There is a difference if an existing module declares an ID type which is not Never. The proposal, as written, would break such existing code.

IIRC, previous proposals of the same kind did preserve existing conformances instead of replacing them with the one of the standard library. Maybe someone has a better recollection than mine, and could point to such a precedent?

1 Like

I am +1 on this proposal, it is straight-forward.

However, this continues to highlight a significant design point we need to look at: Never should implicitly conform to a bunch of protocols. Having one-off swift evolution proposals for each of them doesn't really make sense, we should have a systematic approach.

-Chris

34 Likes

I support this change, no problem :+1:

But I guess I'll ask the elephant in the room question -- why not look into making Never a true bottom type? It always seemed weird to me that Swift lacked a true bottom type and Never seemed like the natural candidate for it.

This does not block the current proposal from being accepted, just wanted to see if there's any thoughts we can share here?

22 Likes

The acceptance rationale for SE-0215 provides some extra info:

  • Never should become a blessed bottom type in the language. This matches with semantics in other languages and its intended role in Swift.

With respect to the latter, the Core Team discussed what Never being a bottom type actually meant. From that discussion we reached the following conclusions:

  • Semantically, as a bottom type, it would mean that Never should be implicitly convertible to any other type. This composes well in the type system because of the fact that instances of Never cannot be instantiated.
  • However, being a bottom type does not imply that Never should implicitly conform to all protocols. Instead, convenient protocol conformances for Never should be added as deemed useful or necessary.

There are various details to suss out with making Never a bottom type, and thus making it a bottom type would be served by a separate and well-considered proposal . It is the opinion of the Core Team that making Never a bottom type would be separate from it conforming to all protocols, and thus the motivation of this particular proposal and the particular problems it addresses stands on its own.

With respect to protocol conformances, the Core Team felt the language has clearly moved in a direction where explicit protocol conformance is fundamental to the language model. The compiler has grown affordances to make the implementation of protocol conformances easy in many cases (e.g., synthesized implementations of Hashable ) but that the explicit protocol conformance is quite important. Adding rules for implicit protocol conformances ā€” something that has been considered in Swiftā€™s history ā€” ends up adding real complexity to the language and its implementation that can be hard to reason about by a user as well as by the language implementation.

(which is not the say that that's the end of the discussion, but just the latest official statement from the Core Team. Note of course the explicit invitation for a "separate and well-considered proposal" to discuss further)

3 Likes

Perhaps that approach should start with a list of all protocols in the Swift Standard Library and solicit any objections to conforming Never to each of them?

I remember that remark. It would be helpful to hear a little more detail about this reasoning, which Iā€™m afraid entirely eludes me.

The bottom type is inherently a unique beast; there can be only one. And in my naĆÆvetĆ©, it seems to me that this unique special case can and should conform to all protocols:

From the compilerā€™s point of view, I would assume that since no runtime values ever belong to it, it does not need a witness table or any other kind of runtime support other than a metatype. In type checking, it would form a unique ā€œalways trueā€ special case, one that does not require any sort of more general implicit conformance mechanism.

From the language userā€™s point of view, Iā€™m hard-pressed to think of any logical reason why any protocol would not want Never to conform to it.

Iā€™m sure thereā€™s an excellent rationale here! It just eludes me. As it stands, Chrisā€™s and Konradā€™s remarks echo my sentiments exactly:

If making Never a true bottom type, including all protocols, is off the table, then at least this:

ā€¦seems like the minimum sensible approach.

2 Likes

AdditiveArithmetic
BidirectionalCollection
BinaryFloatingPoint
BinaryInteger
CVarArg
CaseIterable
CodingKey
Collection
Comparable
CustomDebugStringConvertible
CustomLeafReflectable
CustomPlaygroundDisplayConvertible
CustomReflectable
CustomStringConvertible
Decodable
Decoder
Encodable
Encoder
Equatable
Error
ExpressibleByArrayLiteral
ExpressibleByBooleanLiteral
ExpressibleByDictionaryLiteral
ExpressibleByExtendedGraphemeClusterLiteral
ExpressibleByFloatLiteral
ExpressibleByIntegerLiteral
ExpressibleByNilLiteral
ExpressibleByStringInterpolation
ExpressibleByStringLiteral
ExpressibleByUnicodeScalarLiteral
FixedWidthInteger
FloatingPoint
Hashable
Identifiable
IteratorProtocol
KeyedDecodingContainerProtocol
KeyedEncodingContainerProtocol
LazyCollectionProtocol
LazySequenceProtocol
LosslessStringConvertible
MirrorPath
MutableCollection
Numeric
OptionSet
RandomAccessCollection
RandomNumberGenerator
RangeExpression
RangeReplaceableCollection
RawRepresentable
SIMD
SIMDScalar
SIMDStorage
Sequence
SetAlgebra
SignedInteger
SignedNumeric
SingleValueDecodingContainer
SingleValueEncodingContainer
Strideable
StringInterpolationProtocol
StringProtocol
TextOutputStream
TextOutputStreamable
UnicodeCodec
UnkeyedDecodingContainer
UnkeyedEncodingContainer
UnsignedInteger
_AppendKeyPath

The 2 reasons I know of why Never would not semantically conform to a protocol are:

  • an infallible initializer or static var of Self type (would need to fatalError).
  • The protocol is 'closed' (internally exhaustive) and the author did not consider Never conforming.
4 Likes

Ah, and thus var p: P = Never(ā€¦) should logically compile, which isā€¦not nice.

And then thereā€™s the problem of conflicting protocol requirements:

protocol P0 { var x: Int { get } }
protocol P1 { var x: String { get } }
// How can Never conform to both?

It seems to me it might be possible to get around both these problems by prohibiting access to all protocol requirements ā€” initializers, static and instance members, everything ā€” via the naked Never type:

func f(p0: P0, never: Never) {
  p0.x // allowed
  never.x  // not allowed, even though Never conforms to P0
}

Hard to shake the feeling thereā€™s a solution lurking around the corner here. However, I do now see how this becomes a rabbit hole, thanks to your comment!

What would the pitfall of this be in practice? The Never type is unoccupied, so e.g. switching on the runtime type of a value canā€™t ever hit an unexpected non-match:

protocol P { }
struct A: P { }
struct B: P { }
extension Never: P {}

func f(_ p: P) {
    switch p {
        case is A: print("a")
        case is B: print("b")
        default: fatalError("unreachable")  // This is in fact unreachable, even though Never: P
    }
}

Hmm, I suppose one could gum things up by switching on the metatype of a presumed-exhaustive protocol:

func f(_ p: P.Type) { ā€¦ }
f(Never.self)

Is that the concern? Is there more Iā€™m missing?

That's what I was thinking of in particular. I don't have any ideas of what one would switch on the metatype for, but it works, and making Never conform to such protocols would change the behavior.

1 Like

What about the disguised Never type? Recall that types can be materialized in any generic function:

protocol DefaultInitializable {
    init()
}

func instantiate<T: DefaultInitializable>() -> T {
    return T()
}

let n: Never = instantiate()

The same problem applies to all static methods and properties (not just Self-constrained or otherwise ā€œfancyā€ ones).

One could work around this by fatalErroring, or maybe returning nil for nullable initializers, but this strikes me as unprincipled; depending on your point of view, itā€™s either turning an error that should be static into a dynamic one, or implicitly adding ad-hoc behaviour to the bottom type.

It would also require the static parts of protocol conformances to actually be synthesized for Never (either up front or through special runtime synthesis), whereas use of Never as a subtype of a concrete type should never incur overhead.

3 Likes

Runni

Running the risk of derailing this proposal, which is not my intent. (+1 from me)

Why donā€™t we make adding scoped conformance trivial?

1 Like

This review for SE-0319 has concluded and the proposal was accepted.

Thank you to everyone for the feedback and contributions to this proposal.

1 Like