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 array unpacking/destructuring to GDScript #2135

Open
Birdulon opened this issue Jan 17, 2021 · 43 comments · May be fixed by godotengine/godot#77102
Open

Add array unpacking/destructuring to GDScript #2135

Birdulon opened this issue Jan 17, 2021 · 43 comments · May be fixed by godotengine/godot#77102

Comments

@Birdulon
Copy link

Birdulon commented Jan 17, 2021

Describe the project you are working on

A rhythm game, though more importantly a project where it makes sense for certain functions to return multiple values.

Describe the problem or limitation you are having in your project

I have a number of functions that return 2-3 distinct values, packed into an Array in GDscript. A simple example would be one that returns [PoolVector2Array positions, PoolRealArray angles].
Currently, I have two options to unpack that:

var result = note.get_points():
var positions = result[0]
var angles = result[1]
# ...

And what I'm currently using where convenient, binding match:

match note.get_points():
	[var positions, var angles]:
		# ...

Except it's not as convenient as it could be since it introduces new scope and two indent levels.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Arrays will be able to unpacked on a single line similar to Python or Lua. It will be possible to assign (and sometimes declare) multiple variables on a single line to the inner contents of an array.
a, b, var c = [1, 2, 3] will set existing vars a to 1, b to 2, new var c to 3.
In the case of my earlier specific example, it would become:

var positions, var angles = note.get_points()
# ...

Which saves the visual noise of both workarounds and the scope+indents of the binding match.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Python and Lua have it a bit easier with implicit declarations, but there's still a number of ways we could have this with explicit declarations:

var positions, var angles = node.get_points()  # My personal favourite but might be harder for the parser?
[var positions, var angles] = node.get_points()  # Basically the same as the binding match
var [positions, angles] = node.get_points()  # This is probably undesirable as you can't mix existing variables, but better than nothing

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

Yes, it can be worked around with a few lines of script per call. This adds a lot of visual noise though as it can come up quite frequently.
It can also be worked around by using Dictionaries for everything, but that's an uncomfortable amount of overhead for such simple functionality, and is only a drop-in replacement for new variables being declared, not reusing existing ones.

Is there a reason why this should be core and not an add-on in the asset library?

It should be core to the language syntax.
Lacking this language feature isn't the worst thing in the world but having it enables other convenient features like python-style dictionary iteration.

Related issues:

godotengine/godot#23055
#1965

Additional keyword for anyone who uses the same search terms as me: Tuple

@Calinou Calinou changed the title Add array unpacking to GDscript Add array unpacking/destructuring to GDscript Jan 17, 2021
@Calinou
Copy link
Member

Calinou commented Jan 17, 2021

I would go for this syntax personally as it reminds me of JavaScript's destructuring syntax:

var [positions, angles] = node.get_points()

I find the other syntaxes much less readable, especially var positions, var angles as it reminds me of C-style multiple variable declarations (which behave differently).

@YuriSizov
Copy link
Contributor

Some way to make that syntax type safe would be nice too. Maybe using the as keyword:

var [ positions as PoolVector2Array, angles as PoolRealArray ] = node.get_points()

@Calinou
Copy link
Member

Calinou commented Jan 17, 2021

@pycbouh Couldn't we reuse the existing type declaration syntax?

var [positions: PoolVector2Array, angles: PoolRealArray] = node.get_points()

Either way, I wonder how inferred type (:=) would be declared. Maybe := should still be allowed, but it would require all variables in the declaration to have an inferable type.

@YuriSizov
Copy link
Contributor

Couldn't we reuse the existing type declaration syntax?

I don't mind either way, I just thought as looked nicer and more syntactically explicit. This doesn't go against how the as keyword already used for casting, either. Also, colon used like that ([positions: PoolVector2Array, angles: PoolRealArray]) can be confused for object literals. But still, fine either way by me.

Inference may be the tricky part. Especially if some of the values can be Variant for example.

@me2beats
Copy link

var [positions:,  angles:PoolRealArray] = node.get_points()

which means positions is inferred

@bluenote10
Copy link

var [positions, angles] = node.get_points() # This is probably undesirable as you can't mix existing variables

In theory, it should still be possible to mix existing variables with introducing new ones. If a variable is already declared, it becomes an assignment only.

@me2beats
Copy link

hmm ..
In case vars can be declared earlier (in other lines) then I see this like:

[a, b, c] = x  #a,b,c were declared earlier
[var a, var b, var c] = x  #a, b, c are new vars
[var a, b, var c] = x  # var b was declared earlier

[a:int, b:, c] = x  #a= int, b is inferred, c is Variant

I see no problem writing var multiple times

btw [brackets] could be omitted, but imo this would make it less readable/clear:

var a, b, var c = x
var positions, var angles = node.get_points()

vs

[var a, b, var c] = x
[var positions, var angles] = node.get_points()

@me2beats
Copy link

me2beats commented Feb 2, 2021

Do I understand correctly that the implementation of this proposal would allow to implement

for item, value in enumerate(array)

and

for key, value in dict.items()

?
Or this is not related to the proposal?

@Birdulon
Copy link
Author

Birdulon commented Feb 2, 2021

Do I understand correctly that the implementation of this proposal would allow to implement

Yes, though adding those iterators to the standard classes can be a separate proposal if it suits.

@YuriSizov
Copy link
Contributor

I don't think that the iterator syntax has anything to do with unpacking/destructuring. It should be a separate proposal, if it doesn't exist already, and it's probably easier to implement as well.

@Birdulon
Copy link
Author

Birdulon commented Feb 3, 2021

Iterators would just need to return arrays for them to work with this proposal, they'd just be one application of the syntax.

@DarthMikeSundays
Copy link

Another usefull feature related to this would be dictionary destructing

@Calinou Calinou changed the title Add array unpacking/destructuring to GDscript Add array unpacking/destructuring to GDScript Jul 2, 2021
@Calinou Calinou moved this to Discussion Stalled in Proposals shortlist for review Apr 21, 2022
@blipk

This comment was marked as off-topic.

@Calinou
Copy link
Member

Calinou commented Aug 6, 2022

@blipk Please don't bump issues without contributing significant new information. Use the 👍 reaction button on the first post instead.

@Birdulon
Copy link
Author

Birdulon commented Aug 6, 2022

It has been brought to my attention that C++17 has this syntax for tuples: (matches third suggestion in OP)

auto [x, y] = z;

Rust has a practically identical syntax

let (x, y) = z;

Surely we don't want our scripting language to lose on ergonomics to C++ and Rust, right? 😛

@amedlock
Copy link

I like this proposal as JS destructuring is quite nice. If we go this route we should include the rest operator as well:

let [x,y ... z] = myArray;
let a = { a: 100, b: 200 ... myObject};

@Heraclito-Q-Saldanha
Copy link

Heraclito-Q-Saldanha commented Jan 24, 2023

I would love to see a destructuring and pattern matching syntax similar to the Rust

struct Person{
    name: String,
    age: u32,
    height: u32
}

let ferros = Person{
    name: "Ferros".to_string(),
    age: 19,
    height: 1.80,
};

let Person{name, age, ..} = ferros;

println!("{name} {age}");

let john = Person{
    name: "John".to_string(),
    age: 22,
    height: 1.76,
};

if let Person{age: 22, name, ..} = john{
    println!("{name} is 22 years old");
}else{
    println!("not 22 years old");
}

let vec2 = (10, 20);

match vec2{
    (0, 0) => println!("zero"),
    (_, 1) => println!("Y is one"),
    (1, y) => println!("x is one and Y is {y}"),
    (x, y) => println!("it's not zero or one, it's {x} {y}")
}

@dalexeev
Copy link
Member

dalexeev commented Mar 8, 2023

@TheYellowArchitect
Copy link

TheYellowArchitect commented Apr 17, 2023

let a = { a: 100, b: 200 ... myObject};

That is a dynamically created C struct! If you could declare it seperately (even in the same .gd, so it has the same scope), this would mean GDScript supports C structs (basically, variables which contain exclusively variables)

So this code would be the same

#OG
let struct_instance = { a: 100, b: false ... myObject};

#Same as the below
var struct_template: struct = { a: int, b: bool ... myObject: myObjectType}
let struct_instance = struct_template.new( 100, false, myObject)

Note that the existence of the above helps also in netcoding, as you can send RPC arguments which are not primitives (see #6216)

@SlugFiller
Copy link

I just want to point out that while destructuring itself is nice, array destructuring could actually lead to an anti-pattern. It basically encourages the use of untyped array as a makeshift tuple type. This is fine for untyped languages, but is a step in the wrong direction for GDScript which is gradually moving towards type safety.

Destructuring requires first-class tuples first. Or structs, which is essentially the same.

Technically, in current GDScript, you can just define an inner class, and then return an instance of it (A much better pattern than abusing arrays or dictionaries). So, destructuring assignment for classes is also an option.

@joao-pedro-braz
Copy link

Instead of necessarily dealing with arrays, I think we could defer to only allocating one if the "spread" syntax is used, like:

var [a_cool_variable, ...everything_else] = a_multiple_returning_function()

In this context, a_cool_variable would be akin to a match binding-variable and everything_else an Array (which could be typed if we can infer at compile time whether it's gonna be homogeneous).

The signature for the function a_multiple_returning_function() could look like:

func a_multiple_returning_function() -> [String, int, Vector2]:
    return ["Hi!", 42, Vector2.ZERO]

I think we can get away with not allocating anything unnecessarily.

@SlugFiller
Copy link

@joao-pedro-braz A signature like [String, int, Vector2] implies tuple types. Which GDScript does not currently have. There are a couple of proposals to that extent, but nothing with consensus.

If tuple types are a prerequisite for this proposal, at least one tuple type proposal should be linked from it.

@Birdulon
Copy link
Author

This is fine for untyped languages, but is a step in the wrong direction for GDScript which is gradually moving towards type safety.

GDScript is a fully dynamic language that is bolting on gradual typing. We don't need to obstruct this proposal with typed tuple prerequisites, it is sufficient to leave room in the syntax to allow for them.
I might also add that the documentation style guide is currently far less enthusiastic about the role of type hints in current GDScript: ( https://docs.godotengine.org/en/latest/contributing/documentation/docs_writing_guidelines.html#dynamic-vs-static-typing )

As static type hints are an optional feature of GDScript, we chose to stick to writing dynamic code.

Surely if the goal was to never encourage anyone to write untyped GDScript, documentation would be a top priority. So no, tuple types are not a prerequisite for this proposal, they are merely a mutually beneficial yet still independent feature, and impeding either for the sake of the other is just putting up with a worse GDScript for longer.

@joao-pedro-braz
Copy link

@SlugFiller
I guess there's two facets to this proposal:

  • De-structuring containers (Initially, Arrays) into variables
  • Having a heterogeneous type-safe container of elements

Doing both simultaneously would indeed require a new type "Tuple" which would (likely) be a subset of Array, but heterogeneously typed (and fixed sized, but that's the same as it being read-only).

The issue with this approach (which is not what I'm proposing, more below), besides the lack of consensus, is that adding new types is costly due to the way Variant works. We would have an easier time getting a solution merged if it didn't involve extending the base Variant class.

What I am proposing is the de-structuring facet, which is essentially syntax-sugar for:

match note.get_points():
	[var positions, var angles]:
		# ...

But without an intermediate Array (unless the spread operator is used).

The [String, int, Vector2] syntax does resemble a Tuple declaration and to a certain extent that's intentional, given how notorious Tuples are in de-structuring. A "real" (if ever implemented) Tuple could be declared as Tuple[String, int, Vector2], which I would argue is the most intuitive syntax and does not conflict with what I'm proposing.

The [String, int, Vector2] syntax could be refined to (String, int, Vector2), if the consensus is that it's too ambiguous with the Array declaration and alike. This new syntax would look like:

func multiple() -> (String, int, Vector2):
    return ("Hi!", 42, Vector2.ZERO)

@joao-pedro-braz
Copy link

joao-pedro-braz commented May 11, 2023

I guess we could be conservative here and just add the de-structuring "operator" for Arrays, but since typed Arrays are homogeneously typed, the user would probably have to de-structure into untyped variables, which isn't very ergonomic.

As @YuriSizov suggested, we could make these de-structured variables typables (var [foo: String, ...] = a_func()). But I don't think that's ideal, since it requires you to know beforehand what the inner types of the Array being de-structured are and that's easy to to break if anything changes "upstream".

@joao-pedro-braz
Copy link

I think we can make this work in a really nice way by implementing it in two "phases":

  1. First one, we add the de-structuring syntax for arrays:
    • We can infer types if needed (if the right-hand side of the assignment is a Typed Array)
    • We allow each de-structured variable to be typed separately (var [foo: String] = ...)
    • We introduce the spread syntax to capture "everything else" (var [foo, ...everything_else] = ...)
    • TBD (More on this below), we allow the original array to be captured (var [foo, ...everything_else], original = ...)
  2. As a second pass, we introduce the "virtual" Tuple-like [String, int , Vector2] type:
    • Essentially an alias for a statically-typed (No intrinsic/runtime type information) read-only Array
    • We extended the de-structuring syntax to take the it's static-typing into account when inferring a variable
    • I envision a use-case like:
      func _run():
          var [foo, bar] := cool_func()
          #              ^ Infer "foo" and "bar"
          #   ^ [both typed]
      func cool_func() -> [String, int]:
          return ["123", 123]

As for the TBD topic:

  1. Does it make sense to allow de-structuring at class member level?
  2. If so, does it make sense to allow a setter and/or getter to be defined? Like so:
    var [foo, bar] = [123, "123"]:
        set(value):
            print(value)
        get:
            return value
    In this context, each de-structured variable would get it's own setter/getter, and given each can have a different type, the setter and getter becomes untype-able.
    As mentioned, what if instead we introduce a way to capture the original Array?
    We could then have the setter and getter refer to that "original" variable. The syntax I propose:
var [foo, bar], original = [123, "123"]:
    set(value):
        # refers to "original = something"
    get:
        # refers to "print(original)", for instance
        return original

@SlugFiller
Copy link

@Birdulon That's interesting. I'd be interested in knowing the discussion that lead to this choice. But I imagine a factor of it was not confusing users that are new to programming. Typeless is indeed useful for learning or fast prototyping. But should most likely be gradually reduced when moving towards production.

There's also the question of when it was written. If it's a leftover from Godot 3, then typing would have had very different implications back then.

@joao-pedro-braz [String, int] is a tuple type. There's nothing virtual about it. It demands from the compiler to error out on something like

func cool_func() -> [String, int]:
  if randf() > 0.5:
    return ["123", Node.new()] # Type mismatch
  else:
    return ["123", 123, 456] # More elements than expected

var [foo, bar, blah] := cool_func() # Too many elements in deconstruction

The only difference is whether it's a first-class type or not. i.e., if this is allowed:

var foo : [String, int] = cool_func() # First-class tuple type
var foo := cool_func() # If tuple types are not first-class, then this is an error, and you MUST use cool_func ONLY with destructuring
foo[0] = "abc" # Alternately, if we define a "virtual tuple" as a "read only array" rather than a "fixed size array", then this is an error. Although tuples being read only is actually perfectly reasonable, for the same reason strings are read only.
var a : String = foo[0] # But what about this? Should this be an error? Should it be allowed?

Alternatives that don't involve a tuple type (first class or otherwise) would be:

  1. Working only for uniform types:
func cool_func() -> Array[int]:
  return [1,2,3]

var [a, b, c] := cool_func() # Perfectly fine. All are ints

BUT, this will not work for the OP's request, since positions and angles are (presumably) different types

  1. Working purely with Variant:
func cool_func() -> Array:
  return [123, "123"]

var [a, b] := cool_func() # a and b are Variant
var [c : int, d : String] = cool_func() # Error. You can't guarantee that cool_func will only return int and string
  1. Avoid array-styled tuples completely, and do destructuring for classes instead:
class MyClass:
  var a : int
  var b : String
  func _init(p_a: int, p_b: String) -> void: # It would be nice if this could be auto-generated using some sugar syntax
    a = p_a
    b = p_b

func cool_func() -> MyClass:
  return MyClass.new(123, "123")

var [a, b] := cool_func() # a is int, b is String
# But this is not really a huge save over:
var foo := cool_func()
print(a)
# vs
print(foo.a)

I think @Birdulon is aiming at alternative 2 above as the desired form. But as I've said above, I believe it's an anti-pattern. It will get people into the habit of writing error-prone code.

And the fact that the documentation is making the same mistake does not make two wrongs a right.

@joao-pedro-braz
Copy link

@vnen Since this is on the roadmap, could you chime in?

@jtnicholl
Copy link

jtnicholl commented May 16, 2023

I think creating objects is a better solution to the problem this is trying to solve.
In the case of the OP's example:

var positions, var angles = node.get_points()

it could be done as something like:

var points: Array[Point] = node.get_points()

or

var points: PointSet = node.get_points()

Godot and GDScript are built around object-oriented programming.

There's also the return arguments' order. Forgetting it will cause problems:

var angles, var positions = node.get_points()

And if positions and angles are the same type, it could be a very difficult mistake to track down. Using objects also prevents this issue.

@wareya
Copy link

wareya commented May 16, 2023

The "cure" of using objects is far worse in many cases than the problems you run into when you don't have unpacking/destructuring. Unpacking/destructuring is a common feature even in major mainstream object-oriented programming languages like C#, Kotlin, etc, and it is not out of place there. Forcing values that are packaged together solely for data flow/control flow reasons to be part of the same object does not aid in forming abstractions as OOP is meant to do; it's just paradigmatic overreach, like the NetBeanSingletonFactoryBuilderSpawners of yore.

As for "but it's so bad in javascript": this already works in gdscript match statements, and nobody seems to have any issue with it there. Real tuples would be nice, though, unrelated to this proposal.

@joao-pedro-braz
Copy link

Destructuring isn't only useful for function returns.
In the PR I created for this proposal (godotengine/godot#77102) besides destructuring Arrays (var [var foo] = [123], where foo will be equal to 123) I also introduced a "general" destructuring for objects/Dictionaries, like:

var { var x: float, var y: float } = Vector2(0.2, 0.8)
prints(x, y) # Will print "0.2 0.8"

Both of which can be nested together, as to allow a simpler "complex deep access", like:

class FooBar:
  var a_cool_property := "hello"
  var another_cool_property := 123
  var an_array := ["foo", "bar"]

# This
var { var a_cool_property: String, var another_cool_property: int, an_array = [_, var bar: String] } = FooBar.new()
prints(a_cool_property, another_cool_property, bar)

# Versus
var foo_bar := FooBar.new()
var a_cool_property := foo_bar.a_cool_property
var another_cool_property := foo_bar.another_cool_property
var bar := foo_bar.an_array[1]

@jtnicholl
Copy link

# Versus
var foo_bar := FooBar.new()
var a_cool_property := foo_bar.a_cool_property
var another_cool_property := foo_bar.another_cool_property
var bar := foo_bar.an_array[1]

And why do you need to write var bar = foo.bar; some_func(bar); instead of just some_func(foo.bar)?
It does result in lines that are a bit longer, but if you care about line length that much, the array destructuring statements are already very long and not easy to read. The one in your example is 115 characters.

This whole concept still seems to me like a solution to a problem that doesn't exist.

@owowed
Copy link

owowed commented May 20, 2023

@jtnicholl Array destructuring could be very useful for quickly naming things that are coupled together in array.

For an example, when creating a command in some sort of developer console, and you want to destructure the command arguments and name them as variables:

func give_player_item(args: Array):
    var command_name: String = args[0]
    var item_name: String = args[1]
    var item_amount: int = args[2]
    var item_data: Dictionary = args[3]

    Logger.info(command_name + " executed")

    var item = Item.from_name(item_name)
    item.load_data(item_data)
    
    $Player.give_item(item, item_amount)

CommandManager.add_command_callback("give", give_player_item)

Using array destructuring, you can forget the var and args[index], resulting in way more readable and cleaner code:

func give_player_item(args: Array):
    # array destructuring
    var [ command_name: String,
        item_name: String,
        item_amount: int,
        item_data: Dictionary ] = args

    Logger.info(command_name + " executed")
    
    var item = Item.from_name(item_name)
    item.load_data(item_data)
    
    $Player.give_item(item, item_amount)

CommandManager.add_command_callback("give", give_player_item)

@TheColorRed
Copy link

TheColorRed commented Aug 1, 2023

Object/Dictionary destructuring would be helpful too, not only with arrays.

# Only grab the keys/properties that we need (the new `var` must be the same as the `key`/`property`)

# Destructure a Node3D object
func reset_transform(node: Node3D):
    var { position, rotation } = node
    position = Vector3(0, 0, 0)
    rotation = Vector3(0, 0, 0)

# Destructure a dictionary
var dict: Dictionary = { 'a':1, 'b':2, 'c':3 }
var { a, b } = dict

Not sure if it was mentioned (as I didn't see it) in the proposal or comments, but allowing destructuring in the function params could be an option too.

# Parameter destructuring with an object
func reset_transform({ position, rotation }: Node3D):
    position = Vector3(0, 0, 0)
    rotation = Vector3(0, 0, 0)

# Parameter destructuring with an array
func set_position([x, y, z]: Array[float]):
    position = Vector3(x, y, z)

@Ury18

This comment was marked as off-topic.

@Calinou
Copy link
Member

Calinou commented Oct 6, 2023

@Ury18 Please don't bump issues without contributing significant new information. Use the 👍 reaction button on the first post instead.

@bryanmylee
Copy link

Just to chime in, I arrived here looking for a simple "Go"-like error handling paradigm. My biggest issue with GDScript in production is in writing functions that could return errors.

A typed Rust-like Result type would be nice, but until we get generics in GDScript, I'm using an array to return data, err.

func join_server() -> Variant:
  # ...
  return [session, null]
  # ... or
  return [null, "no connection"]

I'm okay dealing with the lack of typing by using assertions, but consuming the result of the function is not convenient without destructuring.

var session_res = join_server()
if session_res[1] != null:
  handle_err(session_res[1])
  return
var game_res = join_game(session_res[0].game_id)
if game_res[1] != null:
  handle_err(game_res[1])
  return
# ...

With destructuring, I could do:

var [session, err] = join_server()
if err != null:
  handle_err(err)
  return
var [game, err] = join_game(session.game_id)
if err != null:
  handle_err(err)
  return
# ...

An additional question though, does this pattern cause GDScript to allocate an array in the compiled code?

@warent
Copy link

warent commented Nov 4, 2023

Just to chime in, I arrived here looking for a simple "Go"-like error handling paradigm. My biggest issue with GDScript in production is in writing functions that could return errors.

A typed Rust-like Result type would be nice, but until we get generics in GDScript, I'm using an array to return data, err.

func join_server() -> Variant:
  # ...
  return [session, null]
  # ... or
  return [null, "no connection"]

I'm okay dealing with the lack of typing by using assertions, but consuming the result of the function is not convenient without destructuring.

var session_res = join_server()
if session_res[1] != null:
  handle_err(session_res[1])
  return
var game_res = join_game(session_res[0].game_id)
if game_res[1] != null:
  handle_err(game_res[1])
  return
# ...

With destructuring, I could do:

var [session, err] = join_server()
if err != null:
  handle_err(err)
  return
var [game, err] = join_game(session.game_id)
if err != null:
  handle_err(err)
  return
# ...

An additional question though, does this pattern cause GDScript to allocate an array in the compiled code?

I come from JavaScript / Golang, and I also follow this exact pattern of returning an array of values (Go doesn't quite do that, but it allows multiple returns which is visually very similar). In React it appears everywhere as e.g.

const [ state, setState ] = useState(0)

This would help clean up a lot of my code to be able to destructure the returns directly into variables as such!

@jamie-pate
Copy link

jamie-pate commented Mar 4, 2024

An additional question though, does this pattern cause GDScript to allocate an array in the compiled code?

I would assume the initial implementation would allocate, and a separate issue could be created to optimize that case, but would not get addressed as a priority.

An occasional extra array allocation here would likely not dominate your performance, and if you are doing something 1,000s of times per frame in gdscript maybe it'd be best to move it to a complied language anyways

@joao-pedro-braz
Copy link

joao-pedro-braz commented Mar 4, 2024

An additional question though, does this pattern cause GDScript to allocate an array in the compiled code?

In godotengine/godot#77102 arrays are only allocated to slice the original array in a spread operator (var [_, ..this_contains_a_slice] = ...), otherwise it's only addressed.

@anpaza
Copy link

anpaza commented May 1, 2024

My two cents.
Right now GDScript does not allow declaring multiple variables in one line, e.g. some hypotetical invalid code:

var a, b, c
var a: int, b: String
var a: int, b: String = 3, "33"
var a, b := 3, "33"

Personally I feel like the square brackets are not needed here. Encountering a comma in expression should be enough to transform expression result into a tuple.
I wouldn't call this "destructuring" and I wouldn't mix this with arrays / dictionaries. This approach looks like a temporary quick'n'dirty solution, which will influence GDScript forever.
Instead, it has more to do with function argument passing (you may regard args as a tuple), where the parentheses are part of the function call syntax, not of the tuple itself.

@repsejnworb

This comment was marked as off-topic.

@Calinou
Copy link
Member

Calinou commented May 23, 2024

@repsejnworb Please don't bump issues without contributing significant new information. Use the 👍 reaction button on the first post instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Development

Successfully merging a pull request may close this issue.