[Pitch] Non-Frozen Enumerations

I never understood the rationale behind "@unknown default" properly, IMHO it just doesn't work in general case.


Consider the client networking application that accepts a stream of drawing commands sent over the network by the server and renders them to the screen accordingly.
The client and the server are both v1.0 and all is good.

Then there was a change to one of the enumerations that both client and server use: a new case was added. The client usage of that enumeration contained "@unknown default" clause so when the client was recompiled with the new v1.1 library there were no compilation errors. No one alarmed, yet we could have one of the following outcomes:

1. some shapes are missing or not drawn correctly (e.g. colours are off)
// common enumeration
enum PayloadType {
    case string
    case point
    case rect
    case polygon // new case in v1.1
}
// client code
switch payloadType {
	case .string: drawString()
	case .point: drawPoint()
	case .rect: drawPoint()
	@unknown default: drawPlaceholder()
}
2a. the app stops rendering some streams, and you have to restart the stream, but the issue repeats.
// stream format:
// <lenType> <len> <payloadType> <payload> ... repeat

// common enumeration
enum LengthType {
    case byte
    case short
    case word
    case long // new case in v1.1
}

// client code
switch lengthType {
	case .byte: len = readByte();   let payload = readBytes(len); renderPayload(payload)
	case .short: len = readShort(); let payload = readBytes(len); renderPayload(payload)
	case .word: len = readWord();   let payload = readBytes(len); renderPayload(payload)
	@unknown default: print("can't really proceed"); abortStream()
	
}
2b. there are crashes when rendering some streams
// same as above, plus:
// client code
switch lengthType {
    ....
    @unknown default: fatalError("can't really proceed")
}

Note that the library change was "minor" yet we could still have the outcome #2, which is hardly minor from the end users POV and doesn't feel a justifiable behaviour when going from library v1.0 to v1.1


IMHO, this is better (from the end user point of view at least):

switch payloadType {
    case .string: drawString()
    case .point: drawPoint()
    case .rect: drawPoint()

    // Option A: "lazy developer":
    default: drawPlaceholder() // normal default clause

    // Option B: "hard working developer":
    // no default statement here
    // no warning when compiling with library v1.0 (IMHO how it should be)
    // error with compiling with library v1.1 (and a chance to improve)
}

switch lengthType {
    case .byte: len = readByte();   let payload = readBytes(len); renderPayload(payload)
    case .short: len = readShort(); let payload = readBytes(len); renderPayload(payload)
    case .word: len = readWord();   let payload = readBytes(len); renderPayload(payload)
    // no default statement here
    // no warning when compiling with library v1.0 (IMHO how it should be)
    // error with compiling with library v1.1 (and a chance to improve)
}

How do I opt-opt of getting a warning:

Say, I have a zero warning tolerance policy by making all warnings errors... So I can't have a warning there. Yet, I do not want to add "@unknown default" (or a normal default) as if I do that – I will not notice the change when the upgraded library gets a new case! Any way to opt-out of SE-0192 ?

1 Like