Skip to content

Const Keyword for Local Declarations#166

Open
bradsharp wants to merge 3 commits intomasterfrom
rfc-const-keyword
Open

Const Keyword for Local Declarations#166
bradsharp wants to merge 3 commits intomasterfrom
rfc-const-keyword

Conversation

@bradsharp
Copy link
Contributor

@bradsharp bradsharp commented Jan 9, 2026

This document introduces the const keyword for local variable bindings, explaining its syntax, semantics, and potential drawbacks.

Rendered

This document introduces the `const` keyword for local variable bindings, explaining its syntax, semantics, and potential drawbacks.
@jackdotink
Copy link
Contributor

The motivation is very weak and the drawbacks of breaking backwards compatibility are... bad.

The motivation boils down into preventing accidental reassignment, and enabling unspecified implementation optimizations. Accidental reassignments by and large just don't happen. As for the unspecified optimizations, luau today knows if a variable is constant or not, and if it is, it applies optimizations. Checking if a binding is ever reassigned is trivial.

I assume the suggestion of breaking backwards compatibility comes with the idea of a tool to allow users to migrate code. This is bad because such a tool is infeasible for the largest user of the language, roblox. The migration tool cannot be run at user's own leisure because roblox forces the latest version of luau, and the tool cannot be run automatically effectively because many users do not use roblox as a source of truth for their code.

All in all, this feature opens too many difficult questions for the lack of utility it provides.

Expand on the motivation for introducing the `const` keyword, emphasizing its role in ensuring stability for exported variables and preventing accidental reassignment. Provide examples illustrating the implications of mutable bindings in module exports.
Clarify the behavior and implications of the 'const' keyword in local declarations, including its contextual usage and potential drawbacks.
@bradsharp
Copy link
Contributor Author

bradsharp commented Jan 12, 2026

The motivation is very weak

export is the primary motivation for introducing const-ness to the language. When we return from a module with exports we freeze the result (see why here). This leads to an issue where the variables can be reassigned but the return value itself doesn't change. If we're going to introduce const-ness to the language for export then it seems reasonable to expose it for general use at the same time.

I've added more details about this to the RFC.

the drawbacks of breaking backwards compatibility are... bad.

Instead of reserving const as a reserved keyword it can be introduced as a contextual keyword that is only valid in positions that local is also valid. This makes the change compatible with existing code as const foo = 5 would fail to parse today.

I've covered this in the RFC also.


```luau
const x = 1
x = 2 -- error
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of error? Static(Parsing Error) or dynamic?
Difference is more obvious inside a function definition.

const a = 42
function foo() -- 1
  a = 43
end
foo() -- 2

Should we see an error at parsing of 1 or at the call of 2?
JavaScript picks option 2, but honestly I'm not aware of advantage of it. So I would prefer option 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can be implemented as a parsing error but we may want a runtime error as a backstop for cases like loadstring. I would expect the error to be on the line of the actual assignment, in this case a = 43.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an aside JavaScript likely picks (2) because it's the line that leads to a being mutated and without it the value remains constant. For sanity, I think we should stick to erroring on a = 43 even if the code is unreachable.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS goes crazier here:

const a = 42;

function bar(overrides) {
  with (overrides) {
    let foo = function() {
      console.log(a);
      a = 43;
      console.log(a);
    }

    foo();
  }
}

bar({ a: 50 }); // prints 50, 43
bar({ b: 50 }); // prints 42 and throws TypeError: Assignment to constant variable.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned above it is not an issue right now.
But it can be in conflict with developing proposals, like destructive assignment.
Theoretically you can have something like that in you code now:

const {a, b}

and it sould be treated as a call to const with a single param.
Assuming destructive assignment will look like

const {a, b} = x

It can be an issue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As const variables must always be initialized const {a, b} would be invalid (and therefore treated as a function call) while const {a, b} = x remains unambiguous as this is invalid syntax today. The one downside I see if that this could become a gotcha, so we might want to consider a linter warning for const {a, b}.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bradsharp While const {a, b] = x is not ambiguous, it is still not reasonably parsable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It almost makes one want for a "Do not add destructuring syntax" RFC

const x: number = 5
const t: { a: number } = { a = 1 }
```
Multi-assignment is also supported:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth mentioning vararg multi assignment explicitly.

it should be fine from runtime point of view to have something like

function f() return 1 end

const a, b = f()

Because f can return multiple values in last position(same with ...). In that case b should be initialized with nil. But typechecker probably should complain here.

Copy link

@SPY SPY left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think RFC in a good shape and can be merged.

@MagmaBurnsV
Copy link
Contributor

There's 9 thumbs down. Shouldn't we wait for more community input?

A major problem with this RFC is that it's primarily motivated on fixing a hole caused by the current semantics of the export-by-value RFC. This motivation is entirely speculative and different semantics for export-by-value can and should be chosen to solve this instead.

Secondary motivation for safeguarding reassignment is also rather unpopular. Anecdotal experience from myself and others says that accidental local reassignments rarely happen, and more often we want cross-module immutability, which is taken care of with table.freeze.

@jackdotink
Copy link
Contributor

I'm more concerned on the future compatibility concerns with destructuring syntax, as in, this RFC doesn't leave a path open for destructuring syntax.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

Comments