[Pitch #2] Build-Time Constant Values

Property @const attribute

I don't understand the proposed differences between instance and type properties.

  • Why can only @const let be initialized with a compile-time expression?
  • Why can only @const static let properties be elided by the compiler?

Supported Types

  • Are boolean literals also needed for the SwiftPM example?
  • Are there any changes to the ExpressibleBy... protocols?

Protocol @const property requirement

  • Why don't the conforming types also need @const on their greeting properties?
1 Like

I love this. Could we paint this shed with @compileTime instead? Or @comptime as some folks call it.

4 Likes

@const let on instance members would be a more complex feature, because it would require propagation of the constness from the initializer (synthesized or otherwise) to the property. This is certainly an important piece of future work, and probably blends with many other future proposals covering const propagation, how cont works with functions, and the ability to declare arbitrary structs const so long as they meet certain criteria etc.

Probably, this proposal should add them.

I don't think so.

In general, Swift's tendency is to allow, but not to require, repetition of such things (see recent discussions on repetition of the primary associated type on inheriting protocols). Swift is not a language that favors being overly explicit (it strongly favors type inference, after all) except when it's important to avoid harm or misunderstanding. In this case it doesn't seem there's much of a case for that happening. If you fail to fulfill the requirement, or break it by accident when making changes later, the compiler will tell you straight away. But examples describing some harm that might arise could help make the counter case.

1 Like

To me, "propagation of the constness" implies constness should be attached to the type rather than the declaration.

I know propagating constness is out of scope for this pitch, but when it comes to decide on syntax and semantics it seems important to know how the other const-related features will plug into this.

Borrowing from some of my words from the last pitch:

4 Likes

Perhaps this could be an extension to @available:

  • @available(comptime) - available only at compile-time; assertion that something must be fully-evaluated prior to runtime (eg, "\(thing1) \(thing2)" : StaticString)
  • @available(runtime, comptime) - usable at runtime like usual, but also meets whatever the compile-time-ability rules are (eg, mutable Array for meta-computation)
  • @available(runtime) - the implicit default, spelled out explicitly

This would leave open to other varieties in future as domains expand (linker? load? etc). And then also allows things like @available(runtime) @available(swift 9.9, comptime).

2 Likes

my mental model of compile time is closer to frozen, inlined, or final.

Generally, I think many attempts to reuse existing keywords for new features threaten confusion for little obvious benefit. But in this case, the obvious new keyword is const, which itself carries some potentially misleading connotations. constexpr is a much closer semantic match, but it’s extremely tied to C++.

Of the keywords you’ve proposed reusing, frozen is the one I find least inaccurate. But it’s still not a perfect fit—if you encountered a @frozen struct for the first time after seeing a @frozen expression, you might reasonably conclude that all values of that struct are themselves computed at compile time and stored in the data segment of your binary.

I believe the main question that the parent had, one that I share, is whether the @const let greeting: String syntax (in a protocol) is a new syntax that this proposal would introduce. Specifically, the following code is currently rejected:

protocol Foo {
    let bar: Int
}

...with the message:

error: protocols cannot require properties to be immutable; declare read-only properties by using 'var' with a '{ get }' specifier

Would this be special-cased for @const, or am I misunderstanding?

1 Like

Yes, this would be new and specific to @const.

My interpretation of what allowing non-const let in protocols would mean is creating a protocol requirement that a property can be determined at run time to be any value, but once returned the first time, would always need to return the same value. It’s not clear whether this is a useful feature to add to Swift – maybe it’s useful but it would need a motivation. Purely for consistency does not seem like sufficient motivation to me.

3 Likes

Can't think of an application for it in my code but it's the sort of thing I might have used if Swift had been available 20 years ago, so I can see the use of it and it seems like a useful feature.

The language I was using 20 years ago was C or C++, and yes const there means something different. But I can't see there being any room for confusion as in Swift let is used when you would use const in C++. This is so fundamental to Swift that by the time anyone encounters @const they should not be thrown off by it.

My main concern with const is not that it would be confused with let, but that reading @const let name = "stackotter" really doesn't tell me very much, assuming that I haven't seen const before. I probably wouldn't associate that with build-time evaluation of expressions and values (especially if I came from a language where it means something else). But if it was @buildTime let name = "stackotter" I think that most people would have a much better first guess at what that means. 'Build-time' isn't necessarily the best phrase for this purpose, but it is certainly less cryptic than const imo.

Also, if build-time mutation of values was added (as some were considering earlier), constant could become a bit of a misnomer.


It wouldn't be the end of the world if @const was chosen, but we're in a position to choose a nice descriptive syntax for this new feature, and the more intuitive the syntax is, the easier and faster it is to learn.

17 Likes

I want to add to this by by borrowing some other words of mine from the last pitch:

15 Likes

I’m very excited to see Swift moving forward into this realm ^^

I do have to repeat something mentioned in previous threads do: I don’t like the use of “constant” for this.
Other people have already mentioned some reasons but I want to add, for me there is no relationship between ‘compile time’ and ‘constant’ from source perspective. If we look at runtime then yes, comptime = constant but for that we already have let.
I don’t think const = comptime because for me that’s a bit shortsighted. Now, if we are saying Swift won’t ever be able to run code at compile time then okey, but that seems quite limiting for a modern language.
I would love for it to eventually be able to run arbitrarily Swift code at comptime at which point saying something is const makes no sense. Nothing should stop me writing some code that decides some values based on environment vars.
From that pov calling this const is just reflecting a limitation of the current system.

8 Likes

I have a few questions to help me understand what is being proposed here:

In "Supported Types" section, it is mentioned that (literal) Array and Dictionary are supported. What is the runtime representation (memory layout) of the values of these types? If they are identical to their normal counterparts, then how are they constructed? Currently, an Array that is initialized with a literal is dynamically constructed at run time. What makes value of an Array that is initialized with such a literal a build-time value?

The thing I miss most about C++ is what const does that let doesn't, like having const parameter types that non-const values can be "upgraded" to, and const member functions that aren't allowed to change the object (sort of the opposite of Swift's mutating).

So my concern about calling this const is not that it's a holdover from C++, but that it's re-using the term to mean something different.

3 Likes

I think this is not a good analogy because IUO is a marker on the type itself, not the declaration. And I think, maybe this attribute that indicates some value is "known at compile time" belongs to the type of the value as well. IOU analogy also gives me a wild idea on how to mark these: How about we use # as a type suffix to indicate this? For example:

// Instead of:
@const let myCompiletime = "Known at compile time"
// How about:
let myCompiletime: String# = "Known at compile time"
// In the same spirit of 
let myIUO: String!

Which itself leads to a very different interpretation of the whole concept: A value of type String# would be an entirely new type. This new type is a pointer to a region in the loaded compiled image that represents the string value[1]. In this case, the target memory image will not match a runtime representation of String, but we will be able to create a runtime string using the bytes that we find by dereferencing String#. This also means that we have:

func takesCompileTimeKnown(_ arg: String#) { /* ... */ }

// A variable holding a value that is known at compile time:
var runtimeDecided: String# = "Default"

// EDIT: renamed the condition flag of the `if` statement to clarify
if someRuntimeDecidedCondition {
    runtimeDecided = "Other Compile-time Known Value"
}

takesCompileTimeKnown(runtimeDecided) // Valid

[1] In practice it can be a fixed relative offset in the compiled code at each use site if it is within the same compilation unit.

2 Likes

They are call const because unlike let (that are immutable and not really const), compile time constants are stored in read-only memory section. In this regard, I find it logic to call this concept const.

And it would still be perfectly logic to call that const, even if it is generated by running code. You can have a dynamic generator of constant values.

1 Like

If we were designing IUOs today, I expect we would add an attribute and not privilege it with a postfix ! sigil on the type. Such shorthand was essential in the early days of Swift when unannotated C/ObjC APIs were so common. But these days, it's been relegated to a feature that is on the brink of deprecation and doesn't really merit such sugar. As such I don't think IUOs are particularly a precedent to follow.

8 Likes

Sorry, I find that distinction between const and immutable rather arbitrary. Also as a regular swift user I don’t care one bit where the compiler puts these values in memory. Should I?

It also feels like I have to help out the compiler here with stuff I thought/hoped the compiler would be able to figure out. Having to add const feels like noise and too much overhead, sorry. Then again once it clicks I might love it to death.

To me let signals constant. A value that once set does not change during its lifetime. It seems for reasons I don’t fully follow it’s important to make the distinction between compile and runtime for constant/immutable values. Fine, but calling it const won.t help a beginner or regular user easily get to that point. Calling it e.g. buildtime, like others suggested, would make it much more clearer what’s going on.

I am self-taught and far removed from compiler development so what do I know. #grainOfSalt

6 Likes