Enum case and String interpolation is not very safe: can mistakenly interpolate the case when actually want case rawValue, is there anyway to prevent this?

enum Foo: String {
    case bar = "bar rawValue blah"
    case baz = "baz rawValue blah blah"
}

let v = Foo.bar

// this is a mistake, want .rawValue, how to prevent?
print("\(v)")   // bar

// want
print("\(v.rawValue)")   // bar rawValue blah

It appears Swift has some special magic to interpolate the case to result into the string of the case name. Anyway to tell Swift on the enum to not do this?

I think this should do it...

extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: Foo) {
        appendInterpolation(value.rawValue)
    }
}

let v = Foo.bar
print("\(v)")   // bar rawValue blah

You can implement CustomStringConvertible and have description return rawValue. That’s not the default because there’d be no way to get the other behavior if that’s what you wanted. (That’s a bit of a dodge in the answer; of course such a feature could be added to the language.)

3 Likes

The fact that the default string representation of an enum case is not rawValue is not unsafe. This behavior is not always what you want.

I don't mean this language feature is unsafe. I mean I can by mistake interpolate the case when I really want the rawValue and would like to prevent this mistake. @roosterboy actually provided a good solution:

extension String.StringInterpolation {
    mutating func appendInterpolation( _ value: Foo) {
        //appendInterpolation(value.rawValue)
        fatalError("Don't do this!")
    }
}

I think if we can annotate the function with something like this:

@disallowed

the compiler can catch this mistake at compile time!

extension StringInterpolationProtocol {
  @available(*, deprecated, message: "Did you mean to interpolate the “rawValue”.")
  mutating func appendInterpolation( _ value: Foo) {
    fatalError()
  }
}

I expected unavailable or obsoleted to upgrade the warning to an error, but neither seems to have any effect. :man_shrugging:

It does have an effect: The method is not called at all when it is annotated with @available(*, unavailable), whereas without this annotation, it is called.

Mark unavailable, what I'm seeing is as if the whole function doesn't exist and the interpolation actually call the built-in whatever thing it does, that's is: it result in bar printed out.

Whereas deprecated only show warning and the code compile. I would prefer to be able to mark the function as "not allowed" and show compile error if this function is called.

BTW, in iPad Playground deprecated don't show any warning. unavailable behave the same: it calls the builit-it one.

deprecated is good enough for now.