Both of these ways below seem to work, but it's varied over the years so I'm looking for documentation on it. I don't see any. Is this supposed to work into the future?
I have an enum that represents some meta data columns added to a SQL table. The "dbName" property (ie, the SQL column name) is this:
var dbName: String { "_\(self)" }
I know about raw values. But if there is a standard, official way to get the string version of the case name, then I don't need to use a String raw value.
You don't need to set those values yourself. enum Thing: String will synthesize the rawValue to match the case name, and give you an initializer. You can rely on those values, unlike the type description.
Another part of my resistance to the String raw value is that when I ran some tests a while a back it performed slightly slower than enums based on Int or no raw value. Admittedly, it was not going to matter in most situations. I was doing things like throwing a million of them in a dictionary as keys. But the perfectionist in me thinks that since Swift has the meta-data for the case name, there should be a standard way to get it, without changing the implementation of my enum. :)
Also, it's very useful to print the enum names in logging/debugging messages. And that's a case where you don't want to change every enum to use raw Strings.
That test sounds like its bottleneck would be the hashing of Strings, which, unless you're doing that a lot yourself, isn't representative of your actual workload. Besides which, String(describing:) and string interpolation can be slow as well.
At the moment the speed of getting the name is not important to me, but I can see situations where I might care about the speed of == and switch. I wonder if with raw String-based enums if the performance of those is different from Int-based enums. It seems like it would be.
I may find some time later today to write some performance test code for this.
I've done that a lot, and I had the same question as you a couple of days ago. I think it would be nice if String(describing: someEnum) was documented and guaranteed to work as it currently does in the future.
Here's the String initializer which is used there.
/// Creates a string representing the given value.
///
/// Use this initializer to convert an instance of any type to its preferred
/// representation as a `String` instance. The initializer creates the
/// string representation of `instance` in one of the following ways,
/// depending on its protocol conformance:
///
/// - If `instance` conforms to the `TextOutputStreamable` protocol, the
/// result is obtained by calling `instance.write(to: s)` on an empty
/// string `s`.
/// - If `instance` conforms to the `CustomStringConvertible` protocol, the
/// result is `instance.description`.
/// - If `instance` conforms to the `CustomDebugStringConvertible` protocol,
/// the result is `instance.debugDescription`.
/// - An unspecified result is supplied automatically by the Swift standard
/// library.
///
/// For example, this custom `Point` struct uses the default representation
/// supplied by the standard library.
///
/// struct Point {
/// let x: Int, y: Int
/// }
///
/// let p = Point(x: 21, y: 30)
/// print(String(describing: p))
/// // Prints "Point(x: 21, y: 30)"
///
/// After adding `CustomStringConvertible` conformance by implementing the
/// `description` property, `Point` provides its own custom representation.
///
/// extension Point: CustomStringConvertible {
/// var description: String {
/// return "(\(x), \(y))"
/// }
/// }
///
/// print(String(describing: p))
/// // Prints "(21, 30)"
public init<Subject>(describing instance: Subject)
So what String(describing: someEnum) gives us is "the default representation supplied by the standard library" / "An unspecified result supplied automatically by the Swift standard library".
I wouldn't be surprised if this was already relied upon often. I used this trick for the first time just yesterday, and I'm pretty sure I've done so only because I've seen it before. I too wish there was a standard forward-compatible way to get the name of the case.
My use case involves having compile time constants that can represented both as a number or as string depending on context. An enum is pretty convenient to express this mapping because it enforces the case identifiers are unique and the numeric raw values are unique too, plus you can iterate on the cases.
There’s no need for performance test code, a really simple chunk of code will demonstrate why this isn’t a problem. Here’s a sample program:
enum MyEnum: String {
case theFirst
case theSecond
}
print(MemoryLayout<String>.size)
print(MemoryLayout<MyEnum>.size)
This will print::
16
1
This is because raw value enums do not literally store their backing value, but instead use the standard enum representation. The raw values are stored in a side table and looked up as needed.
I've actually hit a case where String(describing: someEnum) doesn't work and it's really scuttled my plans for an important use-case I'm not sure how I'm going to resolve it—may have to wind up using code generation sadly...
TLDR: if the enum in question conforms to CodingKey (and possibly other protocols) the protocol hijacks the output of String(describing:), and there doesn't appear to be any way to get the case name.
In my case, I have a JSONRPC API I need to interface with that I don't control. It has a slightly asymmetric API shape between creation & update/read operations. I had to write my own custom serialization code for creation paths (do not want to have almost duplicate models for read vs create), and I really wanted to use my existing CodingKeys enums for my models since those are already designed to map between the model property names and the JSON key values. Since create operations aren't really local-performance-bound (they happen infrequently by user action) I can afford to use Mirror to manually compose the create JSON output based on the shape of the model, but to do that, I need to filter some properties, and transform others. I wanted to use the CodingKeys enum as a currency type for key representation since I already need them (my models need Codable conformance for API deserialization & local caching). Unfortunately, this does not work, because the CodingKey protocol hijacks the enum case description.
I really don't want to manually curate two enums for every model, with the only difference being CodingKey conformance... At this point, the most attractive solution I can come up with is to introduce Sourcery and use it to generate a second set of Keys
Ah, yes of course, any protocol that adds a var description: String { ... } will override the default representation.
Would this be an alternative to Sourcery for you?
enum E1 {
case foo
case bar
}
enum E2: CodingKey {
case baz
case qux
}
protocol P {}
extension P {
var theKey: String { String(describing: self) }
}
extension P where Self: CodingKey {
var theKey: String { self.stringValue }
}
extension E1: P {}
extension E2: P {}
print(E1.foo.theKey) // foo
print(E2.baz.theKey) // baz
Although I guess x.description and String(describing: x) should never be used for anything else than debugging / testing.
Thanks, but the problem with this approach is that as a CodingKey, the case name and the rawValue are not necessarily the same, and this distinction is important, as what I'm ultimately trying to achieve is a construction where the case names are matched against property labels discovered through Mirror, and the property's value assigned to the JSON object with the case's rawValue as the key. Your solution above only gives me access to the rawValue of the case, not the case name.
Or to put it another way, it's precisely the CodingKey's nature as effectively a type-safe "dictionary" of "Swift property name" to "external JSON key name" that I want to leverage in this case, which makes it particularly galling that the CodingKey implementation forces me into unwanted code duplication.
I wound up biting the bullet, introducing Sourcery to my project, and auto-generating a var caseName: String { get } for every enum conforming to my WritableModelKey protocol. This works, but makes me sad
I know that transforming between strings and types isn't considered "safe" but this still feels like something that should be a first-class operation, especially considering that String is the property label currency type in Mirror. There are just too many places where being able to access the caseName of an enum case programmatically is useful for odd cases like this.
I suppose the "clean" way to achieve this is to write my own JSON Encoder, but that's a really heavy solution to something that I can achieve with a 20-line function using Mirror
It also wouldn't help at all with other use-cases that have been mentioned.
I don't like to advertise, but a user of a library I'm working on had this exact issue. While I agree that the standard library could probably vend it's own way to give this information to users, I have an interim solution here: Accessing name of enum with a raw value · Issue #9 · Azoy/Echo · GitHub. (I believe everything in this solution is ABI stable on Darwin platforms.) This just looks at the enum metadata to get it's name.
I wish more people knew this. I’ve seen so many examples of people using enum raw-values on huuuuuge enums, thinking that the conversion was free, when it isn’t.
The inheritance-like syntax certainly doesn’t help the issue. It’s one of my biggest pet peeves in this language.