Logging errors in Swift

Error reporting and handling is tricky. In C, it's done by returning and examining error codes, which is very error prone since it's easy to forget to check for the error and it's often difficult to decipher what the error code means. Modern languages use exceptions, and some form of try / catch to move control from the place where an error occurs to where the code can deal with it. This is much more convenient, but in many cases there are errors that don't need any special handling. What matters to the rest of the code is that an error occurred, and the code needs to do something else. For example, if a function returns a value but can also throw an exception, the caller may not care what kind of exception it is and just do something else. It could be something like this:

guard let input = try? AVCaptureDeviceInput(device: captureDevice)  
else { return }  

If the input cannot be created, there is nothing else to do. Swift has the try? construct to make the code more concise, but if we use it, we lose any information about why the input could not be created, which should be really useful during development. If we decide to log the error, the code would become something like this:

do {  
   let input = try AVCaptureDeviceInput(device: captureDevice)
}
catch {  
   print(error)
   return
}

which is much more verbose and becomes even more so if we want to replace try? with do/catch in a larger expression. This makes it very tempting to just use try? and not bother with the error or to even design APIs to return optional values instead of throwing errors. But the error may give a critical insight into problems that may happen after the error, so it would be great to have something expressive like try? while still logging those errors. Here is one way to do it:

func maybe<T>(_ arg: @autoclosure () throws -> T) -> T? {  
    do {
        return try arg()
    }
    catch {
        print(error) // could be something else
        return nil
    }
}

With this syntactic sugar, the original code would become

guard  
   let input = maybe(try AVCaptureDeviceInput(device: captureDevice)) 
else { return }  

which is very close to what we had with try?, but prints all the errors.