I have following enum in a class.
enum Attributes: String, CustomStringConvertible {
case eventDate
case eventName
case eventType
case country
var description: String {
return self.rawValue
}
}
When I try have the following code, compiler complains with following error.
var attributesList: [String] {
return [
Attributes.eventDate, //<-- Compiler error on this row
Attributes.eventName,
Attributes.eventType,
Attributes.country]
}
Cannot convert value of the type 'Attributes' to expected element type 'String'
Shouldn't the "CustomStringConvertible" protocol return the "description"? What is wrong in the above code?
TL;DR - It doesn't work because an array of Attribute
s cannot be assigned to an array of String
s, they are both mismatched types, and Swift does not do automatic conversion between types, and an explict conversion needs to be specified.
In Swift, when you initialise an array using an array literal, the following happens under the hood:
let words = ["hello", "world"]
words
. Since we have not specified the type of the words
, implicitly an array is assumed. The type of the elements underlying the array is determined based on the contents of the array literal.String
types; this is easily understood by the compilerArray
) conforms to a pre-defined protocol called ExpressibleByArrayLiteral
which has an associated type constraint to match Element
, the compiler will actually be converting our line to the followingExample:
let words = [String].init(arrayLiteral: ["hello", "world"]) // we do not call this init directly
This is how initialising with array literals work. In the above example, since we did not specify the type of array, the implicit type setting will work. If we specified a mismatched type, the assignment will fail, since ExpressibleByArrayLiteral
requires the associated Element
type of the array literal and the actual array you are assigning to to match.
So the following fails:
let words:[String] = [1, 2] // array literal has Element=Int, array has Element=String
This also shows that there is no implicit type conversion between Int
and String
, even though Int
conform to CustomStringConvertible
.
In your case, you are trying to assign an array literal consisting of Attributes
to an array of String
. This is a type mismatch. This is the reason it fails.
If you state protocol conformance, the following line will work:
var attributesList: [CustomStringConvertible] {
return [
Attributes.eventDate,
Attributes.eventName,
Attributes.eventType,
Attributes.country
]
}
// note that we have an array of CustomStringConvertible protocol,
// each element here is still of Attributes type
// a type conforming to a protocol can be cast to an instance
// of that protocol automatically
// The above initialisation works simply because the following
// also works without any further action required
// let a:CustomStringConvertible = Attributes.country
If you really want a list of string values, you need to map this to a string explicitly:
var attributesList1: [String] {
return [
Attributes.eventDate,
Attributes.eventName,
Attributes.eventType,
Attributes.country
].map { $0.description }
}
var attributesList2: [String] {
return [
Attributes.eventDate.description,
Attributes.eventName.description,
Attributes.eventType.description,
Attributes.country.description
]
}