Some duplicate enum type casting in Swift

280 Views Asked by At

See the codes below:

enum Gender {
  case Male, Female, Unknown
}
enum ExplicitGender {
  case Male, Female
}

func whatGender(_ g: Gender) {
  print("I am \(g)")
}

func makeSureYourGender() -> ExplicitGender {
  return ExplicitGender.Male
}

var myGender = makeSureYourGender();

// How to Cast type `ExplicitGender` to `Gender`?
// This fails
// whatGender(myGender)

The syntax myGender as ExplicitGender is not correct.

Actually, the Male,Female in ExplicitGender and Gender are the same. Make the 2 is 2 different Enum Types is not exactly correct.

Or There is another code structure or syntax to resolve this problem?

See also:


enum Language {
  case English, French, Arabic, Russian, Chinese, Spanish, Japanese, German, Italian
}

enum UNOfficialLanguage {
  case English, French, Arabic, Russian, Chinese, Spanish
}

In typescript, i will do it with:

type UNOfficialLanguage = 'English' | 'French' | 'Arabic' | 'Russian' | 'Chinese' | 'Spanish';
type Language = UNOfficialLanguage | 'Japanese' | 'German' | 'Italian';
2

There are 2 best solutions below

12
Rob Napier On BEST ANSWER

Your TypeScript example is not an enum. It's a union of string literal types. There is no equivalent to literal types in Swift. (TypeScript also has enums, but they're a different thing.)

Swift and TypeScript have radically different approaches to what it means to be a type. In TypeScript, two things that have the same structure are the same type ("structural typing"). In Swift, two things can have the same structure can be unrelated and what matters is the name of the type ("nominal typing"). The TS way is convenient, but there are many types it cannot express. The Swift way can express more things, but it lacks certain conveniences.

For example, in TypeScript, if you declare two interfaces with the same properties, they are interchangeable:

interface Animal {
  legs: number;
}

interface AirTravel {
  legs: number;
}

let trip: AirTravel = {legs: 4};

function feedAnimal(a: Animal) {}

feedAnimal(trip)  // No problem... legs are legs

The equivalent code in Swift will give an error:

struct Animal {
  var legs: Int
}

struct AirTravel {
  var legs: Int
}

let trip = AirTravel(legs: 4)

func feedAnimal(_ a: Animal) {}

feedAnimal(trip)
// error: cannot convert value of type 'AirTravel' to expected argument type 'Animal'

Whether you think one approach is better typically depends on the problems you're trying to solve. (I find the Swift approach dramatically more powerful than the TypeScript approach. Others find Swift restrictive because it won't let you do things you "know" are correct unless you can prove to the compiler it's correct.)

To your example, just because two symbols have the same spelling (Language.English and UNOfficialLanguage.English) doesn't mean they are related in any way. If you want to convert between them, you'll need to convert between them.

That said, Swift does have conveniences to convert to strings and from strings.

enum Language: String {
  case english, french, arabic, russian, chinese, spanish, japanese, german, italian
}

enum UNOfficialLanguage: String {
  case english, french, arabic, russian, chinese, spanish
}

Note the addition of : String. This allows you to convert easily between the enum and a string representation of the enum.

let l = UNOfficialLanguage.arabic
let language = Language(rawValue: l.rawValue) // type is Language?

Note that language is of type Language? since this conversion could fail. Since you happen to know that Language is a superset of UNOfficialLanguage, you could force the conversion in that direction:

extension Language {
    init(_ official: UNOfficialLanguage) {
        self.init(rawValue: official.rawValue)!
    }
}

let language = Language(l) // type is Language

For more on this feature, see Raw Values in the Swift Programming Language.

0
Umar Maikano On

You can not (Downcast or Upcast) cast objects that are not related to each other. Please check casting on swift for information on how to cast.

Gender and ExplicitGender are enums which are not related to each other.

One solution is to create a helper function which converts Gender types to ExplicitGender types and vice versa. Please see the below code as an example:

func genderFromExplicitGender(gender: ExplicitGender) -> Gender {
  switch gender {
   case .Male: return .Male
   case .Female: return .Female
 }
return .Unknown }

The catch here is even though Gender and Explicit gender have Male and Female. They are not related to each other in anyway. The code above checked for Male (as ExplicitGender.Male) and returns Male (as Gender.Male), does the same for Female.

I will advise you read more on casting in swift