godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.17k stars 98 forks source link

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

Open Wavesonics opened 4 years ago

Wavesonics commented 4 years ago

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 commented 4 years ago

Can't be worked around

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

Wavesonics commented 4 years ago

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

Calinou commented 4 years ago

For reference, quoting my own message from https://github.com/godotengine/godot/issues/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 commented 4 years ago

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).

sszigeti commented 4 years ago

@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 commented 4 years ago

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:

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 3 years ago

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 commented 3 years ago

Well, seems that using:

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

dalexeev commented 1 year ago

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

const is used in GDScript in the sense of "value known at compile time", like constexpr in C++.

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

In 4.0 this was changed, now the contents of const arrays and dictionaries cannot be changed.

jcostello commented 1 year ago

In 4.0 this was changed, now the contents of const arrays and dictionaries cannot be changed.

That is what I'm implying. If values of arrays and maps can be changed in const that would solve the need of a val or readonly. The reference cannot be changed but the content of the array or map.

vnen commented 1 year ago

@jcostello that wouldn't solve the problem as you still wouldn't have immutable variables. While you wouldn't be able to change the reference, you could still change it's contents the same way you can change a regular variable. So you cannot use the contents as readonly since they can be changed and the array itself needs to be a compile time constant to use const so it's also not a solution even if you want an array.

However, since Godot 4 has read-only Arrays and Dictionary, you can actually leverage that for sort of immutable variables:

var imut_container := { imut = non_const_expression() }
imut_container.make_read_only()
imut_container.imut = "new value" # Gives an error since the dictionary is now read-only.
Shidoengie commented 1 year ago

I believe this is a key feature that Gdscript is missing, and it would be extremely helpful for things like exports. I would use let instead of val thought as it's more distinct. Imutable variables are key for functional programming patterns. As for branches, I agree that they should allowed to be used as expressions, and even could replace the current ternary statements, but that would require rust like results, aka implicit return of the last expression in a block. this however should be mentioned in a separate issue.

let b = true
let a = if b: 6 else: 9
let c = if b:
   6
else:
   9

Bugsquad edit: Removed excessive quoting.

ssokolow commented 1 year ago

I think it's too much of a footgun for val and var to differ by only one letter, both of which consist mostly of a vertical stroke.

mnn commented 1 year ago

I think it's too much of a footgun for val and var to differ by only one letter, both of which consist mostly of a vertical stroke.

Scala uses var and val and I don't think in hundreds of hours I have ever used them incorrectly. Probably because I use val everywhere and only where performance is extra important var was used. Another approach would be to have val (immutable) and mutable val (mutable). But I suspect preferring immutability (similarly to FP and types) is not something GDScript devs are interested in. It could be a setting of a project (when enabled everything is immutable by default, explicit modifier must be used to make it mutable), but I believe similar proposal was shot down with enforcing types.

AThousandShips commented 1 year ago

Changing var to val is not going to fly as it is a huge compatibility breaking change

Also changing from mutable by default to immutable by default additionally breaks compat, and makes the code for even simple things significantly more complex by adding mutable everywhere

It also makes little sense to me to have immutable by default in a script language

Shidoengie commented 1 year ago

I think it's too much of a footgun for val and var to differ by only one letter, both of which consist mostly of a vertical stroke.

Scala uses var and val and I don't think in hundreds of hours I have ever used them incorrectly. Probably because I use val everywhere and only where performance is extra important var was used. Another approach would be to have val (immutable) and mutable val (mutable). But I suspect preferring immutability (similarly to FP and types) is not something GDScript devs are interested in. It could be a setting of a project (when enabled everything is immutable by default, explicit modifier must be used to make it mutable), but I believe similar proposal was shot down with enforcing types.

FP patterns are on their rise recently, and imutability would be wonderful for things like apis. I personally don't like mutable val and in my opinion if this was to ever happen system closer to rust let and let mut would be better although let mut would replace var as so it shouldn't be a project setting, and due to that it would be a major breaking change, so it is not a very good idea on its self.

ssokolow commented 1 year ago

Scala uses var and val and I don't think in hundreds of hours I have ever used them incorrectly. Probably because I use val everywhere and only where performance is extra important var was used. Another approach would be to have val (immutable) and mutable val (mutable). But I suspect preferring immutability (similarly to FP and types) is not something GDScript devs are interested in. It could be a setting of a project (when enabled everything is immutable by default, explicit modifier must be used to make it mutable), but I believe similar proposal was shot down with enforcing types.

The same rationale could be used to argue that there was no need to disallow assignment in tests in if statements in Python and Rust and so on because "I never have a problem avoiding accidental if x = 1 in C".

Just because you don't have a problem with an error-prone design decision in one language doesn't mean it's a good design decision.

Shidoengie commented 1 year ago

Changing var to val is not going to fly as it is a huge compatibility breaking change

Also changing from mutable by default to immutable by default additionally breaks compat, and makes the code for even simple things significantly more complex by adding mutable everywhere

It also makes little sense to me to have immutable by default in a script language

Yes indeed, we shouldn't not change var to val. As for adding mutable everywhere I disagree, there exists a some languages with imutable by default for instance rust, and from personal experience I rarely rarely have to mutate data.

Shidoengie commented 1 year ago

Also something unmentioned is that imutable variables can prevent bugs that occur by accidental mutation of a value, like for instance you have a export for the player jump height, exports as far as I'm aware cannot be const therefore a bug could be unintentionally introduced along the line that for example doubles the jump height

AThousandShips commented 1 year ago

prevent bugs that occur by accidental mutation of a value

The proposal literally mentions this as an argument

Shidoengie commented 1 year ago

prevent bugs that occur by accidental mutation of a value

The proposal literally mentions this as an argument

Oh I did realize it my bad

dalexeev commented 1 year ago

I like readonly var or let. But readonly var has questions:

1. Should it be an annotation or a keyword? static is a keyword, @onready and @export are annotations. If we add public/protected/private modifiers in the future, will they be keywords or annotations? For example, private readonly static var x = 1.

The point of annotations is to reduce the keyword count and keep the parser simple, but static is a keyword. I think this is because static is a common keyword in different programming languages, while @onready and @export are Godot-specific.

2. Immutable variables can also be useful as local declarations, in which case you probably don't want long keywords. Especially since 4.x supports local constants. I'd prefer let or other keyword (but not val, since it's too similar to var).

var and const are different keywords for "stored values" with different traits, not var val and const val for example.

If we have var and let, then we allow the user to choose the option they need from two "equal rights" options (three with const). We do not impose any of them, neither mutable nor immutable. And if we have var and readonly var, then we implicitly prefer the mutable option, and make the immutable option secondary / more verbose / less accessible.

mnn commented 1 year ago

Just because you don't have a problem with an error-prone design decision in one language doesn't mean it's a good design decision.

Scala had recently(ish) a major release with many breaking changes. Had this var vs val been an issue, they would have changed it (they changed many core features and syntax). They haven't, so that tells me very few people in real world has problems with it.

I would wager it comes from the "prefer safer" approach. If you heavily prefer immutability (of variables and inside data structures), you rarely use var and almost everywhere is val, so at worst when you try mutating/reassigning something which you wanted to be var but made a mistake with val, compiler complains. If you heavily prefer val, then other way doesn't really happen, because you have so few vars in a place and those are carefully chosen because of limitations like performance.

Edit: Also since Godot comes with a script editor, it should be quite simple to implement e.g. coloring based on which variable is var/val (or any other names capturing same idea).

ssokolow commented 1 year ago

My favourite language is Rust, so I'm well aware of the whole "most variables don't need to be mutable" thing and agree... but "code is read more than it's written".

Having val and var be so similar is like forgetting your commas when writing prose. You can infer around it, but it's much more prone to mis-reading... especially if the person doing the reading isn't having a well-rested, distraction-free day.

mnn commented 1 year ago

Also changing from mutable by default to immutable by default additionally breaks compat

Maybe I didn't write it correctly, I meant by default option for "non-reassignable by default" would be off. So no change in compat.

and makes the code for even simple things significantly more complex by adding mutable everywhere

Well, yes, that's the point in such languages. Since mutations are source of many bugs (especially in parallel and async programming), it is generally best to not prefer them (not make it easier for a user to shoot themselfes in the foot - make mutable variables very apparent in the code). I also believe non-reassignable "variables" have performance benefits, or at least potential for more optimization.

It also makes little sense to me to have immutable by default in a script language

What difference does it make, if you are using highlevel language or scripting language to interact with the engine? You can script okish in Scala (Ammonite) or Haskell (Stack) and both have immutable data structures, Haskell has non-reassignable "variables" (thought redefinable in certain places), Scala supports both. Also by GDScript adding types and being compiled (if I understand the export process correctly), it has started moving away from a scripting language in my opinion. Again, I may have written it wrongly before, but I meant an option toggle on "non-reasignable by default" for a project, not that such option would be by default activated. Immutable data structures by default could be quite a problem, I fully agree. Might be a better approach to just add new data structures which are immutable and a warning/error (with option to disable with comment or similar) if you use a mutable datastructure in non-reasignable "variable".

Having val and var be so similar is like forgetting your commas when writing prose. You can infer around it, but it's much more prone to mis-reading... especially if the person doing the reading isn't having a well-rested, distraction-free day.

I honestly don't really care how it will be called (just that it's short), I just pointed out var/val seems to be a non-issue and demonstrated that on pretty large language and its recent rework where they fixed a lot of similar usability issues, but this wasn't one of them. I wouldn't like to have another FP (maybe just FP close?) feature feeling like afterthought because of cumbersome syntax, similar how lambdas ended (very verbose, to the point my hacky library for v3 is better, at least in syntax).

ssokolow commented 1 year ago

I honestly don't really care how it will be called (just that it's short), I just pointed out var/val seems to be a non-issue and demonstrated that on pretty large language and its recent rework where they fixed a lot of similar usability issues, but this wasn't one of them. I wouldn't like to have another FP (maybe just FP close?) feature feeling like afterthought because of cumbersome syntax, similar how lambdas ended (very verbose, to the point my hacky library for v3 is better, at least in syntax).

ivar (immutable var) would be good enough, since it's an additional character rather than just one character changed to another, similar one.

var vs. let would be a valid choice under the same logic, since they're so visibly different.

sullyj3 commented 1 year ago

+1 for let, it's short, clearly visually distinct from var, and has a lot of precedent being used for immutable variables (javascript is the exception that proves the rule)

ssokolow commented 1 year ago

let would also put the language into a state where you could pretend that let came first and var was added later, which is nice.

Shidoengie commented 1 year ago

Imutable variables would be amazing for exports, thats what i would mainly use them for

produno commented 1 year ago

I like readonly var or let. But readonly var has questions:

  1. Should it be an annotation or a keyword? static is a keyword, @onready and @export are annotations. If we add public/protected/private modifiers in the future, will they be keywords or annotations? For example, private readonly static var x = 1.

The point of annotations is to reduce the keyword count and keep the parser simple, but static is a keyword. I think this is because static is a common keyword in different programming languages, while @onready and @export are Godot-specific.

  1. Immutable variables can also be useful as local declarations, in which case you probably don't want long keywords. Especially since 4.x supports local constants. I'd prefer let or other keyword (but not val, since it's too similar to var).

var and const are different keywords for "stored values" with different traits, not var val and const val for example.

  • var - value can change at runtime.
  • let - the value is known upon assignment and cannot be reassigned (can't change if we think about references, not contents).
  • const - the value is known at compile time and can't change.

If we have var and let, then we allow the user to choose the option they need from two "equal rights" options (three with const). We do not impose any of them, neither mutable nor immutable. And if we have var and readonly var, then we implicitly prefer the mutable option, and make the immutable option secondary / more verbose / less accessible.

If i were to choose i would prefer readonly. I think the points about it being more verbose are not really warranted, considering we have autocompletion. I would expect it to autocomplete in this case if typing an r on a new line, so it would just be two key presses anyway. It is also much easier to see when skimming code. Also, whether most variables need to be mutable or not, most people will have most variables mutable, so i don't see `readonly being used that often.

I also do not think they belong as annotations.

ssokolow commented 1 year ago

If i were to choose i would prefer readonly. I think the points about it being more verbose are not really warranted, considering we have autocompletion.

If declaring and initializing in separate locations (eg. declare in file/class scope, initialise on construction) is supported, then readonly will be misleading, since it's really "write once".

Also, whether most variables need to be mutable or not, most people will have most variables mutable, so i don't see `readonly being used that often.

I don't think it's reasonable to use an assumption like that to justify making it more verbose for people coming from languages like Rust or various functional languages to do the "immutable unless mutation is necessary" pattern. That's reminiscent of how making const-ness and restrict opt-in in C and C++ limit their real-world use.

produno commented 1 year ago

If declaring and initializing in separate locations (eg. declare in file/class scope, initialise on construction) is supported, then readonly will be misleading, since it's really "write once".

Isn't that the same with C#?

I don't think it's reasonable to use an assumption like that to justify making it more verbose for people coming from languages like Rust or various functional languages to do the "immutable unless mutation is necessary" pattern. That's reminiscent of how making const-ness and restrict opt-in in C and C++ limit their real-world use.

The assumption being that beginners will most likely keep all variables mutable. Its the same precedent set with types. I would argue languages like Rust are more advanced but here with GDScript, we are trying to lower the cost of entry for beginners.

I guess we need to decide what is best for a language developed primarily for game development, that also allows easy entry for beginners but also allows advanced programming techniques for professionals, or for building larger and more advanced games.

Shidoengie commented 1 year ago

If declaring and initializing in separate locations (eg. declare in file/class scope, initialise on construction) is supported, then readonly will be misleading, since it's really "write once".

Isn't that the same with C#?

I don't think it's reasonable to use an assumption like that to justify making it more verbose for people coming from languages like Rust or various functional languages to do the "immutable unless mutation is necessary" pattern. That's reminiscent of how making const-ness and restrict opt-in in C and C++ limit their real-world use.

The assumption being that beginners will most likely keep all variables mutable. Its the same precedent set with types. I would argue languages like Rust are more advanced but here with GDScript, we are trying to lower the cost of entry for beginners.

I guess we need to decide what is best for a language developed primarily for game development, that also allows easy entry for beginners but also allows advanced programming techniques for professionals, or for building larger and more advanced games.

Yes, that's why I believe let would be the best choice, it's not veborse and it's simple

hilfazer commented 1 year ago

I see 2 advantages to val or let:

  1. same length as var so they will look nice next to each other
var foo := 3
var bar :Node
val baz = "Godot is awesome!"
  1. are very short. We might consider how those one time assignable variables will be used. Some people have announced they will use them together with @export. People might also be using it with @readonly. We could also consider potential syntax additions like encapsulation wich would most likely require a keyword in front of every variable.

@onready private readonly var some_stuff

would be quite lengthy.

dogman035 commented 1 year ago

I think, conventions aside, var and val are too visually similar and would be bad for readability. So I'm in favor of let

Calinou commented 1 year ago

I think, conventions aside, var and val are too visually similar and would be bad for readability. So I'm in favor of let

vnen was more in favor of an annotation in this comment, but maybe I'd go for @final in this case (Python uses a : Final type annotation as part of its typing_extensions package).

ssokolow commented 1 year ago

My only concern would be potential confusion for people coming from Java. Java's final is best known in its role as the inverse of C++'s virtual rather than in its role as being about single-assignability.

I could easily see that confusing people who've had a semester or two of Java and little more.

(I'm one such person and I'd actually either forgotten or never learned about its use outside opting out of virtual dispatch.)

tokengamedev commented 10 months ago

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 commented 9 months ago

"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 commented 9 months ago

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 commented 8 months ago

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.