Getting the name of a Swift enum value

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?

The Language Guide's section on Enumerations doesn't mention it.

enum Foo {
    case aaa
}

let f = Foo.aaa
String(describing: f) == "aaa"
"\(f)" == "aaa"
1 Like

What are you trying to do? Getting the case name by itself isn't useful, so what are you using the value for?

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.

I'd say that the "standard" is the CustomStringConvertible, or even rawValue.

1 Like

It's also useful for logging. I wouldn't say it "isn't useful".

2 Likes

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.

6 Likes

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 :pensive: 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 :sob:

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.

1 Like

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 :pensive:

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 :expressionless:

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.

You could probably ask the runtime to get the case name, but keep in mind things can change without notice, so use at your own risk:

@_silgen_name("swift_EnumCaseName")
func _getEnumCaseName<T>(_ value: T) -> UnsafePointer<CChar>?

func getEnumCaseName<T>(for value: T) -> String? {
    if let stringPtr = _getEnumCaseName(value) {
        return String(validatingUTF8: stringPtr)
    }
    return nil
}

enum Foo {
    case bar1
    case bar2(Int)
}

let b1 = Foo.bar1
let b2 = Foo.bar2(0)

print(getEnumCaseName(for: b1)) // bar1
print(getEnumCaseName(for: b2)) // bar2
2 Likes

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.

Just to satisfy my curiosity, how huge is “huuuuuge” :sweat_smile: and how large is this lookup penalty?

I don’t think I ever assumed it was free, but I also don’t have a good mental model of the relative expense either.