Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for immutable/one-time assignable variables #820

Open
Wavesonics opened this issue May 10, 2020 · 73 comments · May be fixed by godotengine/godot#99778
Open

Add support for immutable/one-time assignable variables #820

Wavesonics opened this issue May 10, 2020 · 73 comments · May be fixed by godotengine/godot#99778

Comments

@Wavesonics
Copy link

Wavesonics commented May 10, 2020

Describe the project you are working on:
video game

Describe the problem or limitation you are having in your project:
I can't enforce a varriable only being assigned once

Describe the feature / enhancement and how it helps to overcome the problem or limitation:
I propose adding a new keyword to GDScript: val in addition to the existing var for declaring variables. This would force the variable to only be assigned once.

Kotlin, ECMA6, and Swift all have a feature similar to this.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:
declaring a variable as val will throw a error if the variable is assigned more than once. It allows for a few nice conventions:

  1. It allows for a sort of scoped constant (currently constants can only be global)
  2. It prevents accidental re-assignments for varriables that weren't intended to change. It makes maintaining the code less error prone.
  3. It can enforce a variable only gets assigned once in an if/else chain or match statement:
# This is valid
val x: int
if foo > 2:
    x = 42
else:
   x = -5
# This is valid
val x := 42
# This is NOT valid
val x: int = 42
x = -5

If this enhancement will not be used often, can it be worked around with a few lines of script?:
Can't be worked around

Is there a reason why this should be core and not an add-on in the asset library?:
Can't, it's a language level feature

@Jummit
Copy link

Jummit commented May 11, 2020

Can't be worked around

You can make a setter function with setget and only assign a value if it is null.

@Wavesonics
Copy link
Author

@Jummit only if you are talking about a member variable, not if this is a local variable.

@Calinou
Copy link
Member

Calinou commented May 11, 2020

For reference, quoting my own message from godotengine/godot#23695:

I'm in favor of let, as it's harder to confuse compared to val (which looks close to var at a quick glance). Some languages like Nim already use let for immutable variables. However, there's the downside that JavaScript users may be confused by this keyword, since let variables are mutable there.

Edit: Swift also uses the let keyword for immutable variables (and var for mutable ones).


It can enforce a variable only gets assigned once in an if/else chain or match statement:

Allowing this would likely complexify the implementation a fair bit, so I'm not sure about it. (Just a gut feeling, I'll let vnen decide on this.)

@mnn
Copy link

mnn commented May 11, 2020

It can enforce a variable only gets assigned once in an if/else chain or match statement:

Allowing this would likely complexify the implementation a fair bit, so I'm not sure about it.

Using if/else in its expression form and introducing or improving match to be an expression would IMO solve the problem more elegantly (without the need of flow analysis which I imagine would be quite a feat to implement).

@Calinou Calinou changed the title GDScript: One time assignable varriables GDScript: Add support for immutable/one-time assignable varriables May 11, 2020
@Calinou Calinou changed the title GDScript: Add support for immutable/one-time assignable varriables Add support for immutable/one-time assignable varriables May 11, 2020
@Calinou Calinou changed the title Add support for immutable/one-time assignable varriables Add support for immutable/one-time assignable variables May 11, 2020
@sszigeti
Copy link

@Jummit only if you are talking about a member variable, not if this is a local variable.

Local variables do trigger setter and getter functions when used with self. See a related proposal.

Unrelated, I'd also like to state that val and var are not only too similar, but some people have trouble pronouncing "L" and "R" properly, so let's not open that can of worms. ;)

@nobuyukinyuu
Copy link

nobuyukinyuu commented May 31, 2020

I use val as a var name for locals and property arguments a lot. Maybe some other keyword or combination of keywords could be used? var my_variable = x const maybe?

Other suggestions:

  • const var
  • immutable
  • final or sealed
  • readonly
  • let

or just allow local constants.... But I'm guessing the reason that wasn't allowed is because of philosophy over the definition of what const is and should be....

@vnen
Copy link
Member

vnen commented Jun 1, 2020

Describe the problem or limitation you are having in your project:
I can't enforce a varriable only being assigned once

Honestly, this is not the purpose of the heading. Making a proposal "I want X" then say that's because "I can't do X" isn't very informative. The question is: why do you need variables to be only assignable once?

I know it's an interesting programming concept that some languages implement. But why and how would that be beneficial in GDScript?

or just allow local constants.... But I'm guessing the reason that wasn't allowed is because of philosophy over the definition of what const is and should be....

Not sure what such philosophy is, but in Godot 4 local constants will be allowed. From the perspective of implementation it's a no-brainer since now it can reuse almost all of the code made for class constants (which wasn't the case before).

@nobuyukinyuu
Copy link

nobuyukinyuu commented Jun 1, 2020

Not sure what such philosophy is,

Well, normally, I imagine it would be that a keyword like const is typically reserved for variables that are known to be constant at design-time, which is definitely different from variables that are immutable. consts in that sense would only be a subset of immutable variables, the latter of which only become "constant" once they're first assigned (though I imagine most syntaxes require assignment on the same line as where an immutable var is declared).

@Wavesonics
Copy link
Author

Fair point about the heading. It wasn't meant to be facetious, but I suppose I could have added a bit more to it. Local const does solve many of the use cases I had in mind. One pattern that I personally like which it does not cover is something like the val keyword in Kotlin:

val x: int
if(condition_1)
    x = 1
else if(condition_2)
    x = 2
else
    x = -1

If someone goes back to maintain this code, it's fairly resistant against mistakes. Removing the else will result in unassigned error at compile time. Adding another branch but forgetting to assign x is a compiler error, modifying an existing branch, but adding a path that doesn't assign or, or even re-assigns it, all result in compiler errors.

The more common use case of just assigning a local that shouldn't be changed is covered by local const though, so that would solve a lot of my own use cases as well.

@kayomn
Copy link

kayomn commented Jul 11, 2020

I think some mention should be made about the concept being something being visibly immutable. For example, say I have the following value:

class_name Player extends KinematicBody

var _breadcrumbs: = PoolVector3Array();

Then I wanted to expose it by providing a get function.

func get_breadcrumbs(index: int) -> PoolVector3Array:
  return self._breadcrumbs;

Visibly const would allow you to guarantee that the callsite cannot modify the data structure while having it mutable as usual within the origin class, by providing a limited view to the data without having to write multiple sets of getters.

func get_breadcrumbs(index: int) -> readonly(PoolVector3Array):
  return self._breadcrumbs;

This is just spit-balling however, as I don't really use GDScript enough yet to know if this would have any implications that would break existing code or go against any sort of design philosophy with it.

@hilfazer
Copy link

hilfazer commented Mar 23, 2021

I wanted to make a proposal for this but i'm not sure if it's worth a proposal and it makes more sense in the current context so i'll mention it here.

The idea is to add oninstanced keyword. It would assign variables upon instantiation, so when NOTIFICATION_INSTANCED gets emitted. Or more precisely - right before that, just like var k = 5 at the top of the script is initialized right before _init().
I recall some users had the need to refer to child nodes before _ready().

It'd be useful mostly for caching child nodes which now is usually done with onready keyword. oninstanced would be allowed to be used in place of onready:

oninstanced val = $"Godot 4 will be great"
or
oninstanced let = $"In the meantime try Godot 3.3"
or whichever keyword would be chosen.

Why does it make more sense in the context of this proposal? Because oninstanced would be the last time an immutable script variable could be initialized which would allow for code like the one above. It would give immutability + value assignment on declaration line.

@nonunknown
Copy link

Well, seems that using:

  • let
  • imut

Seems to be good options!

Also This could let us export those imutables, for example, lets suppose I want a constant speed variable, but I want to export it so I can change it:

@export imut MAX_SPEED := 10.0 # This can be changed only in-editor

@MaasTL
Copy link

MaasTL commented Dec 5, 2022

Hi, I need this Feature

Here is where (Godot 4.0 beta 7):

@export var week: Week : set = set_week
var _is_ready: bool = false

func _ready() -> void:
	connect("ready", func () : _is_ready=true)

func set_week(value: Week) -> void:
	if not self._is_ready:
		await ready

	week = value
	
	# Update the Children
	for weekday in self.get_children():
		#Do stuff ...

You see that _is_ready is never suppose to change and if it does, the children won't get updated.

So let's just pretend it's 2:00 am in the Morning and I make the mistake of writing _is_ready = false in a random script or another contributer was just playing around with it
Now I have to try and find the error

If the variable would be immutable, the debugger would point me right to the codeline, where I try to assign a value to an already locked variable

But without it I'd be noticing that my children aren't getting updated anymore
And depending on the projects size I might be looking into multiple error sources
Once I would have figured out it's _is_read I'd still have to find the piece of code that changes it (which might be inside a random script somewhere in my project)
You get my point

So please let me lock varialbes! :D

@dalexeev
Copy link
Member

dalexeev commented Dec 5, 2022

4.0 is in feature freeze. This may be added later in 4.x because it doesn't break backwards compatibility. The current workaround is

var my_var = init_value:
    set(value): assert(false, "my_var is readonly")

@vnen
Copy link
Member

vnen commented Mar 7, 2023

Hi, I need this Feature

Here is where (Godot 4.0 beta 7):

@export var week: Week : set = set_week
var _is_ready: bool = false

func _ready() -> void:
	connect("ready", func () : _is_ready=true)

@MaasTL right here you are changing the value, so immutability would not solve for you because the _is_ready wouldn't be allowed to change to true after the initial assignment of false.

This case could be worked around with a setter:

var _is_ready: bool = false:
	set(value):
		_is_ready = _is_ready or value

This way it can only ever change from false to true, assuming the node is not supposed to leave the tree and become "unready".

This case would require you to change at runtime the immutability state of a variable which I think it's a bit dangerous and also harder to enforce (no compile-time checks would be possible for instance). I believe it would be better solved by access modifiers if we want to go that route.

@vnen
Copy link
Member

vnen commented Mar 7, 2023

Regarding keyword, I believe it's much better to find an affix for var rather than just replacing it. So readonly var x or var immutable x. It is just much more clear what means instead of trying to figure out the difference between var and let or val. Not to mention that val is very close to var and might be mistaken for one another in a quick glance.

Or an annotation, which is much more compatibility friendly, though I do prefer avoiding annotations for this kind of thing.

@jcostello
Copy link

readonly could also work but I insist that const should be use here.

For most cases readonly var and const will work the same. The only diference will be with maps and arrays when const should allow to not change the reference to a new one but to alter the map or array

@tokengamedev
Copy link

Any keyword that makes the variable immutable is fine. It can be readonly var or let or final var.
My question in the code below

# Defined a read-only var variable, it can be any keyword
class_name ParentClass

readonly var MYCLASS := CustomClass.new() #1st assignment

func _init()
    MYCLASS = CustomClass.new() #2nd Assignment

Which assignment is final?
If 1. then why not use const keyword as anyway the reference will be const not the content
If 2. then can it be assigned in derived class also. (It gives quite flexibility and power)

@farfalk
Copy link

farfalk commented Feb 5, 2024

"let" will probably confuse people coming from webdev. In modern JavaScript, let is a way to define scoped reassignable variables
( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let )

I know JS is the only language using let this way... But it's also the most widely used language worldwide ( https://www.statista.com/statistics/793628/worldwide-developer-survey-most-used-languages/ ) and GDscript also contains a "var" keyword just like JavaScript, so the confusion potential for new users is quite high imo

@asrrehel
Copy link

asrrehel commented Mar 1, 2024

Although Godot has been upgraded to version 4.2 and there are still no runtime constants that we can use, i think it is quite unreasonable to think that the let keyword may be misunderstood by JavaScript programmers.

What really worries me is that, as @Calinou said, "Immutable variables must also be initialized as soon as they're declared." Will this prevent us from defining immutable variables ​​in the constructor method when creating an instance? What i mean is, one of the situations where such an immutable variable would be most useful would be as used in the code snippet below.

class_name SomethingHasImmutableID
extends Object

let id: int

func _init(p_id: int):
   id = p_id

like in cpp

class SomethingHasImmutableID {

private:
    const int m_id;

public:
    SomethingHasImmutableID(int p_id) 
        : m_id { p_id } 
    {}
};

If you let me think out loud, imho to achieve the effect here, it can be ensured that let variables can be defined as null, and then the first assignment can only be made if it is null, throw an error otherwise for sure. But the problem here is that initialization does not have to be done in the _init function. In fact, it is not necessary to do it at all. It is obvious that such a situation is unacceptable. Therefore, if it is known that a value is assigned to the let variable in the _init function, then the let variable can be allowed to be defined as null.

@m21-cerutti
Copy link

m21-cerutti commented Mar 4, 2024

Would be great to make it work with @export too, my use case would be like :

class_name ResourceContainerBase
extends Object

@export readonly path: String
...

func create() -> ResourceContainerBase:
	var ressource= preload(path)
	...
	return ressource
...
class_name ResourceContainerImpl
extends ResourceContainerBase

func _init():
   path= "const path inside godot"

For now, preload work only only work on const variable that we can't associate with export (use it more for serialisation than edition),
Parse Error: Preloaded path must be a constant string.

But when writing this message I supose maybe it would be complicated to make it, even if we have const inside variable, set it in children could be make the task too complicated.

@Opposite34
Copy link

Opposite34 commented May 21, 2024

"let" will probably confuse people coming from webdev. In modern JavaScript, let is a way to define scoped reassignable variables ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let )

I know JS is the only language using let this way... But it's also the most widely used language worldwide ( https://www.statista.com/statistics/793628/worldwide-developer-survey-most-used-languages/ ) and GDscript also contains a "var" keyword just like JavaScript, so the confusion potential for new users is quite high imo

I feel like this is a weak argument. In JS, most do the immutable variable assignment with const, which is already different than how GDScript use it for constants (even though the original creator of the const concept in JS intended for it to be a constant, it did not turn out that way for most people working in the language). I dipped my toes in nim lately, and I do really like that they used the keyword let for it, but readonly / readonly var does seem like a nice alternative too.

@ssokolow
Copy link

ssokolow commented May 21, 2024

There are various languages with no var and a let where, whether or not they're pure functional, it serves a role in the language equivalent to var.

I'd prefer something like readonly or readonly var so it doesn't wind up having confusion potential akin to doing this within the same language:

def do_thing(foo: int) -> int: 
    whatever  # This is a normal function

int do_thing(int foo) {
    whatever  // This is a closure
}

(You laugh, but Rust's closure syntax isn't exactly that similar to its function syntax when you see it taking a single expression with inferred types. fn regularFunction(foo: usize) -> usize { whatever } vs. |foo| whatever.)

@Opposite34
Copy link

Opposite34 commented May 21, 2024

I dont understand the argument,

There are various languages with no var and a let where, whether or not they're pure functional, it serves a role in the language equivalent to var.

I'd prefer something like readonly or readonly var so it doesn't wind up having confusion potential akin to doing this within the same language:

def do_thing(foo: int) -> int: 
    whatever  # This is a normal function

int do_thing(int foo) {
    whatever  // This is a closure
}

(You laugh, but Rust's closure syntax isn't exactly that similar to its function syntax when you see it taking a single expression with inferred types. fn regularFunction(foo: usize) -> usize { whatever } vs. |foo| whatever.)

This already doesn't work in GDScript because it does need explicit identifier whether it is a var or a function:

func do_thing(foo: int):
	print(str(foo))

var do_thing = func(foo: int):
	print(str(foo))

will be caught by the LSP: "do_thing" has the same name as a previously declared function

Not sure how that relates to if a language is to use let, readonly, or readonly var for immutability.

@ssokolow
Copy link

ssokolow commented May 21, 2024

I dont understand the argument,

I think it makes a language more confusing if it borrows what, outside of JavaScript, are "100% synonymous... just in different languages" and assigns different sematics to them.

Imagine what a hassle it would be if, as an English-French bilingual person, you looked at a codebase and saw both do and fait keywords without it being clear and obvious what the difference is... or let's try for a more direct analogy. Having no experience with any keywords but var and let, how would you feel if you encountered a language that took its terminological influence from 1960s languages and used set and assign and it wasn't clear from context what the difference was.

It also reminds me of situations like tripping over false friends when learning natural languages.

will be caught by the LSP

I firmly believe that it's a misdesign for a language to rely on IDE tooling to paper over shortcomings in the intuitiveness or human-friendliness of the syntax itself. Among other reasons, it makes it more hostile to users choosing a "use external editor" option that happens to not have an LSP host or for which they don't want to go through the hassle of setting up the LSP just yet.

(eg. I habitually code Rust, Python, CSS, HTML, and JavaScript/TypeScript using a Vim that doesn't really give me anything beyond syntax highlighting and automatic paired tag insertion to prevent the highlighting from flickering when I type an opening character. For me, it's the coding equivalent of using a distraction-free writing tool like FocusWriter. It helps me to stay focused.)

@Opposite34
Copy link

It also reminds me of situations like tripping over false friends when learning natural languages.

I understand the argument now. Yeah in that case, readonly / readonly var would be clearer for someone never touched GDScript before. I still don't see how it connects to the closure example you linked though, but I get your point now.

it makes it more hostile to users choosing a "use external editor" option that happens to not have an LSP host or for which they don't want to go through the hassle of setting up the LSP just yet.

This is Godot we are talking about. Regardless if you are using an outside editor or not, you still have to run the game from Godot, so no matter what, the tooling will exist. Moreover, you can just attach the LSP to any editor, it's simply running on a localhost port. For example, neovim's setup for Godot LSP is just as simple as attaching it like this reddit post showed.

I habitually code Rust, Python, CSS, HTML, and JavaScript/TypeScript using a Vim that doesn't really give me anything beyond syntax highlighting and automatic paired tag insertion to prevent the highlighting from flickering when I type an opening character.

Even then, you will still have to run your code inside Godot itself which will notify the (reassigning function as closure) error for you. It is a game engine for a reason.

But yeah, I can see why readonly / readonly var is a better intuition in general.

@ssokolow
Copy link

I still don't see how it connects to the closure example you linked though

Two different syntaxes which, in their respective languages of origin, are synonymous (function declaration in Python and C) but, in that hypothetical, encode a semantic difference.

This is Godot we are talking about. Regardless if you are using an outside editor or not, you still have to run the game from Godot, so no matter what, the tooling will exist. Moreover, you can just attach the LSP to any editor, it's simply running on a localhost port. For example, neovim's setup for Godot LSP is just as simple as attaching it like this reddit post showed.

You're assuming that...

  1. The user's preferred editor has support for acting as an LSP host (eg. It appears that the third-party LSP host plugins for Notepad++ are struggling with API breakages that render them incompatible, and I don't think SciTE supports LSPs.)
  2. The user wants integrated LSP functionality (eg. for Rust, I rely on bacon to run cargo check or cargo clippy in a separate terminal window where its effects will be out of my field of view until I actively switch to it and, before I bought my brand-new PC in January, I manually ran cargo check because it would impart quite the amount of flow-breaking latency on a 2011 Athlon.)

(I have a Vim plugin that supports acting as an LSP host... I just generally keep it limited to format-on-save functionality because so much IDE/LSP functionality is distracting to me.)

My point was that a language's syntax shouldn't assume a specific kind of relationship between the user and their static analysis tooling.

@Opposite34
Copy link

Opposite34 commented May 21, 2024

I think I misunderstood the closure thing. I didn't realized you were comparing Python and C at first as I thought you were focusing on accidentally reassigning a function as a closure variable (which is why I gave that GDScript snippets and show you that LSP will catch it). That makes a lot more sense, and I think I agree with you now that readonly / readonly var will probably be more inclusive than let.

My point was that a language's syntax shouldn't assume a specific kind of relationship between the user and their static analysis tooling.

Regarding this point, let's agree to disagree. This is because I look at GDScript similar to a DSL where its usage is to be used with Godot engine and not as a generalized language (regardless of your editor the code still has to be run inside the engine, in which you will get the tooling anyway). However, that's only tangentially related to the current topic, so I'll end my reply here as to not clog the thread.

(but basically I'm now favoring either readonly or readonly var over let for GDScript, and I hope this discussion is still being read by people implementing it into the language)

@dalexeev
Copy link
Member

dalexeev commented May 21, 2024

I think we need to find a balance between clarity and convenience. In any case, you have to read the documentation to understand how the language works. This doesn't mean we shouldn't care about clarity and intuitiveness. However, if the alternative is too cumbersome and has other disadvantages, then it may make sense to sacrifice a little clarity. Especially in the case of keywords that are used frequently.

In my opinion, readonly var has several disadvantages:

  1. It's cumbersome. Not only because it takes up more space, but also because it consists of two keywords. You have to autocomplete twice every time you declare an immutable variable, which is a bit more distracting.
  2. This makes immutable variables secondary to mutable ones. Given point 1, you will be less comfortable if you wanted to use immutable variables whenever possible (in functional manner). If we were talking only about member variables, then I would probably agree with you, but for local variables readonly var looks much worse than let to me.
  3. It is less likely that let is often used as an identifier. While readonly can easily be a property of a custom control or data structure. Yes, we can allow readonly to be used as an identifier, but it's better to avoid soft conflicts too. For example, because it reduces bugs with incorrect syntax highlighting in our and third-party editors.

It's normal that keywords are used differently in different languages. There are tens of more or less popular languages in the world and thousands of little-known ones, we cannot avoid conflict with any language. The difference between var and let in GDScript may be different than in JavaScript. GDScript is related to it no more than GDScript is related to Python. readonly var may also confuse some people. For example, C# users may expect that a readonly member variable can be assigned in the constructor. Immutable variables and readonly fields are similar but slightly different concepts because static and local variables are not fields.

Quote Calinou:

I'm in favor of let, as it's harder to confuse compared to val (which looks close to var at a quick glance). Some languages like Nim already use let for immutable variables. However, there's the downside that JavaScript users may be confused by this keyword, since let variables are mutable there.

Edit: Swift also uses the let keyword for immutable variables (and var for mutable ones).

Why should we consider JavaScript more than Nim and Swift?

@ssokolow
Copy link

In my opinion, readonly var has several disadvantages:

Just some things to throw against the wall which would address "two keywords" part and the C# concern about readonly:

  • fixed (as opposed to "variable" but distinct from "constant")
  • fix (both as a shortening of "fixed" and in the "fix in place" sense.)
  • once (not once var... just once)
  • oncevar
  • ivar (short for "immutable var")

Why should we consider JavaScript more than Nim and Swift?

Because its position as the only language with direct access to the browser's DOM and the long period when it was the only non-plugin language to be runnable client-side has led to it being the most used programming language in the world (far more used than Nim or Swift) and one of the languages novice programmers get pointed to as their first language?

@nobuyukinyuu
Copy link

nobuyukinyuu commented May 21, 2024

Because its position as the only language with direct access to the browser's DOM and the long period when it was the only non-plugin language to be runnable client-side has led to it being the most used programming language in the world (far more used than Nim or Swift) and one of the languages novice programmers get pointed to as their first language?

I don't think this is a good reason to give it any more credence than any other language quite frankly (unless the goal is to make gdscript more like JavaScript, which it isn't and shouldn't be)... But I don't think we should give special credence to any language that didn't directly inspire gdscript anyway. JavaScript syntax just happens to rub me the wrong way in particular.

fixed and ivar are fine suggestions though. I don't think they're any better or worse than let or readonly. I wouldn't even mind some context-sensitive thing like field or local.

Edit: The only thing I really have a problem with is making it val or an annotation. Annotated keywords don't seem to have a consistent scope or domain in gdscript 2.0 and come off as a mess to me and will probably end up being used like a dumping ground if we're not careful

@dalexeev
Copy link
Member

dalexeev commented May 21, 2024

Note that let in JavaScript only exists because the var behavior was found to be unfortunate (the var declaration applies to the entire function, not the block, for which let was introduced). GDScript doesn't have this problem natively, var applies to the block.

Therefore, it would be weird for us to be inspired by the JavaScript solution that is due to historical reasons. It makes more sense to look at other languages. In fact, let is quite good for immutable variables: when in mathematical definitions and proofs we say "let x equal something" it is very close to immutable variables and the functional paradigm.

fixed and fix have the same problem as readonly: they are more likely to be used as identifiers. Also, can you name any language that uses fixed or fix for immutable variables? There are already at least two languages for let: Nim and Swift.

ivar has several disadvantages. It differs from var by just one character, is less intuitive than readonly var or let, and is subjectively ugly. We do not have any other keyword in the language with a single-letter abbreviation. It's less readable.

The only alternative I see is immutable var / immutable. But this is not much different from readonly var / readonly. The advantage and at the same time disadvantage is that the term is scientific, so it is less likely that someone will use immutable as an identifier.

@ssokolow
Copy link

ssokolow commented May 22, 2024

If let really is the best option, I'm OK with that... I just want to make sure the solution space is thoroughly explored first.

If this were a clean-slate situation, my personal favourite is let and let mut from Rust.

@shingo-nakanishi
Copy link

shingo-nakanishi commented Sep 6, 2024

How did this discussion go? (Is it still under consideration?)

In Flutter, or rather in Dart, there are final and const. I think Dart's const is similar to GDScript's const. It’s strange that the final keyword has not been actively mentioned in the discussion. Dart, which already has both const and final with functionality almost identical to GDScript’s const, is not being considered. It’s puzzling why Dart has not been brought up as a topic.

@clorl
Copy link

clorl commented Oct 3, 2024

The naming part of the proposal is kinda irrelevant, whatever you choose, users will adapt, but this would be a great feature.

Possible naming schemes would be
let for immutable, var for mutable
readonly var
immutable var
pending const (as in "this const isn't defined yet but will be, can be assigned only once)

etc

@shingo-nakanishi
Copy link

In discussions about immutable variables, it seems that in C#, there are only read-only properties, but no immutable local variables. As a result, the discussions about read-only properties and immutable variables sometimes get mixed up.

What I am looking for is immutable variables. They would provide hints when reading the source code, especially in cases where the value of a function is stored in a temporary variable, indicating whether the variable can or cannot be changed. (This is just one example.)

foo = bar()

In most cases, it’s unlikely that the return value of a function will be modified. There’s no real need to store it in a mutable variable, but since immutable variables are not supported in the language, we are left with no choice but to use mutable variables.

@geekley
Copy link

geekley commented Nov 22, 2024

I didn't see anyone mention function parameters (sorry, thread too long).
You could argue having immutable parameters is just as useful as immutable local variables.

So I guess there's an argument for the annotation / prefix-keyword syntax as it would be consistent (using @final as an example):

@onready @final var sprite = $Sprite
func foo(@final node: Node) -> void:
  @final var parent = get_parent()

I'm not saying this is better. I really like no-prefix let/val at least in local variables, because it's succinct. And I guess there is nothing preventing a keyword be used in parameters, except it's slightly weird (tbf, it's used in GDShader; so maybe not that weird).

@onready let sprite = $Sprite
func foo(let node: Node) -> void:
  let parent = get_parent()

Still, perhaps it's worth considering a combination of both?

@onready readonly var sprite = $Sprite
func foo(readonly node: Node) -> void:
  val parent = get_parent()

or

@onready @final var sprite = $Sprite
func foo(@final node: Node) -> void:
  val parent = get_parent()

@geekley
Copy link

geekley commented Nov 22, 2024

The way I see it:

  • let:
    • PRO: as succinct as var (practical to type and no extra visual clutter)
    • PRO: likely won't clash with anything
    • con: meaning conflicts with widely used JS where its use is not just different, but opposite
    • con?: not really self-explanatory IMO? not too bad, but IDK
  • val:
    • PRO: as succinct as var (practical to type and no extra visual clutter)
    • PRO: kinda self-explanatory
    • con: will clash with existing usages of val as an identifier (not an issue if it's only used in local vars)
    • PRO? con? people said it's too similar to var; I see it as an absolute win, the concept is similar
  • @readonly or @final or readonly or final:
    • con: another word to type; extra clutter to look at
    • PRO: final is self-explanatory, readonly is the most self-explanatory
    • PRO: will not clash, specially if using annotation
    • PRO: similar to other widely-used languages

@geekley
Copy link

geekley commented Nov 22, 2024

And... there's also def if you're not bothered by other languages like Python using it for function definition:

@onready def sprite = $Sprite
func foo(def node: Node) -> void:
  def parent = get_parent()
  • def:
    • PRO: as succinct as var (practical to type and no extra visual clutter)
    • PRO: likely won't clash with anything
    • con?: meaning is a bit different from Python; though it's still a somewhat similar concept (#define comes to mind)
    • PRO: self-explanatory enough

I think def would be the best choice.

@tetrapod00 tetrapod00 linked a pull request Nov 28, 2024 that will close this issue
3 tasks
@JackErb
Copy link

JackErb commented Nov 28, 2024

I posted a pull request to mark variable declarations as immutable with "let". #99778

I have a question. How should we deal with mutability of function parameters? Ideally, I'd like function parameters to be immutable by default, but I don't want to make this a breaking change of course.

How about we reuse the let syntax in function parameters to mark them as immutable:

func foo(let bar: int) {...}

@geekley
Copy link

geekley commented Nov 28, 2024

How about we reuse the let syntax in function parameters to mark them as immutable:
func foo(let bar: int) {...}

I guess it's better than using some annotation. And I doubt a prefix like _ (yikes!) or symbol (&bar or $bar) would be any better.

@Calinou
Copy link
Member

Calinou commented Nov 28, 2024

How about we reuse the let syntax in function parameters to mark them as immutable:

You can already do var something: int in function parameters, so I'd say using let in the same context is acceptable.

@geekley
Copy link

geekley commented Nov 28, 2024

@Calinou Really? In master? I get an error in 4.3 Expected parameter name

@Calinou
Copy link
Member

Calinou commented Nov 29, 2024

Really? In master? I get an error in 4.3 Expected parameter name

Nevermind, maybe it was possible in 3.x.

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