Facets of Swift, Part 4: Functions

Tammo Freese
Swift Programming
Published in
16 min readAug 17, 2014

Facets of Swift is a series of articles on various aspects of the Swift programming language. This fourth article is about functions. I wanted to cover methods as well, but it turned out that functions are such a broad topic they deserved an article of their own.

Anatomy of a Function Declaration

A function declaration in Swift has the following format:

func functionName(parameters) -> returnType {
// statements
}

To understand return types and parameters, it is helpful to know about tuples and tuple types in Swift. If you have read the second article of this series on tuples, great! You’re all set, and you can skip the next section. If you haven’t read the second article or you would like a refresher, just read on.

Refresher: Tuples and Tuple Types

Swift allows us to group two or more items in a tuple by putting the values in parentheses. We can access the elements of a tuple by their index:

var result = (200, "OK", true)
println(result.1) // Prints "OK"

We can optionally name some or all of the elements of the tuple. Named elements can be either accessed by index, or by their name:

var result = (code: 200, name: "OK", hasBody: true)
println(result.name) // Prints "OK"
println(result.1) // Prints "OK"

Tuple types are built by putting two or more types in parentheses, optionally giving them names. That beautifully mirrors how tuples are defined. When assigning tuples, the types of the elements must match. Furthermore, when names are given, the names must match:

var result: (code: Int, String)
result = (code: 200, “OK”) // OK (no name mismatches)
result = (200, name: “OK”) // OK (no name mismatches)
result = (200, “OK”) // OK (no name mismatches)
result = (statusCode: 200, “OK”) // COMPILER ERROR (name mismatch)

There is also a tuple with no elements, the empty tuple with the empty tuple type.

var empty: () = ()

In the previous example, we declared the type explicitly as the compiler warns if the empty tuple type is inferred. The type Void is a type alias for the empty tuple type ():

var empty: Void = ()

For single values and types, parentheses are used to group expressions only: T, (T), ((T)), and so on all refer to the same type. In the following code snippet, foo has type Int:

let foo: ((Int)) = ((42))

Of course, the parentheses are not needed and the type can be inferred, so we can write shorter code instead:

let foo = 42

Return Types

After a short recap of what tuples are, we can compare the various possible return types in Swift. The return value of a function can be any single type, or any tuple type.

Returning Nothing

We can declare that a function returns nothing by using the empty tuple type:

func foo() -> () {
}

If we prefer, we can write Void instead of ():

func foo() -> Void {
}

The empty tuple type is the default return type for functions, so we can omit it to improve readability:

func foo() {
}

Having Void as default return type for a function is a better choice than in C, where the default return value is int:

foo() {
return 42;
}

Why is that a better choice? Specifying nothing as a return type means that you get the closest thing to nothing there is in Swift, and not a number.

A Single Return Type

We can declare that a function has a single return type:

func foo() -> Int {
return 42
}

I assume most of the Swift code we write will use either no return type, or a single return type. In C and Objective-C, we only have those two choices. The designers of Swift however decided to allow more.

Two or More Return Types

In Swift, we can use tuples to return more than one element from a function. We use the tuple type as return type, and return a tuple:

func okStatus() -> (code: Int, name: String, hasBody: Bool) {
return (200, "OK", true)
}
let status = okStatus()
println(status.name) // Prints "OK"

To summarize: In Swift, a function’s return type is the empty tuple type by default. We can also use a single type, or a tuple type with two or more (optionally named) elements as return type.

Now that we know about return types, let’s have a look at parameters.

Parameters

Parameters offer even more possibilities than the return type. First, we’ll have a look at the parameter list itself. Then, we cover the three kinds of parameter passing, followed by explaining three special parameter kinds.

No Parameters

We can specify an empty parameter list by leaving the space between the parentheses empty. No surprises here:

func foo() {
}
foo(42) // COMPILER ERROR

That’s different in C, where empty parentheses are interpreted as an unspecified number of parameters:

void foo() {
}
foo(42) // NO COMPILER ERROR

Parameters for Objective-C blocks are handled in the same manner. The following code snippet compiles without errors:

void (^block)();
block = ^(int i) {
printf("%d\n", i);
};

block();
block(1);
block([NSObject new], 42);

To state that we don’t want to allow parameters to be passed in C or Objective-C, we need to put void between the parentheses:

void foo(void) {
}
// ...
foo(42); // COMPILER ERROR

As with the return type, Swift has a better default behavior than C/Objective-C: Specifying no parameters means having no parameters, and not any number of parameters. But of course, functions that actually accept parameters are way more interesting.

One or More Parameters

To accept one or more parameters, just put the names and types between the parentheses of the function declaration, and the values between the parentheses of the function call:

func foo(x: Int, y: Int, z: Int) {
}
foo(1, 2, 3)

Now that looks very similar to what we’re used to from C.

Parameter Passing

As mentioned in the previous article, by default a parameter in Swift is passed as a constant. That means its value can’t be changed inside the body of a function:

func foo(i: Int) {
i++ // COMPILER ERROR
}

If we want to, we can explicitly state that a parameter is a constant:

func foo(let i: Int) {
i++ // COMPILER ERROR
}

By defining the parameter as a variable, we can change it inside the function body:

func foo(var i: Int) {
i++
}

The changes won’t make it out of the function, though. If we would like to change the variable with which the function is called, we declare the parameter as an in-out parameter using the keyword inout. Then at the caller side, we have to pass in a variable which is prefixed with an ampersand:

func foo(inout i: Int) {
i++
}
var x = 1
foo(&x)
println(x)

So we have three kinds of parameter passing. There are three kinds of special parameters as well.

Special Kinds of Parameters

The first special type is the ignored parameter. If we don’t want to use the value inside the function, but still require it as a parameter, we can use an underscore instead of a variable name:

func foo(_: Int) {
}
foo(42) // OK
foo() // COMPILER ERROR

An ignored parameter frees us of having to come up with a name for a parameter we don’t use, like when we override a method and don’t use a given parameter, or implement a closure and don’t use a given parameter. Both cases will be covered in detail in later articles.

For plain functions, I don’t see much use for an ignored parameter. That’s quite different for the second special kind of parameters: By using the type T…, we can make a function accept a variable number of values of type T. Inside the function, the parameter is exposed as an array with type [T]. The following code will print the numbers from 1 to 4, each on a separate line:

func printNumbers(numbers: Int...) {
for number in numbers {
println(number)
}
}
printNumbers(1)
printNumbers()
printNumbers(2, 3, 4)

Such a parameter is called a variadic parameter. You may know variadic parameters from C and Objective-C, but maybe you never built a function with a variadic parameter because accessing the values was a bit of a pain. In Swift, a variadic parameter is very easy to use. I think we will see more of those.

The variadic parameter comes with a few strings attached. We may only use one per function. While that may seem as an unnecessary restriction, it prevents ambiguity and general code horror. The variadic parameter has to be the last in the list—a good design decision as it would feel really weird to have an optional number of parameters at the beginning, or in between other parameters. We can’t use inout on a variadic parameter: If we need to modify a list of values from our function, we can use an in-out array parameter instead. The documentation says nothing about using var on a variadic parameter, so we better avoid it. Besides, the current beta 5 compiler crashes if we try.

The third kind of special parameter is the parameter with default value. Imagine we write a helper function that prints a separator. This could be how the function looks like:

func printSeparator() {
// ...
}

Two problems here. First, while the function name suggests that a separator is printed, there is no hint on how the separator looks. Second, we could not change the look of the separator. Default parameters to the rescue:

func printSeparator(separatorCharacter: String = "-", count: Int = 79) {
// ...
}

Much better. Now the code suggests that the methods by default prints the “-” character 79 times. We also have the possibility to change those defaults:

printSeparator()                                    // (1)
printSeparator(separatorCharacter: "=") // (2)
printSeparator(count: 50) // (3)
printSeparator(separatorCharacter: "=", count: 50) // (4)
printSeparator(count: 50, separatorCharacter: "=") // (5)

We would now expect that (1) prints “-” 79 times, (2) prints “=” 79 times, (3) prints “-” 50 times, both (4) and (5) print “=” 50 times. Things to note here are that we can skip default parameters from the beginning and only override later defaults (3), and that we are free to order the defaults however we want (4), (5). The most obvious difference is: Required function parameters in Swift are unnamed by default, parameters with default values are named by default. We have to pass the arguments by name, the following code would not compile:

printSeparator("=")     // COMPILER ERROR
printSeparator("=", 50) // COMPILER ERROR

The term “parameter with default value is misleading. Both the Swift grammar, and the compiler allow a default expression:

func foo(number: UInt32 = arc4random()) {
println(number)
}
foo() // Prints a random number

Parameter Order

We can put parameters with default values in front of parameters without default values:

func foo(a: Int = 0, b: Int) {
}
foo(2)
foo(a: 1, 2)

Try to avoid such code though, it’s hard to read. Better put required parameters first, followed by parameters with default values:

func foo(b: Int, a: Int = 0) {
}
foo(2)
foo(2, a: 1)

Parameter Names

We’ve seen that Swift has named and unnamed parameters. That’s not the whole story though. Each parameter in Swift has an external name and a local name. The external name is used when calling the function, the local name is used inside the function. In the following code snippet latitude is the external name, and lat the local name:

func foo(latitude lat: Float) {
// ...
}
foo(latitude: 53.48)

If we don’t want to use an external name, we use an underscore instead; if we don’t want to use a local name, we use an underscore instead (which gives us an ignored parameter).

It would be quite annoying though if we would have to always write down external and local parameter. The Swift designers put defaults in place that help us to write shorter code. Whenever we miss an opportunity to write shorter code, the compiler warns us that our code is too long.

Naming Required Parameters

Coming from C, we are used to function parameters without external names. A plain function parameter in Swift does not have an external name by default as well. So instead of

func foo(_ lat: Float) {  // COMPILER WARNING
// ...
}
foo(53.48)

we can write

func foo(lat: Float) {
// ...
}
foo(53.48)

If we like, we have the option to use an external name:

func foo(latitude lat: Float) {
// ...
}
foo(latitude: 53.48)

If the external and internal name are the same, the compiler warns us that there is a shorter alternative:

func foo(lat lat: Float) {  // COMPILER WARNING
// ...
}
foo(lat: 53.48)

The shorter alternative is to put a hash symbol in front of the parameter name:

func foo(#lat: Float) {
// ...
}
foo(lat: 53.48)

Naming Parameters With Default Values

For a parameter with a default value, the default behavior is to use the same external and local name. So instead of

func foo(#lat: Float = 0.0) {  // COMPILER WARNING
// ...
}
foo(lat: 53.48)

we can write

func foo(lat: Float = 0.0) {
// ...
}
foo(lat: 53.48)

It is a good idea to name parameters with default values. If a function has more that one parameter with a default value, the names allow us to only change those parameters we want:

func foo(lat: Float = 0.0, lng: Float = 0.0) {
// ...
}
foo(lat: 53.48)
foo(lng: 9.95)

We can still get an externally unnamed parameter with default value by using an underscore as external name:

func foo(_ lat: Float = 0.0) {
println("Latitude: \(lat)")
}
foo(53.48)

Parameters with default values are a great way to keep compatibility when improving an existing API. We can add a new parameter with a default value.

// Before
func foo() {
// ...
}
// After
func foo(x: Int = 0) {
// ...
}

Old code still compiles without errors; new code can use the parameter to set another value than the default. Or we can add a default value to a preexisting parameter. Again, old code still compiles without errors, new code can call the function without the parameter.

// Before
func foo(x: Int) {
// ...
}
// After
func foo(_ x: Int = 0.0) {
// ...
}

Ignored Parameters

To ignore a parameter, we use the underscore as local name:

func foo(lat _: Float) {
}
foo(lat: 53.48)

If we don’t want to use an external name, we use an underscore as well:

func foo(_ _: Float) {  // COMPILER WARNING
}
foo(53.48)

The compiler warns us as there is a shorter alternative:

func foo(_: Float) {
}
foo(53.48)

Naming Parameters: Recommendation

When naming function parameters, we have lots of freedoms. Deviating from the defaults however can make our code harder to understand. Therefore we should try to use the default as much as possible.

Function Types

In C, we have function pointers which we can use to reference functions:

int sum(int a, int b) {
return a + b;
}
// ...
int (*foo)(int, int) = sum;
printf("%d\n", foo(1, 2));

The variable foo has a function pointer type mapping two int parameters to an int return value. In Objective-C, we have block variables which we can use to reference blocks:

int (^foo)(int, int) = ^(int a, int b) {
return a + b;
};
printf("%d\n", foo(1, 2));

The variable foo has a block pointer type mapping two int parameters to an int return value. In Swift, we have function types as well:

func sum(a: Int, b: Int) -> Int {
return a + b
}
var foo: (Int, Int) -> Int = sum
println(foo(1, 2))

The variable foo has a function type mapping two Int parameters to an Int return value, or more exact mapping a tuple of type (Int, Int) to the type Int.

So Pretty

Swift’s function types are a huge improvement over C and Objective-C. It’s immediately obvious that they are way more readable. Imagine you want a variable foo with a function type that maps a function from Int to Int to a function from Int to Int. That’s both easy to write down in the first try, and easy to read:

var foo: (Int -> Int) -> (Int -> Int)

In C, the same variable would look like this:

int (*(*foo)(int (*)(int)))(int);

While some of us may write this down correctly in the first try, reading it is always painful. Granted, we could improve things a bit with a typedef, but even then the C code is less readable than the Swift version:

typedef int (*IntToInt)(int);
IntToInt (*foo)(IntToInt);

Swift’s function types also unify functions and closures (pieces of code without a name comparable to Objective-C blocks): Both have function types, so a variable with a function type can either reference a function or a closure. In contrast, Objective-C’s blocks are different to C’s function pointers. Closures will be covered in a later article.

Type Inference

If the type of the function can be inferred from the function, we don’t need to write the type of a function down:

func sum(a: Int, b: Int) -> Int {
return a + b
}
var foo = sum
println(foo(1, 2))

The type can’t be inferred if the function is overloaded or uses generics. Then we have to write down the function type to specify which function we want. Here is an example where we get a lessThan function for Int parameters from the < operator:

let lessThan: (Int, Int) -> Bool = (<)
println(lessThan(1, 2)) // prints "true"

The parentheses around the < symbol are needed to make the compiler happy.

Function overloading, generics, and operators will be covered in later articles.

Void: Less Special Than in C

In C, there is no value for the type void, so you can’t declare a variable of void type:

void foo(void) {
}
// ...
void x = foo(); // COMPILER ERROR

Swift’s Void is less special than C’s void. It is a type alias for the empty tuple type (). So there is exactly one value for it, the empty tuple (). Because there is a value for the Void type, all functions in Swift have a return value! A function with return type Void simply returns the empty tuple:

func foo() {
}
var result: Void = foo()

The code even works without the type declaration on the variable result. The compiler only warns us we may not have intended to infer the empty tuple type:

func foo() {
}
var result = foo() // COMPILER WARNING

We can even return the empty tuple from our function:

func foo() {
return ()
}

An empty parameter list in Swift behaves just the same. A function foo() can be called with the empty tuple as parameter:

func foo() {
}
foo(())

What we should remember is: For Swift functions, Void is less special than in C: It is just a type with one valid value.

Function Types and Tuples

If you have read the bonus track of the second article of this series, you may remember that while Apple’s Swift book states that (Int) is Int, the other way round seems to be more accurate: All types behave like they are a 1-tuple. For example, the following code works just fine:

let x = 42
println(x.0)

We can also write down a named 1-tuple:

let x: (y: Int)  = 42
println(x.y)

That is wonderful because together with the handling of Void, it simplifies function types a lot. If we read a type T as 1-tuple (T) and ignore special parameter types, all functions in Swift map from an input tuple type to an output tuple type. No more special cases. Beautiful.

Of course, as Swift is a language still in beta, there are a couple of unexpected behaviors which are not that beautiful. As they may be confusing (and outdated pretty soon), I moved them to the last section as a bonus track. Feel free to skip that section, but be back for the next article of this series about methods!

Bonus Track: Parameter Misbehavior

There are two areas where the current (beta 5) Swift feels a bit weird: Handling of tuples as arguments, and special parameter types.

Tuples as Arguments

While a function’s parameters feel like a tuple type, the compiler sometimes needs to be convinced to accept a tuple. Here is some code I would expect to compile:

func foo(#a: Int) {}

let arg1 = (a: 42)
var arg2 = (a: 42)

foo((a: 42)) // COMPILER ERROR
foo(arg1) // COMPILER ERROR
foo(arg2) // COMPILER ERROR

We get compiler errors as Swift currently seems to not infer named 1-tuples. If we set a breakpoint, we see that arg1 and arg2 are of type Int, not (a: Int). Let’s try to help the compiler a bit:

func foo(#a: Int) {}

let arg1: (a: Int) = (a: 42)
var arg2: (a: Int) = (a: 42)

foo((a: 42) as (a: Int))
foo(arg1)
foo(arg2) // COMPILER ERROR

This compiles, except for the last line. Although the variable arg2 has the correct type, it is still not accepted by the compiler, while the constant arg1 is! To get the code to compile, we have to add yet another cast:

...
foo(arg2 as (a: Int))

In the future, Swift may change so that the casts are not needed anymore. The example illustrates an important point: Swift needs a named 1-tuple to represent the parameter type of a function with one named parameter. Here, the function type of foo is (a: Int) -> ().

The current compiler also gives us a bit of a hard time when using tuples with two or more elements as arguments. Here is an example I would expect to compile:

func foo1(a: Int, b: Int) {}
func foo2(a: (Int, Int)) {}

var arg1 = (42, 42)
var arg2 = 42

foo1(arg1) // COMPILER ERROR
foo1(arg2, arg2)
foo1(42, 42)
foo1((42, 42)) // COMPILER ERROR

foo2(arg1)
foo2(arg2, arg2) // COMPILER ERROR
foo2(42, 42) // COMPILER ERROR
foo2((42, 42))

Why should all this work? Well, both foo1 and foo2 have the same function type (Int, Int) -> (). Again, we get a couple of errors. Again, we can fix the code by adding casts:

func foo1(a: Int, b: Int) {}
func foo2(a: (Int, Int)) {}

var arg1 = (42, 42)
var arg2 = 42

foo1(arg1 as (Int, Int))
foo1(arg2, arg2)
foo1(42, 42)
foo1((42, 42) as (Int, Int))

foo2(arg1)
foo2(arg2 as Int, arg2 as Int)
foo2(42 as Int, 42 as Int)
foo2((42, 42))

Let’s hope that gets cleaned up before Swift 1.0!

Tuples and Special Parameter Types

A function type maps from an input type (parameter type) to an output type (return type). There are three special types of parameters: in-out parameters, variadic parameters, and parameters with default values. The current Swift beta 5 compiler does not handle them that well.

If we define a function with an in-out parameter and check its type in the debugger, we see an attribute @lvalue on the inout parameter type. For the variable bar in the following code snippet, the debugger reports a type of (@lvalue Int, Int) -> ():

func foo(inout a: Int, b: Int) {}
var bar = foo

However, currently we can’t use @lvalue in our code to build a tuple we could use to call foo or bar with, nor is it a type we can write down. If we want to call the function with a tuple, we have to split the arguments manually:

func foo(inout a: Int, b: Int) {
}

var arg = (42, 42)
foo(&arg.0, arg.1)

For variadic parameters, the beta 5 debugger reports them wrongly as the empty tuple type. For the variable bar in the following code snippet, the debugger reports a type of () -> ():

func foo(a: Int...) {}
var bar = foo

That’s obviously wrong, as the function accepts any number of Int arguments.

Parameters with default values simply disappear in the reported function type, as if there were no defaults. For the variable bar in the following code snippet, the debugger reports a type of (Int, b: Int) -> ():

func foo(a: Int, b: Int = 42) {}
var bar = foo

Maybe this is by intention and bar should only be callable with both parameters? The compiler does not gives us a good hint: bar(1) is neither compiled successfully nor with an error, but it crashes the compiler. Maybe the next Swift version gives us a better hint what’s expected. Whatever happens to bar, there is no way to completely express the function type of foo, as there is no way of writing down a tuple type with defaults.

These rough edges should not discourage us, though. Swift’s function and function types are already way more impressive than what we have in C and Objective-C.

There are lots of interesting topics on functions still to cover, but this article already got way too long. I’ll write about those in future articles. Be back for the next article of this series, which will cover methods!

--

--