Value Types vs. Reference Types in Swift

Kaninchen jäger
5 min readDec 22, 2017

--

There are huge number of articles and blog posts talking about value types and reference types in Swift. Here I will remind you some of the key differences then I try to dig deeper into some of the fundamentals that I couldn’t find them all in one place such as memory allocation, passing by reference vs. passing by value, recursion, and inout.

Struct vs. Class

Structs (as well as enums and tuples) are value types, while classes are reference types. The first difference that distinguishes these two types is an important characteristic so-called implicit data sharing. Here is an example taken from Swift blog:

// Value type example
struct S { var data: Int = -1 }
var a = S()
var b = a // a is copied to b
a.data = 42 // Changes a, not b
print("\(a.data), \(b.data)") // prints "42, -1"
// Reference type example
class C { var data: Int = -1 }
var x = C()
var y = x // x is copied to y
x.data = 42 // changes the instance referred to by x (and y)
print("\(x.data), \(y.data)") // prints "42, 42"
// now we have two objects and they are both looking at the same spot in memory for their data

Reference Types hold a reference to the memory location which the object is stored. Assigning a reference variable to another creates a second copy of the reference and doesn’t copy the data which refers to the same location of the original value.

On the other side a value type instance keeps a unique copy of its data by holding the data within its own memory allocation. Copying a value type, copies the value and make them independent.

The key difference here is reference type instances share a single copy of the data, copying them copies the pointer which points to memory location that holds the real data.

In Swift all built-in types are value types. Not just the traditional Int or Bool, all Strings, Arrays and Dictionaries are value types as well.

Structs and Classes are very similar!

Keep in mind that they are very similar in terms of functionality. They both can have properties, methods, be extended, and conform to protocols. However struct cannot be inherited and cannot have deinitializer (if you are asking why, read the memory allocation section).

Pass by value vs. pass by reference

In Swift everything is passed by value (or copy) by default, so when you pass a value type you get a copy of the value. When you pass a reference type you get a copy of the reference (the copy of the reference still points to the same instance as the original reference).

However Swift does a lot of optimization wherever possible. Passing a value type does not necessarily imply copying. It doesn’t actually copy until there’s a mutation or the possibility of mutation for example let guarantees immutability so there’s no copy actually happens.

If you like to know more about passing by reference and passing by value, a beautiful discussion is waiting for you.

Which one is more efficient?

Although passing a reference type seems to be more efficient (because only a pointer is copied and passed), sometimes there might be cases that passing by reference is not necessarily faster.

If instead of passing 100 bytes of data you are passing an eight byte pointer, that’s faster. But if instead of passing four bytes of data you are passing an eight byte pointer, then you can’t really expect that to make things faster.

But, how can we pass by reference?

inout

Ok, now we know when an instance is passed to a function, it gets copied and it is the copy that is passed to the function. The inout keyword let us share a memory location and pass a reference to an instance (as a function argument) instead of passing by value.

The inout keyword lets us share a variable among recursive calls. The memory location of the depth argument is shared among all calls:

func recurse(depth: inout Int) {

depth += 1
if (depth < 10) {

recurse(depth: &depth) // recurse and increase depth
}
}

var depth = 0
recurse(depth: &depth)
print(depth) // 10

We must use the ampersand &, when passing arguments to a method that receives an inout argument. &depth means reference to depth (in C we can dereference that address using *, but in Swift we don’t need to dereference the parameter depth to change it the value).

Mutability

We know if we define a variable using let keyword, the compiler enforces immutability and it cannot be changed. But still it depends if it is a value type or a reference type.

Reference types

Since you let corresponds to the reference, the reference remains immutable and the reference must remain constant. But the object itself is mutable. In other words, you can’t change the instance of the constant references, but you can mutate the instance itself.

class C {
var value: Int = 10
}
let c = C()
c.value = 1 // This is ok

Value types

The let corresponds to the instance, so the instance remains immutable, and changing any of properties of the instance leads to a compile error, regardless whether a property is declared with let or var:

struct S {
var value: Int = 10
}
let s = S()
s.value = 1 // compiler error

Compiler Error: Cannot assign to property: ‘s’ is a ‘let’ constant

Immutable inout error

If you are using an inout immutable (let) value, the compiler won’t let the memory location of a constant be available:

func test(i: inout Int) {
i+=1
}

let x = 0
test(i: &x) // Cannot use let keyword with inout

Compiler Error: Cannot pass immutable value as inout argument: ‘x’ is a ‘let’ constant

Memory Allocation

Another difference between value types and reference types is that value types are stack allocated, which is more efficient than heap allocated memory which used by reference types.

Heap allocated vs. stack allocated memory

The stack is usually used for data that has a size known at compile time. For example, in many languages integers are stack allocated while strings are not.

The heap is used to store data which its size is unknown until runtime. Unlike the stack’s single (assembly) instruction to allocate space, the heap requires much more work. This is why heap allocated variables have their memory allocated at run time and accessing this memory is a bit slower. While using value types in Swift can be much more efficient than reference types because stack allocation is much cheaper than heap allocation. This makes access faster but comes with the drawback of having to drop support for inheritance.

In Swift value types such as ints, floats and structs are stack allocated while reference types such as classes and closures along with any other data that may grow in run-time like dynamic size arrays and strings are heap allocated.

Further Study

References and Values by Jon Skeet
Is Swift Pass By Value or Pass By Reference

inout Swift
Reference and Value Types in Swift
Memory Management in Rust and Swift
Difference between static memory allocation and dynamic memory allocation

--

--