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
Unit
s 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
Wkwebview Won't Load (Nsviewcontroller, Os X)
Navigation Bar Items After Push from Swiftui to UIkit
Iocreateplugininterfaceforservice Returns Mysterious Error
Swift: Binary Operator '==' Cannot Be Applied to Operands of Type "Protocol"
Does Swift Allow Code Blocks Without Conditions/Loops to Reduce Local Variable Scope
Detect When a Custom Cell Is Selected from Within The Cell Itself
Macos Security Scoped Url Bookmark for Folder
Convert from Nsdictionary to [String:Any]
How to Override Internal Framework Method in Application (Outside Framework)
Swift Error "Domain=Nscocoaerrordomain Code=3840 "Invalid Value Around Character 1."
Swift iOS14 Datepicker Text Alignment
"The Requested Snapshot Version Is Too Old." Error in Firestore