There's no way to channel a fail-able initializer to a throwing one, is there?

Got a fail-able initializer, i.e. init?.

Want to add a throwable one, for Decodable.

When you have a fail-able initializer, and use it in a sibling initializer call, you can use ! to crash the program if the second initializer is not failable, or leave no suffix if the second initializer is also failable, to propagate the fail-ness. But what if you want the second initializer to be nonfailable and convert the fact that the first initializer returned nil with a custom Error, or otherwise eat the error?

I didn't see any example in the Swift guide that covers this. And my naïve attempt to assign the result of self.init to a variable so I can check if it's nil didn't work. The error was:

Initializer delegation ('self.init') cannot be nested in another statement

Hmm...

Here's an awkward workaround:

extension RingBuffer: Decodable where Element: Decodable {

    public init(from decoder: Decoder) throws {
        let values = try decoder.singleValueContainer()
        guard let self0 = RingBuffer(try values.decode(type(of: elements))) else { throw DecodingError.dataCorruptedError(in: values, debugDescription: "Elements was unexpectedly empty") }
        self = self0
    }

}

Was this the imagined resolution for this case, or did the planners of the initializer feature straight up forget this combination? (I'm feeling the latter.) Has anyone done a bug on this? (A quick look didn't find anything.) But, I can't think of a syntax that wouldn't be only slightly less awkward:

extension RingBuffer: Decodable where Element: Decodable {

    public init(from decoder: Decoder) throws {
        self.init(try values.decode(type(of: elements))) ?? throw DecodingError.dataCorruptedError(in: values, debugDescription: "Elements was unexpectedly empty")
    }

}

I guess a bug name could be: "An initializer that delegates to a failable initializer has no way to check if the inner call failed."

2 Likes

It's less about forgetting this combination and more about not having an answer in the first place. You can run into the same issue just with optional constructors if you want to do some sort of cleanup work:

struct Foo {
  init?(_ x: Int) { … }
  init?(specially x: Int) {
    print("about to delegate")
    guard self.init(x) else { // ???
      print("delegation failed")
      return nil
    }
  }
}

You can fake this particular one with defer, but it's pretty bad:

  init?(specially x: Int) {
    print("about to delegate")
    var success = false
    defer { if !success { print("failed") } }
    self.init(x)
    success = true
  }

Your solution of assigning to self is a reasonable and efficient answer for value types, but doesn't handle classes. It'd be reasonable to support it with guard somehow—and I pick guard specifically because it enforces that the else block exit.

2 Likes

Bug SR-9618, "An initializer that delegates to a failable initializer has no way to check if the inner call failed.", has been created.

5 Likes