Runtime constants: Swift

In an earlier post, I reported that if you have a variable in C++ such that the compiler can determine it to be effectively constant… that is, in practice, we cannot change its value, then the C++ optimizing compiler will treat it as if it were an actual constant. The same thing is not true in a language like Go.

Some readers asked about Swift, Apple’s new language.

Let us consider a fair comparison:

fileprivate var length = 10

func sum(array : [Int]) -> Int {
    var answer = 0
    for i in 0..<length {
        answer = answer &+ array[i]
    }
    return answer
}

So we have a function that depends on a variable length that is very specifically not defined as a constant. Swift, like Go and C++, has a notion of a constant, that is, I could write let length = 10. However, I declared it with the var keyword which means that the compiler must assume that I can modify the value of length.

Yet, I defined length to be fileprivate which is the closest thing to C++’s static: it means that if the variable is accessed at all, it must be accessed within the current file (meaning the actual text file containing Swift code). This means that the Swift compiler can examine the current code contained in the file to check whether length is ever modified. In my case, it never is.

So what happens?

Like in C++, it gets compiled to a tight set of additions:

add rax, qword ptr [rdi + 32]
add rax, qword ptr [rdi + 48]
add rax, qword ptr [rdi + 56]
add rax, qword ptr [rdi + 64]
add rax, qword ptr [rdi + 72]
add rax, qword ptr [rdi + 80]
add rax, qword ptr [rdi + 88]
add rax, qword ptr [rdi + 96]
add rax, qword ptr [rdi + 104]

What this means is that the Swift compiler deduced correctly that length was a constant. You do have to turn the optimizations (-O), of course.

But is Swift safe?

Let us do something bad:

var x  = [1, 2, 3, 4, 5, 6, 7, 8, 9];

print(sum(array:x))

My array is too short. In C++, this would lead the sum function to read memory out of bound. If you compiled your code just right, you might get a warning, but the C++ language itself does not do anything to help you.

In Swift, you get the following:

$ swiftc -O mytest.swift
$ ./mytest
(crash)
$

That is, by default Swift relies on bound checking. This means that your program will crash if you try to access memory out-of-bound.

It is important to note that I do mean “crash”. It will not throw a nice exception. Your program just dies a horrible death.

Still, Swift can be used without bound checking:

$ swiftc -Ounchecked mytest.swift
$ ./mytest
45

I got “45” but you could possibly get different values.

That is, with normal optimization levels (typical of a release), you have bound checking and crashing programs. With the crazy optimization level (-Ounchecked), you get no safety.

So the Swift compiler is a state-of-the-art optimizing compiler, at least as far as deducing runtime constants. That’s not surprising, but it is nice to know.

4 thoughts on “Runtime constants: Swift”

  1. It is a design choice to make range checks a fatal error rather than a catchable exception. Those are meant for handling expected runtime failure conditions, not something that is quite clearly a programmer error.

    It is not a choice everyone will agree with. I am not sure I agree with it. But it is by design, not an oversight. Making something a catchable exception will affect the function signature (add “raises”) and any code that uses it.

    1. I understand the spirit. So if you have an application that manipulates your data, and it gets into a state that was not expected, what do you do?

      There is a wrong answer. In C and C++, you just do random shit. The program you are running could start deleting all your files. Who knows? I don’t think we want this anymore.

      Swift seems to be going with… let us crash hard.

      Is that good?

      1. Another way of saying the same thing is to note that Swift automatically puts ASSERT() on all array indexing operations.

        Now the arguments around assert, what is or isn’t worth asserting, assertions on debug vs release builds etc are all old and well-hashed. And definitely not settled conclusively one way or the other.

        Perhaps the most controversial thing Swift has done here is calling it a “crash” rather than “assertion failed – range check error”.

Leave a Reply

Your email address will not be published. Required fields are marked *