Static computed property vs static let constant?

This has probably been asked before, perhaps even by myself but I couldn't find it so:

What are the differences between c and d here:

struct S {
    static let c: Int = 1234
    static var d: Int { return 1234 }
}

?

6 Likes

What exactly do you mean by 'difference'? I'd say there is no difference in your particular example, but if you had Int.random() there instead then d will always return new values on each call.

If you meant to ask what this compiles to at low level, then I cannot answer that question.

3 Likes

For a proof that there's no difference in this specific case see Compiler Explorer
Both compile to the same code when optimizations are enabled.


static output.S.c.getter : Swift.Int:
        push    rbp
        mov     rbp, rsp
        mov     eax, 1234
        pop     rbp
        ret

static output.S.d.getter : Swift.Int:
        push    rbp
        mov     rbp, rsp
        mov     eax, 1234
        pop     rbp
        ret
10 Likes

Any difference at all, given the particular example (a constant, same return value every time it is called), so a very similar question is eg:

Why is Float.pi declared as a static computed property and not a static let constant?

Note that a protocol requirement like static var pi: Self { get } can be satisfied both by a static let constant and a static computed property.

Thanks! So how come the static-computed-property-way seems to be preferred in eg the standard library? It's not the shortest to write/read.

3 Likes

That is because it was probably easier to write a gyb file this way. I don't see any other reason for not making it a constant:

After optimisations, there's going to be no difference. Without optimisations, c's getter will use an unsafeMutableAddressor which would return a pointer to the variable's storage and use it to return the stored value.

Instead of returning a pre-defined value, if this was returning an instance of something (say, a UIView), then yes, there would actually be a difference.

1 Like

So eg this:

extension Point {
    static let zero = Point(0, 0)
}

should be preferred over this:

extension Point {
    static var zero: Point { return Point(0, 0) }
}

?

In a normal library, a let is an additional promise that the value will be the same every time, which might be important if the compiler can't see the body of the function. On the other hand, if the computation of the value has side effects, the compiler has to take care that it's only computed once.

In a "resilient" library (like the stdlib and overlays), let doesn't promise that outside of the library itself, so that it's a "safe" change to go from let to a computed var if necessary.

4 Likes

From the Swift users perspective I would say it's a matter of taste. I personally would prefer the former if I don't need a class variable for example:

extension UITableViewCell {
  class var identifier: String {
    return String(describing: self)
  }
}

First is returning Point(0, 0) which is stored in zero variable's storage. This uses unsafeMutableAddressor as I mentioned above.

The second is creating a new instance of Point(0, 0) each time.

Again, this difference only exists without optimisations enabled. After optimisations, both generate the same SIL.

Yes, unless it's a reference type that's being returned of course:

class C {
    init() {}
}

struct S {
    static let e: C = C()
    static var f: C { return C() }
}

print(S.e === S.e) // true
print(S.f === S.f) // false

EDIT:

And also, non-reference type cases like this:

struct R {
    let v: Double
    init() { self.v = Double.random(in: 0 ..< 1) }
}

struct S {
    static let g: R = R()
    static var h: R { return R() }
}

print(S.g.v == S.g.v) // true
print(S.h.v == S.h.v) // false
3 Likes

That's correct

Nope:

struct A { static let a:Int = 89 }
struct B { static var b:Int { return 89 } }

print(MemoryLayout<A>.size, MemoryLayout<B>.size) // 0 0
2 Likes

oh i completely misread the title of this thread lol

1 Like

Isn't let also a promise that the value is computed only once?

Does the stdlib use a special modifier to not promise this; is there a precedent?

That's the point. let makes that promise, a computed var does not. Sometimes that promise is trivial to keep, but sometimes it means an extra runtime check.

It's part of being a resilient library, i.e. one that's expected to evolve over time while keeping the same ABI. We're hoping to talk more about making that a general feature within the Swift 5.1 timeframe, but the short answer is "-enable-resilience". That has a lot more effects besides just treating let as if it might change, though.

3 Likes

Ah, sorry, I was under the impression the talk was about an allowance to override lets iff they come from resilient libraries.

There's a difference and could make a huge Impact. In case the static property depends on a result of a function and we used static let, It will call the function one time on the first time and save Its value as long the property is alive.

Example: If we used localization in the app and a static variable would retrieve a localized value, If used static let It would call It once, In this case, we could change the language but the variable still refers to the old value. So the solution here should use Computed static

public enum NyEnum {
    public static let intField1: Int = 1
    public static var intField2: Int { 1 }
}

I am using a -O optimization flag.
I have two static props let and var.
They have the same assembly instruction except the unsafeMutableAddressor part.
Why do the swift compiler generate an unsafeMutableAddressor, for primitive types like Int.