Proper way to define and print Errors

There seems to be a many ways to define an Error and and many ways to print the error. I've having trouble deciding what's the best way to do this. How has everyone been tackling this?

Here are the many ways I know to create and print errors:

As an Enum:

#1: Error

enum EnumError1: Error {
    case a
}

print(EnumError1.a) // a
print(EnumError1.a.localizedDescription) // The operation couldn’t be completed. (__lldb_expr_81.EnumError error 0.)

#2: Error and CustomStringConvertible

enum EnumError2: Error, CustomStringConvertible {
    case a
    var description: String {
        switch self {
        case .a: return "AAA"
        }
    }
}

print(EnumError2.a) // AAA
print(EnumError2.a.description) // AAA
print(EnumError2.a.localizedDescription) // The operation couldn’t be completed. (__lldb_expr_242.EnumError2 error 0.)

#3: Error and localizedDescription

enum EnumError3: Error {
    case a
    var localizedDescription: String { // <-- available due to NSError bridging
        switch self {
        case .a: return "AAA"
        }
    }
}

print(EnumError3.a) // a
print(EnumError3.a.localizedDescription) // AAA

#4: LocalizedError and localizedDescription

enum EnumError4: LocalizedError {
    case a
    var errorDescription: String? {
        switch self {
        case .a: return "AAA"
        }
    }
}

print(EnumError4.a) // a
print(EnumError4.a.localizedDescription) // AAA

As a struct:

#1: Error

struct StructError1: Error {
    let x: String
}

print(StructError1(x: "a")) // StructError1(x: "a")
print(StructError1(x: "a").localizedDescription) // The operation couldn’t be completed. (__lldb_expr_210.StructError1 error 1.)

#2: Error and CustomStringConvertible

struct StructError2: Error, CustomStringConvertible {
    let x: String
    var description: String {
        "StructError2(x: \(x))"
    }
}

print(StructError2(x: "a")) // StructError2(x: a)
print(StructError2(x: "a").description) // StructError2(x: a)
print(StructError2(x: "a").localizedDescription) // The operation couldn’t be completed. (__lldb_expr_231.StructError2 error 1.)

#3: Error and localizeDescription

struct StructError3: Error {
    let x: String
    var localizedDescription: String {
        "StructError3(x: \(x))"
    }
}

print(StructError3(x: "a")) // StructError3(x: "a")
print(StructError3(x: "a").localizedDescription) // StructError3(x: a)

#4: LocalizedError and localizedDescription

struct StructError4: LocalizedError {
    let x: String
    var errorDescription: String? {
        "StructError4(x: \(x))"
    }
}

print(StructError4(x: "a")) // StructError4(x: "a")
print(StructError4(x: "a").localizedDescription) // StructError4(x: a)

The above examples are the basic examples I am aware of. I prefer to print variables and description be called automatically, like most data types. I don't like having to special case errors, especially when it's so easy to just call print or include the variable in another string "Failure: (error)". Lately, I've been doing more struct errors to include richer data instead of enums with various associated values. My app does have a lot of the variations of the above and I don't like needing to remember what the type is in order to be able to print it properly.

3 Likes

Wondering the same thing. Which way is the go to way and for some reason in my project with Xcode 14.3.1 I've been logging errors via OSLog and error.localizedDescription and they always log fine without showing the The operation couldn’t be completed.