Wrapping an Error in an enum

Hi,

I have a custom Error called MyError which is wrapped in an enum Response

Problem:

  • When I was trying to print the localizedDescription of MyError, it doesn't use my implementation of localizedDescription unless I cast it to MyError.
  • I was hoping to display the error on the UI

Question:

  • Am I missing something ? Should I be implementing some other protocol ?

Code:

enum MyError : Error {
    
    case redError

    var localizedDescription: String {
        
        let message : String
        
        switch self {
        case .redError:
            message = "Red Error"
        }
        
        return message
    }
}

enum Response {
    
    case good
    case failed(Error)
}


let r1 = Response.failed(MyError.redError)

func f1(response: Response) -> String {
    
    let message : String
    
    switch response {
        
    case .failed(let error):
        message = error.localizedDescription
        
    case .good:
        message = "good"
    }
    
    return message
}

func f2(response: Response) -> String {
    
    let message : String
    
    switch response {
        
    //Need to cast it to get it to print the correct error
    case .failed(let myError as MyError):
        message = myError.localizedDescription
    
    case .failed(let error):
        message = error.localizedDescription
        
    case .good:
        message = "good"
    }
    
    return message
}

print("f1 - \(f1(response: r1))")
print("f2 - \(f2(response: r1))")

Output:

f1 - The operation couldn’t be completed. (__lldb_expr_147.MyError error 0.)
f2 - Red Error

You have to conform to LocalizedError and implement errorDescription: String? instead. I found that confusing, too.

3 Likes

Awesome, thank you so much, was breaking my head over it.

1 Like

It works great but I have a small doubt.

Doubt:
I accidentally typed var errorDescription: String (omitted String Optional) and it compiled ok, except that in run time I was getting the old message as in f1

enum MyError : LocalizedError {
    
    case redError
    
   //Accidentally missed the String Optional but this compiles ok
   //Was confused how the compiler allowed a protocol conformance in spite of missing 
   //the String Optional, or is it a Objective-C protocol with optional methods ?
    var errorDescription: String {
        
        let message : String
        
        switch self {
        case .redError:
            message = "Red Error"
        }
        
        return message
    }
}

Questions:

  • Based on the documentation it looked like a Swift Protocol (I could be wrong), so was a bit confused as to how it was allowed.

  • Or is it a Objective-C protocol with an optional method and therefore allowed it ?

If this is a common issue, imho it worth considering to only have a single Error-protocol with default implementations.

3 Likes

Thanks @Tino, yeah it is a bit confusing, got to be a touch careful while implementing it as it is not so obvious.

Having a single Error protocol would unfortunately be a layering violation, since LocalizedError is a Foundation feature.

Thanks @jrose, there are a couple of things that confused me:

Thanks to @zoul reply, now I understand I was using the wrong Protocol and now I have implemented the corrected one LocalizedError

Explained below is what caused my confusion.

Please note: My knowledge on this is very limited and my assumptions could be totally wrong.

1. Indirection

In the original example, if I printed the following things would work fine:

let e1 : Error = MyError.redError
print(e1) //prints redError

However when I wrapped it inside Response enum it kind of lost it's ability to pick my implementation.

I thought since it was a Protocol, it wouldn't need to know the underlying concrete type conforming to Error to pick the correct implementation.

So f1 doesn't pick my implementation and I had to cast it in f2 to make it pick the correct implementation.

On the first level it works, but when there it is wrapped then it doesn't pick it.

2. Looks like a swift protocol (I could be wrong)

On the surface (command clicking to see implementation) Error looks like a Swift protocol.

  • Error doesn't conform to any other protocols and doesn't have any requirements

  • Not sure if there is a default implementation which is tripping me, but I would have thought when there is a type conforms to it, the conforming type's implementation would be picked over the default implementation.

  • If it is a Swift protocol, is it a bug or am I missing something ?

A protocol value can only use dynamic dispatch for requirements declared in the protocol; other than that, it has to pick an implementation to use at compile time. For more information on this, you can check out the talk that Doug and Ben gave last week at WWDC, Swift Generics.

Error actually might be a little special because it's a protocol value that's bridged to the Foundation NSError type, something that goes outside the way Swift implements bridging for types like String or Array. Some of that might be affecting the behavior here too, even without importing Foundation. We try to keep that minimal, though.

2 Likes

Thanks @jrose for clarifying, I will watch the video on Swift Generics and like you said may be the bridging has something to do with it.