2

I am trying to make a generic failable initializer with optional parameter for RawRepresentable, basically this https://www.natashatherobot.com/swift-failable-enums-with-optionals/

There were a couple of methods proposed one of which is this (EDIT: fixed let in the second clause):

extension RawRepresentable {

    init?(rawValue optionalRawValue: RawValue?) {

        guard let rawValue = optionalRawValue, let value = Self(rawValue: rawValue) else { return nil }

        self = value
    }
} 

from here https://gist.github.com/okla/e5dd8fbb4e604dabcdc3

I have no idea if it was ever working on Swift 2 but I can't compile it on Swift 3. I get:

Command failed due to signal: Segmentation fault: 11

Is there a way to make it work?

P.S. I am aware of other approaches from the article and its comments.

EDIT: Fixed broken copy/pasted code.

user1264176
  • 1,118
  • 1
  • 9
  • 26
  • The compiler should never crash, so this is clearly a bug. It does however appear to be fixed in Swift 3.1 (available with Xcode 8.3 beta). – Hamish Feb 21 '17 at 19:46
  • [File a bug](http://bugs.swift.org) about the crash, of course — no code, however broken it might be, should crash the compiler. – rickster Feb 21 '17 at 20:17

3 Answers3

1

I just copied and pasted your code in a Playground, and the only error I get is that it's missing a let before assigning value in the guard statement.

Are you sure the segmentation fault is because of this extension?

This is the code that works for me (Xcode 8.2.1, Swift 3.0.2):

extension RawRepresentable {

    init?(rawValue optionalRawValue: RawValue?) {

        guard let rawValue = optionalRawValue, let value = Self(rawValue: rawValue) else { return nil }

        self = value
    }
}

-- EDIT --

After defining an enum with raw values I do get an error.

Adding this makes the Playground to crash:

enum Counter: Int {
    case one = 1, two, three, four, five
}

Fixed it by renaming the parameter in init? to be init?(optRawValue optionalRawValue: RawValue?). I guess the problem is that you are calling Self(rawValue: rawValue) inside init?, and the compiler does not know which one to use...

Marcos Crispino
  • 8,018
  • 5
  • 41
  • 59
  • I have tried to define `init?(rawExtValue rawValue : RawValue)` and use that to do `let value = Self(rawExtValue: rawValue)` but I still get the same error. As I stated in my question I am aware of other approaches but I would like to make it work using `init?(rawValue optionalRawValue: RawValue?) `. Or at least understand why compiler crashes. – user1264176 Feb 21 '17 at 17:32
  • See this question to get more details on why the compiler crashed: http://stackoverflow.com/questions/19014359/how-do-i-view-the-full-build-log-on-xcode5 – Dave Weston Feb 21 '17 at 18:50
1

Regardless of the problems with compiling this code or making it work as you'd like it to, I'd argue that you're trying to solve the underlying problem in the wrong way.

Trying to design an initializer like this is an anti-pattern: the design for Optionals in Swift encourages handling and resolving nullability questions at the earliest opportunity, not cascading failures so far that it's hard to recover their origins. If you have a function/initializer that returns nil if and only if it's passed nil, it just shouldn't accept nil in the first place and never return nil.

In Swift 3, you can keep the default rawValue initializer for your enum and resolve the issues from that original NatashaTheRobot post in either of a couple of different ways.

  1. Use a compound if (or guard) statement.

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let string = segue.identifier,
              let identifier = SegueIdentifier(rawValue: string) 
            else { return /* or fatalError or whatever for catching problems */ }
        switch identifier {
             // handle all the enum cases here
        }
    }
    
  2. Do it functional style with flatMap (twice), which executes a closure iff its input is not nil:

    segue.identifier.flatMap(SegueIdentifier.init).flatMap { identifier in
        switch identifier {
            // handle all cases
        }
    }
    

Either way, you're unwrapping both levels of optionality (whether the segue has an identifier, and whether that string is a valid raw value for your SegueIdentifier enum) before the switch, which means that the switch must handle all valid SegueIdentifier cases. In turn, that means you catch yourself if you later add a new SegueIdentifier case and don't handle it. (As opposed to switching on something that might be a valid SegueIdentifier or might be nil, which means you need a default case, which means you'll silently fail if there are new SegueIdentifiers you ought to be handling.)

rickster
  • 124,678
  • 26
  • 272
  • 326
  • Well, I parse a lot of JSON, which means that the overhead for checking each and every possible nil value is to big and it would just bring a code duplication. I'd argue that we have to be practical and would even go as far as saying that each failable initializer with one parameter should always accept optional argument. It is programmer's responsibility to check input if he wishes so. I do agree though, that if it fails only if the argument is nil it should not be failable at all. – user1264176 Feb 22 '17 at 09:04
1

had to add public to init and it compiles and passes tests

Apple Swift version 4.1.2 (swiftlang-902.0.54 clang-902.0.39.2) Target: x86_64-apple-darwin17.6.0

extension RawRepresentable {

    public init?(rawValue optionalRawValue: RawValue?) {

      guard let rawValue = optionalRawValue, let value = Self(rawValue: rawValue) else { return nil }

      self = value
    }
}