[Proposal] Enum subsets

If this does get implemented, I would love to see an overload of String.data(using:) that accepts a subset of String.Encoding that is unicode-based and allows the method to return a non-optional Data

Since String guts are being re-implemented right now maybe you should talk about this with Michael_Ilseman ?

Can you all please remove me from recipients list? I unsubscribed from the mailing list because I'm no longer interested in participating in this group.

I believe I have fixed this so that you will not get further notifications. You had the setting enabled for:
Send me an email when someone quotes me, replies to my post, mentions my @username, or invites me to a topic

in your preferences.

String(decoding:as:) already does this:

let data = Data([0x41, 0x42])
let str = String(decoding: data, as: UTF8.self) // "AB"
type(of: str) // String.Type

Not all Unicode-based encodings are available. I only used it with UTF-8 so far. I'm pretty sure UTF-16 also works (though I don't know if it handles different byte orders correctly).

The guts are pure ABI and have no API impact. String.Encoding subsets seem totally reasonable.

Not on my end. I am working on something else enum related and… if that ever lands I should be closer to maybe getting an implementation together for this.

Has anything come of this? Recently came across a scenario where an enum subset would have resulted in much cleaner code.

The issue I faced was that I was inserting tables into a SQL database from Swift. I have an enum representing the columns in a given table. And I switch over each case to provide a value for each column. This way if new columns are added in the future, compiler will complain if a value isn't provided in the switch.

The problem was one of the columns was an autoincrement ID, and I wanted to allow the DB to decide what ID to use. This means leaving one column out. So I had to create a function that permitted specifying which columns to provide values for, and then only provide values for the included columns in the aforementioned function.

I decided against using a set to specify the columns because I didn't like the idea that new columns could be added without me explicitly deciding whether or not they should be included in a given insert β€” could be multiple inserts (sets created) in different places.

So instead what I did was create a function that accepts an instance of the enum and returns true if it should be included and false if it should not. Then I pass this function as a closure to the function that creates the insert statement, and will only request the values for those columns included by that closure. This works, but I would have preferred to just specify a safe subset; a syntax similar to this:

// The cases belong to the enumeration `Table.Media.Column`.
[include: .kind, .url, .index, exclude: .id]

This could then be accepted wherever a Table.Media.Column.Subset is the type. And if a new case were to be added later on, the compiler would complain if it's not also added as included/excluded in the above subset. And the same would go for cases removed β€” they'd have to also be removed from the subset.

This is a practical workaround I'd use today for simple cases (e.g. enums with no associated values, etc), until (if ever) we have something better in the language.

enum Colour: Int {
    case red, orange, yellow, green, blue, indigo, violet
}

enum LCDColour: Int {
    case red = 0, green = 3, blue = 4
//  case red = Colour.red.rawValue // 😒 πŸ›‘ Error: Raw value for enum case must be a literal
    
    static var checked = false

    static func check() { // call this somewhere. once is enough
        if !checked {
            precondition(red.rawValue == Colour.red.rawValue)
            precondition(green.rawValue == Colour.green.rawValue)
            precondition(blue.rawValue == Colour.blue.rawValue)
            checked = true
        }
    }

    init?(colour: Colour) {
        Self.check()
        self.init(rawValue: colour.rawValue)
    }
    
    var colour: Colour {
        Self.check()
        return Colour(rawValue: self.rawValue)!
    }
}

Usage:

// Usage:
let lcdColour: LCDColour = LCDColour(colour: .green)!
let colour: Colour = lcdColour.colour

Non ideal but better than nothing.

Note that this method avoids doing switch-style conversion. If for some reason you can't make your enums with rawValue (system enum, or your enums are with associated values, etc) - you'd need to do the switch style conversion every time you convert between colour and lcdColour. OTOH switch-style solution is more maintainable as compiler will help you better and you don't have to rely on runtime precondition checking.

Yeah. I mean I personally would never use that approach. Any solution absent the compiler safety (like using the switch-style) is a no nonstarter for me.

And if I'm going to go through the effort of going the switch route, and there stands the possibility of being multiple subsets, then it'd be more convenient to just define them as simple static functions that use the switch-style approach and return an actual Set of the cases for the subset.

I see. I thought you are after static type safety, e.g. ability to switch through all LCDColour type constants (or your analogue of it) and get an error when you added a new LCDColour constant but forgot to handle it in your switch – to me that type of safety is very important (and the very reason I'd not go with a simpler approach:

enum BadLCDColour {
    static let red = Colour.red
}

let colour: Colour = BadLCDColour.red // yeah, wow 😐