Search code examples
swiftenumsinitializationtime-complexity

Swift failable rawValue initializer Complexity


I would like to know the time complexity for the enum failable initializer

Example

enum Operator: Character {
  case parenthesis = "("
  case brace = "{"
  case bracket = "["
}

// Time Complexity?
if let op = Operator(rawValue: "[") {
  ...
}

Solution

  • Note that this is very much an implementation detail, since the documentation doesn't say.

    With my Swift compiler version 5.3.2, I compiled your enum (with -Ounchecked) to a binary, and inspected that with Hopper Disassembler to see the code generated for the raw value initialiser. The initialiser practically does nothing but jumps straight into another procedure. That procedure looks something like this:

    // pseudo code generated by Hopper
    int _$s4main3FooO8rawValueACSgSJ_tcfCTf4nd_n(int arg0, int arg1, int arg2, int arg3, int arg4) {
        r12 = arg1;
        r15 = arg0;
        if ((arg0 == 0x28) && (r12 == 0xe100000000000000)) {
                swift_bridgeObjectRelease(r12);
                rbx = 0x0;
        }
        else {
                if ((Swift._stringCompareWithSmolCheck(0x28, 0xe100000000000000, r15, r12, 0x0) & 0x1) != 0x0) {
                        swift_bridgeObjectRelease(r12);
                        rbx = 0x0;
                }
                else {
                        if ((r15 == 0x7b) && (r12 == 0xe100000000000000)) {
                                swift_bridgeObjectRelease(r12);
                                rbx = 0x1;
                        }
                        else {
                                if ((Swift._stringCompareWithSmolCheck(0x7b, 0xe100000000000000, r15, r12, 0x0) & 0x1) != 0x0) {
                                        swift_bridgeObjectRelease(r12);
                                        rbx = 0x1;
                                }
                                else {
                                        if (r15 == 0x5b) {
                                                if (r12 == 0xe100000000000000) {
                                                        swift_bridgeObjectRelease(0xe100000000000000);
                                                        rbx = 0x2;
                                                }
                                                else {
                                                        rbx = Swift._stringCompareWithSmolCheck(0x5b, 0xe100000000000000, r15, r12, 0x0);
                                                        swift_bridgeObjectRelease(r12);
                                                        rbx = rbx & 0x1 ^ 0x3;
                                                }
                                        }
                                        else {
                                                rbx = Swift._stringCompareWithSmolCheck(0x5b, 0xe100000000000000, r15, r12, 0x0);
                                                swift_bridgeObjectRelease(r12);
                                                rbx = rbx & 0x1 ^ 0x3;
                                        }
                                }
                        }
                }
        }
        rax = rbx;
        return rax;
    }
    

    Notice the values 0x28, 0x7b and 0x5b. Those match the code points of (, { and [ respectively. It seems like the algorithm is basically "check if the argument matches the raw value of every case, in order".

    I tried adding more cases, and the if's got more nested, and I can see the code points of the characters that I added.

    So it seems like indeed, more cases will cause init(rawValue:) to take more time in the worst case (note that this isn't really what time complexity of an algorithm is about).

    However, if you use an Int as the raw value type, then it doesn't do all these complicated string comparisons, because it can use integers to represent your enum. If you also let the compiler infer the raw values, the initialiser will only do a range check. In this case, adding more cases (whose raw values are inferred) will not increase the time that the initialiser will take.

    At the end of the day though, I don't think this little increase in execution time is going to matter at all. You are not going to add millions of cases to your enum.