How to Declare Swift Generic for Enums of Particular Type

Can I make a Swift enum generic, so I can use its cases to infer a type for a generic class?

You've got it backwards, you shouldn't infer the generic type depending on the enum value, because that means you want to determine some compile-time thing (generic type) with a value that is possibly known at runtime (the value of the enum).

Therefore, we need to make the type parameter a compile-time-only thing, i.e. also a type parameter.

You first introduce a VehicleTypeProtocol, and a struct implementing that protocol for each of the enum case:

protocol VehicleTypeProtocol {
// this associated type creates the link between a vehicle type and a config type
associatedtype ConfigType: Config
// this is so that we can assign to Vehicle.type
static var type: VehicleType { get }
}

struct Car : VehicleTypeProtocol {
typealias ConfigType = CarConfig
static var type: VehicleType { .car }
}

struct Bike : VehicleTypeProtocol {
typealias ConfigType = BikeConfig
static var type: VehicleType { .bike }
}

struct Scooter: VehicleTypeProtocol {
typealias ConfigType = BikeConfig
static var type: VehicleType { .scooter }
}

And then the initialiser can be implemented like this:

init<T: VehicleTypeProtocol>(type: T.Type, config: C) where T.ConfigType == C {
self.type = T.type
self.config = config
}

Usage:

let bike = Vehicle(type: Bike.self, config: .mtb)

But man, this is convoluted...

How can I create an instance of a generic enum in Swift

As noted, your function needs a return type if you want it to return anything. Since you seem to want to use the function to create a value of the specified enum type, that return type should probably be either E or E?. (You're wrapping init?(rawValue:), which returns an optional because rawValue may not map to one of the enum cases. So you either want to pass the optional through to your caller or have some logic in your function to unwrap it and handle the nil case.)

Your parameter rawValue also needs a real type — T.Type is not a fully qualified type in your declaration. You can get at the raw value type of the enum using the RawValue typealias that the RawRepresentable protocol (which you've already given as a generic constraint) defines.

So, here's your function:

func createEnum<E: RawRepresentable>(rawValue: E.RawValue) -> E? {
return E(rawValue: rawValue)
}

Note that if you try something like this:

enum Foo: Int {
case One = 1
case Two = 2
}
createEnum(1)
createEnum<Foo>(1)

It won't work — the first one doesn't specify which specialization of the generic function to use, and the second doesn't work because Swift doesn't allow manual specialization of generic functions. Instead, you have to set it up so that type inference does its thing:

let f: Foo? = createEnum(1)
someFuncThatTakesAFoo(createEnum(1))

Constrain generic type by swift enum

Method 1

According to the comments, I think this is what you are trying to accomplish:

protocol MyEnums {}

enum T1: MyEnums { case one, two }
enum T2: MyEnums { case one, two }
enum T3 { case one, two }

let foo: [MyEnums] = [T1.one, T1.two, T2.one, T2.two]

The array can only contain objects conforming to the MyEnums protocol.

Note how enum T3 cannot be stored within foo because it does not conform to the protocol MyEnums.



Method 2

Here's a little more. You actually can do what you were asking:

protocol MyEnums {}
enum T1: MyEnums { case one, two }
enum T2: MyEnums { case one, two }
enum T3 { case one, two }
indirect enum Foo<T: MyEnums> {
case empty
case cons(T, Foo<T>)
}

Note that the generic is <T: MyEnums>.

This code will do exactly what you want. It can store enums conforming to the MyEnums protocol. So you can store enums T1 and T2 if they are conformed to MyEnums, but not T3 because it is not.

So you will be able to do this:

let x: Foo = .cons(T1.one, .cons(T1.two, .empty))

However this second method does not allow for this:

let x: Foo = .cons(T1.one, .cons(T2.one, .empty))

I'll try to see if we can do this..



Method 3

I got it!! This is exactly what you were asking for:

protocol MyEnums {}
protocol MyEnums1: MyEnums {}

enum T1: MyEnums1 { case one, two }
enum T2: MyEnums1 { case one, two }
enum T3 { case one, two }

indirect enum Foo<T: MyEnums> {
case empty
case cons(MyEnums1, Foo<T>)
}

let x: Foo<T1> = .cons(T1.one, .cons(T2.one, .empty))
print(x)

It required 2 protocols, but I finally fixed Method 2. Remember, you can only put in objects conforming to MyEnums1, so T3 is not an option. Perfect!

This was a very cool question to answer.

In Swift, how can you use generic enum types in protocol extensions?

There are two different way you could do it depending on what your requirements are.


If you don't require the type to be a enum, you can simply do

protocol AnimalStorageProtocol {
associatedtype AnimalType: Hashable
var storage: [AnimalType: Int] { get set }
}

This will allow any hashable type to be used.


If you require that the types can only be RawRepresentable where the RawValue is a String you'll have to define another protocol that your animal types will have to conform to.

protocol AnimalType: Hashable, RawRepresentable {
var rawValue: String { get }
}

protocol AnimalStorageProtocol {
associatedtype Animal: AnimalType
var storage: [Animal: Int] { get set }
}

Then you just have to set your enum types to conform to the AnimalType protocol.

enum CatType: String, AnimalType { ... }
enum DogType: String, AnimalType { ... }

Generic Swift 4 enum with Void associated type

In Swift 3 you can omit the associated value of type Void:

let res: Result<Void> = .success()

In Swift 4 you have to pass an associated value of type Void:

let res: Result<Void> = .success(())
// Or just:
let res = Result.success(())

Enums and Generics in Swift

Since you inherit your enums from String you're getting init?(rawValue: String) parsing initializer for free. Personally, I wouldn't create function like unit(of:) because it just throw away the amount part. Instead, I would create parse function like parse(value: String) -> (Double, LengthUnit)?

Anyway, if you really want unit(of:) function and want to reduce code duplication as much as possible you may indeed benefit from using generics.

First of all, we need Unit marker protocol like this

protocol UnitProtocol { }

Then, we can create generic function which will use init?(rawValue: String) of RawRepresentable Units to return unit based on passed string

func getUnit<U: UnitProtocol & RawRepresentable>(of value: String) -> U? where U.RawValue == String {
// you need better function to split amount and unit parts
// current allows expressions like "15.6.7.1cm"
// but that's question for another topic
let digitsAndDot = CharacterSet(charactersIn: "0123456789.")
let unitPart = String(value.drop(while: { digitsAndDot.contains($0.unicodeScalars.first!) }))
return U.init(rawValue: unitPart)
}

And thats essentially it. If you don't like to use functions and prefer static methods instead, then you just need to add these methods and call getUnit(of:) inside

enum LengthUnit: String, UnitProtocol {
case inch
case cm
case m
case yard

static func unit(of value: String) -> LengthUnit? {
return getUnit(of: value)
}
}

enum WeightUnit: String, UnitProtocol {
case g
case kg
case lb
case oz

static func unit(of value: String) -> WeightUnit? {
return getUnit(of: value)
}
}

Or, instead adding unit(of:) methods everywhere we may even do better and add extension

extension UnitProtocol where Self: RawRepresentable, Self.RawValue == String {
static func unit(of value: String) -> Self? {
return getUnit(of: value)
}
}

Now you'll get static unit(of:) for free by just adding conformance to String and Unit

enum WeightUnit: String, UnitProtocol {
case g
case kg
case lb
case oz
}


Related Topics



Leave a reply



Submit