Type Inference with __auto_type

Type inference is a very common feature in today’s modern languages like Swift, Kotlin etc. Fortunately, a similar effect can be implemented in plain C or Objective-C land with the __auto_type C extension that is available since Xcode 8.

For a while now I’ve been using the following macros that are build on top of __auto_type.

#define let __auto_type const
#define var __auto_type

Long type signatures

Type signatures can be very verbose. For example let’s take the following code.

NSDictionary<NSString *, NSDictionary<NSIndexPath *, ASCollectionElement *> *> *supplementElementDict = [elementMap supplementaryElements];

This is quite a long type signature to write out every time you want to declare a variable for the supplementaryElements return value. With __auto_type this will be become much shorter — and it’s still obvious what supplementElementDict is.

let supplementElementDict = [elementMap supplementaryElements];

Constants with let

One benefit of Swift is that let is the most used and convenient way to declare variables constant. Meanwhile const also exists in C and Objective-C, but almost no one really uses it:

NSString *const name = @"Michael";

With __auto_type this can be written way more readable with getting all the benefits:

let name = @"Michael";

Type safety for “light” generics

Often if using a method that returns some object that supports light generics like an NSArray or NSDictionary you have to explicitly declare the return type as well as the generic attribution. Let’s for example use this code:

// Takes in an NSArray<NSString *> *
static void processSomePeople(NSArray<NSString *> *peoples) {
NSCParameterAssert([peoples isKindOfClass:[NSArray class]]);
for (NSString *people in peoples) {
NSCAssert([people isKindOfClass:[NSString class]], @"Should be a NSString");
// Do some more with people
}
}
// Returns NSArray<NSNumber *> *
static NSArray<NSNumber *> *somePeopleNumbers() {
return @[@1, @2, @3];
}

__auto_type will automatically infer the light generics returned from somePeopleNumbers() . The following code will emit a warning.

let people = somePeopleNumbers(); // Returns a NSArray<NSNumber *> *
processSomePeople(people); // Takes a NSArray<NSString *> *

If you don’t declare the generic specifically, the following code will not emit a warning at compile time as the generic type of the NSArray is not explicitly declared.

NSArray *people = somePeopleNumbers(); // Return type is NSArray<NSNumber *>
processSomePeople(people); // Will take in a NSArray<NSString *>

Inline block type inference

If you ever declare a block inline you know the pain to write all of the type signature. When using __auto_type the block type will be inferred automatically and it will look way more familiar to assigning a variable in other types.

// Block variable signature
void (^block)(id, NSUInteger, BOOL *) = ^(id obj, NSUInteger idx, BOOL *stop) {
// Do something
};
[array enumerateObjectsUsingBlock:block];
// Inferred block
let block = ^(id obj, NSUInteger idx, BOOL *stop) {
// Do something
};
[array enumerateObjectsUsingBlock:block];

Familiarity with Swift

Developers that are familiar with Swift’s let and var or C++’s auto / const auto will feel immediately at home using let and var . This is not a big selling point, but it makes it easier for developers that are jumping between different languages multiple times per day.

Problem: Infer nullability type

There is one problem with using __auto_type in your code. Currently __auto_type does not inherit nullability to inferred type. Further information is also found in this Apple radar: https://openradar.appspot.com/27062504.

The reason I think the advantages outweigh this disadvantage is the fact that you have to be explicitly writing down every nullable in your variable signature, to actually get a warning from the compiler for the nullability type. For example let’s take the following code.

// Takes in a nonnull NSString
static void removeUserFromCacheWithId:(NSString * _Nonnull greeting) {
// Try to remove the user from the cache
}
// Returns either a NSString or nil
static NSString * _Nullable currentUserId() {
if (arc4random() % 2 == 0) {
return @"100";
} else {
return nil;
}
}

The following code that is using the functions declared above will emit a compiler warning as a nullable pointer will be passed in into a function that takes in a nonnull pointer.

// This will emit a warning while compiling
removeUserFromCacheWithId(currentUserId());
// This will emit a warning while compiling too
NSString *_Nullable userId = currentUserId();
removeUserFromCacheWithId(userId);

As you can see within the example above, to get a compiler warning you have to explicitly declare the pointer of the return type of currentUserId() as nullable. If you don’t declare it as nullable or use __auto_type it will not show up as warning as the following code will demonstrate.

// All of them are NOT emitting a warning while compiling
// BUT - The latest Clang static analyzer emits:
// Warning: Nullable pointer is passed to a callee that
// requires a non-null argument for both cases
NSString *userId = currentUserId();
// or
let userId = currentUserId();
removeUserFromCacheWithId(userId);

One of the things to mention is, if you use the static analyzer it would catch this for you and show a warning for any case of the previous example.

Summary

I hope I was able to give you a couple of reasons why I think __auto_type is a good thing to use in your code, but finally you have to decide if the upsides outweighs the downsides and you would feel comfortable to use it within your code.