Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between String and StaticString

Tags:

string

swift

I was browsing the docs, and I found StaticString. It states:

An simple string designed to represent text that is "knowable at compile-time".

I originally thought that String has the same behaviour as NSString, which is known at compile time, but it looks like that I was wrong. So my question is when should we use StaticString instead of a String, and is the only difference is that StaticString is known at compile-time?

One thing I found is

var a: String = "asdf" //"asdf"
var b: StaticString = "adsf" //{(Opaque Value), (Opaque Value), (Opaque Value)}

sizeofValue(a)  //24
sizeofValue(b)  //17

So it looks like StaticString has a little bit less memory footprint.

like image 667
Dániel Nagy Avatar asked Aug 31 '25 15:08

Dániel Nagy


2 Answers

It appears that StaticString can hold string literals. You can't assign a variable of type String to it, and it can't be mutated (with +=, for example).

"Knowable at compile time" doesn't mean that the value held by the variable will be determined at compile time, just that any value assigned to it is known at compile time.

Consider this example which does work:

var str: StaticString

for _ in 1...10 {
    switch arc4random_uniform(3) {
    case 0: str = "zero"
    case 1: str = "one"
    case 2: str = "two"
    default: str = "default"
    }
    print(str)
}

Any time you can give Swift more information about how a variable is to be used, it can optimize the code using it. By restricting a variable to StaticString, Swift knows the variable won't be mutated so it might be able to store it more efficiently, or access the individual characters more efficiently.

In fact, StaticString could be implemented with just an address pointer and a length. The address it points to is just the place in the static code where the string is defined. A StaticString doesn't need to be reference counted since it doesn't (need to) exist in the heap. It is neither allocated nor deallocated, so no reference count is needed.

"Knowable at compile time" is pretty strict. Even this doesn't work:

let str: StaticString = "hello " + "world"

which fails with error:

error: 'String' is not convertible to 'StaticString'

like image 107
vacawama Avatar answered Sep 10 '25 06:09

vacawama


StaticString is knowable at compile time. This can lead to optimizations. Example:

EDIT: This part doesn't work, see edit below

Suppose you have a function that calculates an Int for some String values for some constants that you define at compile time.

let someString = "Test"
let otherString = "Hello there"

func numberForString(string: String) -> Int {
    return string.stringValue.unicodeScalars.reduce(0) { $0 * 1 << 8 + Int($1.value) }
}

let some = numberForString(someString)
let other = numberForString(otherString)

Like this, the function would be executed with "Test" and "Hello there" when it really gets called in the program, when the app starts for example. Definitely at runtime. However if you change your function to take a StaticString

func numberForString(string: StaticString) -> Int {
    return string.stringValue.unicodeScalars.reduce(0) { $0 * 1 << 8 + Int($1.value) }
}

the compiler knows that the passed in StaticString is knowable at compile time, so guess what it does? It runs the function right at compile time (How awesome is that!). I once read an article about that, the author inspected the generated assembly and he actually found the already computed numbers.

As you can see this can be useful in some cases like the one mentioned, to not decrease runtime performance for stuff that can be done at compile time.

EDIT: Dániel Nagy and me had a conversation. The above example of mine doesn't work because the function stringValue of StaticString can't be known at compile time (because it returns a String). Here is a better example:

func countStatic(string: StaticString) -> Int {
    return string.byteSize    // Breakpoint here
}

func count(string: String) -> Int {
    return string.characters.count    // Breakpoint here
}

let staticString : StaticString = "static string"
let string : String = "string"


print(countStatic(staticString))
print(count(string))

In a release build only the second breakpoint gets triggered whereas if you change the first function to

func countStatic(string: StaticString) -> Int {
    return string.stringValue.characters.count    // Breakpoint here
}

both breakpoints get triggered.

Apparently there are some methods which can be done at compile time while other can't. I wonder how the compiler figures this out actually.

like image 36
Kametrixom Avatar answered Sep 10 '25 06:09

Kametrixom



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!