Errors in Objective-C

#import <Foundation/Foundation.h>

// By convention, errors are returned as the last parameter in Objective-C methods
// that can fail. We use NSError objects to represent errors.

NSError* f(int arg, int* result) {
    if (arg == 42) {
        // NSError's factory method to create a basic error with a given description
        return [NSError errorWithDomain:@"com.example" code:1 
                               userInfo:@{NSLocalizedDescriptionKey: @"can't work with 42"}];
    }
    
    // A nil value in the error position indicates that there was no error
    *result = arg + 3;
    return nil;
}

// Sentinel errors are typically defined as constant NSString values
NSString* const ErrOutOfTea = @"no more tea available";
NSString* const ErrPower = @"can't boil water";

NSError* makeTea(int arg) {
    if (arg == 2) {
        return [NSError errorWithDomain:@"com.example" code:2 
                               userInfo:@{NSLocalizedDescriptionKey: ErrOutOfTea}];
    } else if (arg == 4) {
        // In Objective-C, we can't directly wrap errors, but we can create a new error
        // that references the original one in its userInfo dictionary
        NSError* powerError = [NSError errorWithDomain:@"com.example" code:3 
                                              userInfo:@{NSLocalizedDescriptionKey: ErrPower}];
        return [NSError errorWithDomain:@"com.example" code:4 
                               userInfo:@{NSLocalizedDescriptionKey: @"making tea",
                                          NSUnderlyingErrorKey: powerError}];
    }
    return nil;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray* numbers = @[@7, @42];
        for (NSNumber* number in numbers) {
            int result;
            NSError* error = f([number intValue], &result);
            if (error) {
                NSLog(@"f failed: %@", error.localizedDescription);
            } else {
                NSLog(@"f worked: %d", result);
            }
        }
        
        for (int i = 0; i < 5; i++) {
            NSError* error = makeTea(i);
            if (error) {
                // In Objective-C, we check for specific errors by comparing their domain and code,
                // or by checking the error's localizedDescription
                if ([error.localizedDescription isEqualToString:ErrOutOfTea]) {
                    NSLog(@"We should buy new tea!");
                } else if ([error.localizedDescription isEqualToString:ErrPower]) {
                    NSLog(@"Now it is dark.");
                } else {
                    NSLog(@"unknown error: %@", error.localizedDescription);
                }
                continue;
            }
            NSLog(@"Tea is ready!");
        }
    }
    return 0;
}

In Objective-C, error handling is typically done through the use of NSError objects. Here’s how the concepts translate:

  1. Error return values: Instead of returning multiple values, Objective-C methods that can fail usually take an NSError** as their last parameter and return a boolean indicating success or failure.

  2. Creating errors: We use [NSError errorWithDomain:code:userInfo:] to create error objects, similar to errors.New in Go.

  3. Sentinel errors: These are often represented as constant NSString values in Objective-C.

  4. Error wrapping: Objective-C doesn’t have a direct equivalent to Go’s error wrapping, but we can achieve a similar effect by creating a new error and including the original error in its userInfo dictionary under the NSUnderlyingErrorKey key.

  5. Checking for specific errors: Instead of errors.Is, we typically compare error domains and codes, or check the error’s localizedDescription.

The structure of the program remains similar, demonstrating error creation, checking, and handling in an Objective-C context. Note that Objective-C uses manual memory management or Automatic Reference Counting (ARC), which is why we wrap the main code in an @autoreleasepool block.

To compile and run this Objective-C program:

$ clang -framework Foundation errors.m -o errors
$ ./errors
f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.

This example demonstrates how to work with errors in Objective-C, including creating, returning, and handling different types of errors.