[swift-dev] Swift C enum Case Mapping

Ryan Lovelett swift-dev at ryan.lovelett.me
Tue Jan 12 21:18:53 CST 2016


I was hoping it would come through as light-hearted. That certainly was
my intent. Text is sometimes hard to convey intent through.

Responses inline.

On Tue, Jan 12, 2016, at 12:52 PM, Jordan Rose wrote:
> Not insolence at all. :-) It's important to justify or come up with good logic here, and doubly so on Linux where NS_ENUM/NS_OPTIONS aren't standard.
> 
> The usefulness of an enum is that you can guarantee that all cases are enumerated. But even that's not always true; people *do* add new elements to C enums without breaking binary compatibility. Or they have "private" cases that are ABI-compatible but do not correspond to a public declaration. So we'll *already* probably be dropping the idea of exhaustive switch over an imported enum unless we have some prior knowledge. (That still needs design.)

Is Swift effectively worried about the case where in v1 of some header
the enum is defined as

enum Foo {
  A, B
}

and in v2 they add `C` or remove `B`. Thus requiring all the Swift code
case statements to no longer compile because they are "not exhaustive".
If so that sounds like a good thing and not a bad thing to me.

Also, if I understand the "private" cases concern. Then I would contend
it's going to happen with the current import behavior. I think that a
"private" case in Swift's switch developers will have to exhaustively
account for that case even though in practice we know it will never
happen; thus leading to coding "dead" cases.

Consider a C enum that was imported as:

enum Foo {
  case PublicA, Private1, PublicB, Private2
}

the `switch` for the "private" might look something like this:

switch temp {
case .PublicA: fallthrough
case .PublicB: print("Expected")
case .Private1: fallthrough
case .Private2: fatalError("These cases are private")
}

Compare this with the current import behavior of a enum which we, human
in the loop, know to have no "private" cases.

typedef enum fe_type {
  FE_QPSK,
  FE_QAM,
  FE_OFDM,
  FE_ATSC
} fe_type_t;

switch type {
case FE_QPSK: fallthrough
case FE_QAM: fallthrough
case FE_OFDM: fallthrough
case FE_ATSC: fallthrough
default: fatalError("This switch is actually exhaustive. Though the
compiler doesn't know that.")
}

Seems like you end up in the same place. Or perhaps I completely missed
your point?

> 
> So then what's the cost of using an enum over a struct for the option set? I guess it's mostly the *implication* that you could switch over it, something you should never do with an option set. There are also option sets with predefined combinations of options:
> 
>> typedef NS_OPTIONS(NSUInteger, MyOptions) {
>>   MyOptionA = 1,
>>   MyOptionB = 2,
>>   MyOptionC = 4,
>>   MyOptionDefault = MyOptionA | MyOptionC,
>> };
> 
> …which would be weird for an enum. As you say, though, it's not a deal-breaker.
> 
> For your gist[https://gist.github.com/anonymous/232d62d77999f5eb27cd]  I'm not really sure what separates type 1 from type 2. Is it the explicit values assigned to the cases? The fact that all of the "type 2" values are powers of 2? And what makes type 3 a set of constants rather than just an enum or option set with a compatibility alias for its first element?

I guess I could have been a bit less cryptic. 😯

In my mind the fact that it has no explicit values assigned to the case
indicates that it is an "true" enum. The type 2 case is distinct from
the type 1 in that it supplies a value at all. The type 3 case is
distinct from the type 2 case in that it has a repeated value. Or put
more broadly, type 3 is anything that is more complicated than type 1 or
2.

> 
> I don't think we want to make these heuristics much more complicated—the more rules there are, the more likely people will be confused when they don't work. One advantage of the current heuristic is that it encourages people to make their headers better; a disadvantage is that not all headers are editable. Maybe we can fix *that* problem instead, for example by looking for annotations on redeclarations of the enum.
> 
> Jordan

After writing and thinking about this for a few days I went looking for
C headers that I didn't write or haven't been religiously using for 5
years to get a survey of how these things are done. The take-away: I
give up and now somewhat understand why the defaults are what they are.
I still don't _enjoy_ them mind you but it seems like that might be
beyond Swift's control. The way that enums are (ab)used is so crazy.

I think after having played that game redeclaration upon import or a
Swift API to convert a type through a protocol (is this even possible? I
love me some protocols.) or some other human-in-the-loop mechanism to
promote the imported enum to an first class enum once imported into to
Swift probably would end up being the most sane. It allows the C people
to continue to be, well C people, and the Swift people to write
idiomatic Swift code. After having thought about it for awhile that
seems like the most sane to me.

Can we get this implemented and merged tomorrow? 😈


More information about the swift-dev mailing list