SE-0359: Build-Time Constant Values

+1

I’m a fan of this, and especially for the future directions. In particular, I look forward to compile time computed initializers, which might help out in embedded applications by reducing code size, and proving more opportunities for optimization.

I read through the entire proposal and followed the discussions.

And it simply pleases me that values can be locked down to a no-doubt-about-it unchangeable form.

3 Likes

I think @const parameters will quickly become a nuisance unless some level of inference is available in the same release. The need to write two separate methods will be troublesome, when most are essentially reconst anyway (by analogy to rethrows). It will annoy library authors to have to write double, and will annoy clients even more when one of the two was neglected.

But I like the eventual goal of being able to do @const zero = pow(e, i * pi) + 1 and have nothing right of the = survive into the binary.

6 Likes

Forgive me but would “i” also be a @const in your example? Would this compile in this case or complain i is not uniquely known at build time?

At its declaration elsewhere, i would likely need to be marked @const, yes. (Although we are in future direction territory, which would be open to debate at that time.)

I am missing Optionals in the list of const-able types.

I would expect this to come with build-time evaluation so life will be easier and we can quickly expand the usage. As for now the feature is still too "weak" to be utilized.

I still don’t quite like the idea of an attribute. Is there any reason that we cannot make it a part of the type system? That is, we can have a series of new types called const P (combined using existing any P rules), and, like how we’re using non-async functions as async, allow implicit transformation from const P to P?

Consider the following example:

@propertyWrapper
struct SpecialSerializationSauce {
  init(key: const String) {...}
}

// compiles because string literal is inferred as `const String`
struct Foo {
  @SpecialSerializationSauce(key: "title") 
  var someSpecialTitleProperty: String
}

P.S. If there’re any source-breaking issues, we can delay the const-prioritized inference to Swift 6.

1 Like

+1, I agree with the proposal as written.

Excited about this (I think I'm rather excited about future directions).
Not in-depth study but have been following this topic.

  • This is definitely one of the missing features of Swift.
  • I love how its expression is settled with @ attribution. Just like mentioned in the proposal, it'll allow more flexibility. Also feels Swifty.

Only concern I had was its identifier: const. It felt or read like @const const value ... or @let let value ..., I mean it's only named @const because it was not a reserved word in Swift yet.

Then this, in the proposal was convincing:

... the potential use of this attribute for various optimization purposes also lends itself to indicate the additional immutability guarantees on top of a plain let (which can be initialized with a dynamic value), ...

So with this concept, const is a good balance... I guess (at least, so far.).

Has this changed at all since the pitch?

1 Like

Strong -1.

IMO from having to maintain a large codebase with a mix of developer levels, this is going to turn out very very poorly.

Developers will see @const in the codebase and due to its use in other languages we will very shortly start seeing @const appended to all and every let variable possible.

This keyword is an open invitation to cargo cult programming. Without any particular reason, code reviewers will start saying "add a @const here", because surely it can only be a positive to mark something const. Eventually, all lets will be marked @const, and the sane engineer in the room will start to wonder what's the point of @const let x = 11 when the compiler should have already inferred that.

I strongly believe @const should only be permitted as part of an API requirement to prevent this from happening. And it will happen.

8 Likes

If I understand the proposal correctly, this "feature" adds no new capability to the language. It might, some day, allow for new capabilities, and I think it's best to hold off until those other proposals are ready.

Would the Core Team have accepted a proposal to add async and await keywords without any actual functionality or design for a concurrency system to use them with?

12 Likes

I find this proposal an important step for introducing more compile-time features in Swift.

I just disagree with the decision to not require ‘get’ in protocol declarations. While I understand that it’s somewhat verbose, const member requirements are also not a newly designed feature. All other protocol-member requirements need to have ‘get’, and I don’t think the proposal makes a strong enough case to break away from this pattern. Unless we plan to allow more members to be written without specifying the accessor, this decisions seems to is inconsistent for no good reason.

11 Likes

I think the proposal does a good job of outlining potential future directions, referencing quite popular features (like a constant, result-builder-based package manifest). There also seems to be a strong push by the Foundation team to validate URLs at compile time, so I’m confident this will not end up as a useless feature.

As I wrote, I believe this proposal, which does nothing on its own for the capabilities of the language, should be deferred until a useful feature which requires this capability is at least proposed. Perhaps even pitched.

I find this proposal premature and have reservations along the lines of @ebg's with respect to code evolution between acceptance and the emergence of those "future directions".

7 Likes

+1 for compile time

I agree with the worry that most declarations will become @const let thus this becoming verbose. I would recommend using val for declarations thought I think this can be done after once more of the compile time facilities is available.

Since we have regex literal in review. How could a regex string now be turned into a regex literal using this proposal?

1 Like

This proposal is self motivating. It brings Swift to feature parity with other languages and improves performance of applications by eliminating runtime cost of static variables.

1 Like

I don't believe this feature can actually deliver on those promises, though. Firstly - what has been proposed by the Foundation team is that URLs specified as string literals will fail at runtime rather than return an optional. So when you write:

let url = URL(string: "http://example.com")!
//                                         ^ ---- This

The force-unwrap is now implicit. That is it. No compile-time validation. You don't write the force-unwrap, but it's like an Array subscript in that it will trap at runtime and crash your app if it fails. The force unwrap is still there.

I mentioned this when the Foundation team posted their pitch - they are talking about this like it is compile-time validation, and people might be mistaken in to thinking that it is, but it isn't. It's a built-in force-unwrap and that can be a pretty significant difference.

Ergonomics of the recently-pitched Foundation.URL would benefit greatly from the ability to require the string argument to be compile-time constant. [...] While a type like StaticString may be used to require that the argument string must be static, which string is chosen can still be determined at runtime, e.g.:

URL(Bool.random() ? "https://valid.url.com" : "invalid url . com")

EDIT: I misunderstood this. It is a (minor) expressivity feature.

But this part doesn't make a huge amount of sense:

With evolving compile-time evaluation facilities, Swift may even gain an ability to perform compile-time validation of such URLs even though the user may never be able to express a fully compile-time constant Foundation.URL type because this type is a part of an ABI-stable SDK.

On the one hand, we can validate URL values at compile time even though it's part of an ABI-stable SDK, but on the other hand, we can't create "fully compile-time constants" because it's part of an ABI-stable SDK?

So... what about if we validate the URL and say it is valid, but that turns out to be a bug? The OS updates, fixes the bug, now it says the URL is invalid -- but we already compiled the code and said it was valid! What are we going to do? Throw the App in to a crash loop? Let it continue with an invalid value?

That may be what happens now, but at least now we return an Optional. We give the user the opportunity to handle errors (or trap, if they decide to), and we don't present any kind of illusion of compile-time validation.

This proposal makes very big promises, but I highly doubt it can deliver on them. It is extremely vague and presents concepts in a misleading way.

11 Likes

I don't believe this is the case. This proposal does not add the compiler optimizations of constant folding and propagation. Those already exist. This proposal simply causes a build failure if the developer wants to enforce the possibility for the compiler to use those optimizations. This proposal does not even provide any guarantees that these optimizations will definitely take place for @const values.

7 Likes

Constant folding and propagation are orthogonal to elimination of static constructors. Furthermore, compiler features that don’t affect surface level semantics are not typically brought through the evolution process.

I can find nothing in the proposal to intimate that static initializers are eliminated. The compiler is already able to do that if the property can be replaced with its value at build time. This proposal is not adding that ability.

4 Likes

I think we should not treat build-time evaluated functions (initializers) the same as usual ones. Library developers and users should bear in mind that using a const function implies “if the library version to build against has any bug, it could be leaked into the program”.

The key point doesn’t change: if there’s any security problem in a library, we should upgrade it as soon as possible. In the past we need to upgrade the minimum supported version, and now we should also rebuild with the new implementation to ensure we get the security fix for build-time values. In most cases users are already doing the latter as a consequence of the former.

This depends on the implementation of URL. It could either crash or throw or use some other way to handle the value. A forbidden value in the initializer doesn’t necessary mean it’s known to be incompatible with the implementation. Runtime errors or preconditions are still necessary for robustness, and library developers can fully control the behavior. A crash is the easiest implementable way.

Of course, what developers should do is to use newer SDKs as possible, and to update their applications to avoid invalid hard-coded values. This should be the ultimate and correct solution.

2 Likes