3

In Swift, the init(rawValue:) system ensures that casting an Int to an enum either results in a valid enum case or nil.

There is no such safety in Objective-C, where an invalid enum member can be created by casting a non-member "rawValue".

typedef NS_ENUM(NSInteger, ExampleEnum) {
    first = 0,
    second,
    third,
};

+ (NSString *)stringForCase:(ExampleEnum)enumCase {
    switch (enumCase) {
        case first:  return @"first";
        case second: return @"second";
        case third:  return @"third";
    }
}

+ (void)testEnum {
    ExampleEnum invalidCase = (ExampleEnum)3; // this "rawValue" is out of bounds
    NSString *string = [self stringForCase:invalidCase]; // nil
}

When switching on an enum, the compiler will warn you if an enum case is not handled:

Enumeration value 'third' not handled in switch

But once all cases have been handled, there is no similar warning that a "default" case is still possible for invalid members of the enum.

What is the behavior in such a situation? The NSString method seems to return nil, and no crash is observed. But there is no return from that method. Is a return nil generated automatically, and under what circumstances?

Note that code statements after the "exhaustive" switch do not result in the warning that would normally be generated:

Code will never be executed

pkamb
  • 33,281
  • 23
  • 160
  • 191
  • 1
    Tangential, but you might be interested in this blog post (it's a quick read): https://owensd.io/2014/10/09/building-swift-style-enums-in-objc-part-3/ – jscs Jan 23 '19 at 02:30
  • 1
    "But what if some hack comes along and throws an uninitialized int into my beautiful function?" - https://softwareengineering.stackexchange.com/questions/179269/why-does-clang-llvm-warn-me-about-using-default-in-a-switch-statement-where-all – pkamb Jan 23 '19 at 22:26
  • Aside: FWIW, when thinking about enums in C (or ObjC), unlike the enums in loads of other languages, they're not really conceptually "special" types-- think of enums as just being syntactic sugar for integers with *some* useful compiler warnings around their usage, but other pitfalls. Using some random integer for the value that's not in the defined enum set is not so much "invalid" as it is just badly written (or designed) code. Enums are kind of a pain in C because of this setup and pretty much every later language (incl Swift) made enums more first-class and useful. – Ben Zotto Jan 26 '19 at 18:21

1 Answers1

4

TL;DR, if none of the cases match, the function does return control to the caller, but the return value is undefined.

The C/ObjC return statement fundamentally does two things. It causes its value to be put into a specific place, so that the caller knows where to look for it. (The place being defined by the platform/language ABI.) Then it moves control back to the calling function (by popping an address off the stack and jumping to it).

In this case, control is going to walk right past the end of the switch, and no return statment will be executed. The compiler does, however, emit a jump for the end of the method.

(In fact -- I'm not great at reading assembler, but -- the debug-annotated assembler that I see Xcode 10.1 creating for your code funnels all the switch cases to a single exit point for the method. And that exit point is also reached if none of the case comparisons succeed.)

But we get to that jump without having put any value into the return register, which means that it's the same as any other uninitialized value -- garbage. The fact that you're reliably getting nil is likely due to either or both of the simplicity of your test program and building in a debug configuration.

jscs
  • 63,694
  • 13
  • 151
  • 195
  • Great to know about he undefined nature of the `nil` result I'm getting. Has this exact topic been covered/written up elsewhere, that you're aware of? – pkamb Jan 23 '19 at 22:14
  • Not sure honestly, but this is straight C behavior; there's nothing specific to ObjC in this situation, so it's probably in the C standard somewhere. – jscs Jan 23 '19 at 22:21
  • 1
    Actually, now that I think of it, there's a chance that ARC is guaranteeing the `nil` because the return type is an object... (as it does for uninitialized object pointers). Not sure about that; I'd have to spend some time looking at the ARC spec. – jscs Jan 23 '19 at 22:24
  • I found a hint in the ARC doc that this might be happening, but I'm uncertain. I'll take another look at the assembler output and see if I can figure anything out. – jscs Jan 25 '19 at 17:31