[Pitch] Destructuring Assignment of Structs and Classes

I wouldn't mind losing an ability to destructure arrays. Destructuring properties with [] is general enough to work with arrays too. And I think "property-based destructuring" or "field destructuring" are better names for this.

As shown above, you could destructure arrays with this syntax too.

 // compiles, `first` is `.some(1)`, last is `.some(1)`
let [first, last] = [1]

// compiles, `first` is `.some(1)`, `rest` is `[]`
let [first, rest = dropFirst()] = [1]

// nested destructuring, contrived example
// `first` is `.some(1)`, `nestedFirst` is `nil`, `last` is `nil`
let [
  first, 
  [nestedFirst = first, last] = dropFirst()
] = [1]

// nested destructuring, shouldn't compile due to name collision
let [
  first, 
  [first, last] = dropFirst()
] = [1]

OTOH, if we allow addressing dropFirst() like that, we again get into scoping mechanics. So maybe {} could work better in that sense.

And there is also pattern matching with switch and if case to consider.
I would probably expect it to work like this:

switch [1] {
case let [first?, last?]:
  print("non-empty array")
default:
  print("empty array")
}
1 Like

Related: @beccadax recently opened a proof-of-concept PR for property destructuring based on tuple pattern syntax:

  struct Alignment {
    enum Lawfulness { case lawful, neutral, chaotic }
    enum Goodness { case good, neutral, evil }
    var lawfulness: Lawfulness
    var goodness: Goodness

    var descriptionPrefix: String {
      switch self {
      case (lawfulness: .lawful):
        return "lawful"
      case (lawfulness: .neutral):
        return "neutral"
      case (lawfulness: .chaotic):
        return "chaotic"
      }
    }

    var descriptionSuffix: String {
      switch self {
      case (goodness: .good):
        return "good"
      case (goodness: .neutral):
        return "neutral"
      case (goodness: .evil):
        return "evil"
      }
    }
  }
6 Likes

Thank you to everyone who has given input on this pitch! It's clear that there are a number of shortcomings in the original post that should be considered for a real proposal or at least a reformed pitch. Things like:

  1. Generalize the name to "property destructuring" and apply it to any entity with properties: structs, classes, enums with computed fields, existentials. Actors would also be included here but have additional concerns, see #7

  2. The curly brace syntax seems to be unpopular and potentially problematic. Something using [ ] similar to a closure capture list, or ( ) similar to tuple destructuring.

  3. Allowing property destructuring both in variable declarations as well as switch statements.

  4. Address questions around extracting a property to a variable with a different name.

  5. Figure out syntax for extracting deeply nested values.

  6. Address questions around if we should allow capture modifiers like weak and unowned in the variable binding.

  7. Compatibility with async let, for example when destructuring actor properties. Seems like async let { propA, propB } = myActor would introduce a task group as if they were written as two async let assignments, there's been no discussion on this topic yet.

  8. Gathering any feedback on destructuring of @dynamicMemberLookup types. My assumption is that any arbitrary name would succeed as a destructured assignment as well as a pattern match.

  9. Handling of property wrappers. Projected values work fine but the wrappers themselves would always need to be aliased by the user to some variable name without the leading $.

After reading through the discussions I think the benefits of this feature would be:

  1. Increase the expressiveness of using entities with properties (in the case of pattern matching)
  2. Reduce boilerplate, clarify intent, and promote correctness in extracting properties to multiple variable bindings

Amazing that @beccadax has begun an implementation for at least the pattern matching part of this proposal. Would love to get your input on the rest of the ideas here!

Maybe if we can answer some of the questions above we can reformulate a pitch with those decisions in mind. Thanks again everyone :slight_smile:

8 Likes

One thing that would be nice to address is a story around exhaustive destructuring. The same way enums support exhaustive switching, tuples support exhaustive destructuring, and recapturing this feature in some fashion would be useful.

Exhaustivity introduces a number of questions, though:

  • What's the syntax for "exhaustive" vs. "non-exhaustive" destructuring? Do we throw an _ in there somewhere to denote ignoring the rest?
    let [make, _] = car
    
  • What's considered exhaustive? Do we need a struct equivalent to open enum to denote when a struct cannot be exhaustively destructured? What even is "exhaustive"? Does destructuring become exhaustive when all stored properties are assigned?

Why should we care about exhaustive destructuring? Well, it is useful whenever you want the compiler to let you know:

  1. When you forget to use a property in some way (the local destructuring will give you an "unused" warning unless you explicitly ignore it with an underscore).
  2. When you have code that should no longer compile when properties are added (or removed).

A real-world example: I've written app features using Combine pipelines, and I used exhaustivity of tuple destructuring to make their associated unit tests more robust. These pipelines were created by taking a bunch of subjects for user inputs, and producing a bunch of publishers that feed their output into various UI elements. In unit tests, I would test that when I sent these subjects various inputs, various outputs would emit. By returning all outputs in a tuple and using destructuring in my tests, I got the following checks from the compiler:

  1. It would warn me if I ever forgot to test the output of a particular publisher.
  2. It would fail to compile if I ever added a new output to a feature (or removed one).

Using tuples worked wonderfully for these requirements, but the ergonomics of tuples in Swift aren't always great, and I would have much preferred to bundle these Combine publishers in a nominal struct type instead.

6 Likes

I think the capture list–like syntax is much more powerful than the curly brackets–based and parentheses-based syntaxes that have been proposed. It solves numerous problems I had with the proposal:

  1. Variables don’t have to have the same name as the accessed property.
let [make0 = make, model0 = model] = car0
let [make1 = make, model1 = model] = car1
let [value0 = self.0, value1 = self.1] = aLongHomogenousTuple
// useful in the case of a long fixed-size array imported from
// C where tuple pattern matching would be impractical
  1. The syntax is more consistent with Swift.

When I said that the use of curly braces was “too JavaScript-y and not very Swift-y”, I didn’t mean that Swift should never borrow ideas from JavaScript. In fact, one of the core philosophies of Swift is that “[i]t is better to borrow good, proven, ideas from other languages than it is to be novel in areas that don't need to change.” Rather, I meant that the syntax makes more sense in JavaScript (where the meaning of curly braces is more ambiguous) than it does in Swift. I think that the square bracket syntax is more consistent with Swift’s syntax and will be less confusing to programmers that already know capture list syntax. Conversely, it will also make capture list syntax less confusing to programmers that already know this syntax.

  1. Deeply nested values are neater.

In the original syntax, deeply nested values were assigned via a colon. This would make for an awkward syntax if the types were declared explicitly:

let { name, cpu: { numberOfCores } }: Computer = computer

As you can see, some colons would indicate a type while others would indicate a nested type. I consider this another case where the syntax makes more sense in JavaScript than it does in Swift, due to Swift associating the colon with type relationships and argument labels. In contrast,

let [name, [numberOfCores] = cpu as CPU]: Computer = computer

uses a colon only to indicate a type, though the syntax is less clear due to the extraneous types. The ideal form of the previous statement would be this:

let [name, [numberOfCores] = cpu] = computer

I like how this syntax doesn’t require a special syntax for nested types, instead reusing the assignment syntax. This seems to be more flexible, allowing things like this:

let [anIntValue, (firstTupleValue, secondTupleValue) = aTupleValue] = anInstanceOfAStructureThatStoresAnIntAndATuple

Some other comments:

  • Destructuring feels like a misleading name for this feature, as we are really just creating syntactic sugar over accessing members of an instance. Can anyone think of a better name for this concept? Perhaps we could call this the “member pattern” (as opposed to the “tuple pattern”, “wildcard pattern”, and Swift’s other patterns)?
  • I think @Max_Desiatov has shown that this syntax eliminates the need for a separate array-based syntax.
  • I don’t think exhaustive destructuring is a good fit for the this syntax — I think that’s better left to tuples and enumerations. Swift generally considers the composition of structures and classes to be an implementation detail, not something that should necessarily be exposed to the programmer.
  • @tera’s idea of a with keyword feels too close to the concept of closures.
{
    print($0.make)
    print($0.model)
    print($0.year)
    $0.year = 2021
}(&car)
  • I don’t really understand the concerns about async let compatibility. The async and await keywords can already be applied to multiple asynchronous operations as long as they’re on the same line within the same statement, just like the try keyword. For example, the following code snippet is valid Swift (though the compiler does give warnings that the declared constants are never used):
actor CarActor {
    init(make: String, model: String, year: Int) {
        self.make = make
        self.model = model
        self.year = year
    }
    
    var make: String
    var model: String
    var year: Int
}

let carActor = CarActor(make: "Subaru", model: "Outback", year: 2021)
Task {
    async let (make, model, year) =
        (carActor.make, carActor.model, carActor.year)
}
  • Should property wrappers work with this proposal? I don’t think they should unless support is also added for tuple assignments, but I’d like to see what others think.
struct MyStruct {
    var [@Clamped(to: 0..<12) number] = someGlobalVariable
}
  • I still have concerns about the clarity, necessity, and discoverability of this syntax. I think Quick Help could clue newcomers into the fact that this syntax is creating a new variable, but they may be confused as to where the variable is being initialized from. If we decide to implement this syntax, it may be a good idea to add it to the language around WWDC-time so that it can be shown to developers onstage before it gains widespread use.

This pitch needs some motivation. The OP doesn't mention any explicitly, and adopting syntax from other languages isn't a goal of Swift, despite it being willing to steal ideas from other languages. This existing in JavaScript isn't a good motivation for adding it to Swift. And as I understand it, the popularity of this feature in JS is due it providing much better failure scenarios than direct member access, as it will produce a much more clear failure when the number of elements don't match rather than providing undefined references. Obviously this isn't a problem that Swift has.

If there is sufficient motivation for this feature, I agree with the suggestion that it should be based on Swift's existing pattern matching syntax and not anything new: let (a, b, c) = struct.

11 Likes

Yup—@jrose and a couple other people were discussing property-based destructuring on Twitter last weekend, and I ended up getting nerd-sniped. :sweat_smile:

The implementation there is very quick-and-dirty; it's actually done by implicitly forming a tuple containing the properties and then matching that instead of the original value, since that let me reuse more of the existing implementation. These patterns also don't work correctly in a bunch of places, like in if case (where they still force a tuple context on the right-hand side of the match). But I found it useful to play around with a little.


To think about the syntax we want, we should start from the principles of the existing pattern-matching syntax. Patterns tend to echo the syntax used to form a value that would match the pattern:

  • You match an enum case by writing .caseName because you would create an instance of that case by writing .caseName.
  • You match a tuple by writing (name: subpattern, name: subpattern) because you would create an instance of that tuple by writing (name: value, name: value).
  • You match a value's type by writing subpattern as SomeType because you would cast an instance to that type by writing value as SomeType.

That means square brackets probably aren't a good fit for this feature. They're pretty strongly associated with collection literals; if I saw square brackets in a pattern match, I would expect them to destructure collections by element:

[let first, let rest...] = myArray
["x": let x, "y": let y] = myDictionary

Curly brackets {...} also aren't used in the syntax for initializing a property-holding instance. And they have a pretty serious ambiguity problem in at least one position where you should be able to write a pattern:

print("hello")
{foo: let foo, bar: let bar} = baz
// The compiler will think that pattern is a trailing closure.

(This ambiguity is why, unlike C, Swift doesn't allow you to write a bare block as a statement—you have to put do in front of it.)


So what is the right syntax? Well, if the goal is to write something similar to the code that would have created the value, the most obvious answer is to use something that looks like an initializer call:

Baz(foo: let foo, bar: let bar) = baz
case Baz(foo: let foo, bar: 14):

But arbitrary expressions are actually allowed in the patterns of case statements, so this could change the interpretation of existing code. It's also a bit strange because in an expression, this syntax needs to match a specific initializer, but in a pattern, it would just be naming arbitrary properties in an arbitrary order.

So I think it's better to extend the existing tuple syntax to also cover other types with properties. The initialization analogy is not as direct without the type name, but it's still true that you put labeled values in parentheses to create the value, so they still at least rhyme.

The simplest way we could do this (and the one I built a quick-and-dirty prototype of) is to simply use the exact same syntax as a tuple, but reinterpret it when the type checker finds that the value being destructured isn't a tuple:

(foo: let foo, bar: let bar) = baz
case (foo: let foo, bar: 14):

This reads really nicely and I think it's pretty easy to understand what's happening, but it does have some drawbacks. The two features have different rules—when you destructure a tuple, you have to name all fields in the correct order ("tuple shuffles" do actually work but they're deprecated); when you destructure something else, you can name properties in any order and you don't have to name all of them. This gives me pause for two reasons:

  1. Tuple destructuring and propertywise destructuring share a syntax, but tuple destructuring has an important correctness feature—you have to name every field and describe what to do with it—that propertywise destructuring does not have. This means that they share a syntax, but one of them is prone to bugs that the other prevents.

  2. You might actually want the propertywise behavior with a tuple sometimes. Tuple shuffles shouldn't be implicit because they're too easy to do accidentally, but explicit tuple shuffles would actually be pretty handy.

I'm not sure that these disqualify this syntax, but they do make me want to think about alternatives. One way to go might be to add an explicit indication that there are other, omitted properties. I favor using _... instead of just _, since _ already means "any single instance":

(foo: let foo, bar: let bar, _...) = baz
case (foo: let foo, bar: 14, _...):

Another option might be to use key path syntax instead of bare names; this drifts away a bit from the idea of using the same syntax that formed the instance, but it's very clear:

(\.foo: let foo, \.bar: let bar) = baz
case (\.foo: let foo, \.bar: 14):

(It also suggests that perhaps we could support other features allowed by key paths—like multiple components, subscripts, optional chaining, and perhaps in the future method calls.)

For that matter, the backslash is arguably unnecessary, and we could just use a leading dot:

(.foo: let foo, .bar: let bar) = baz
case (.foo: let foo, .bar: 14):

I think any of these options (tuple syntax, tuple with repeated wildcard, keypath-labeled, and leading-dot-labeled) would make immediate sense when read. My best guess is that tuple syntax and keypath-labeled syntax would be the easiest to remember how to write, since they're so closely analogous to existing features, but that's very much a guess.

Any thoughts?


P.S. One thing: Whatever solution we adopt, I think it's important that the properties be explicitly named, and that these names be separate from the names of the variables you're declaring. This is necessary to allow the feature to compose well with other pattern-matching features. You can sometimes get away without this in tuples because tuple fields come in a specific order, but that isn't true for structs, classes, and other property-containing types.

18 Likes

Hi @Jon_Shier, thanks for chiming in. I'd like to point out that the pitch mentioned JavaScript exactly once in the prior art section, purely as an example where this feature is widely used in a popular language. I don't believe making Swift more like JavaScript is at all an inherently good motivation. Yes I did borrow the curly brace syntax from JS but am now convinced that this is bad and do prefer let [make, model] = car or parentheses are fine as well.

Thank you for pointing out that the motivation for this feature was not clearly explained. It was a bit spread out in the pitch so I'd like to consolidate it here for convenience and because my understanding has evolved. Let me redefine the Car example with some more average length property names and variable name:

struct Car {
    let manufacturer: String
    let model: String
    let engineType: EngineType //some enum elsewhere, doesn't matter
}

let currentVehicle = Car(manufacturer: "Subaru", model: "Outback", engineType: .fourCylinder)

Often when using a value across the body of a function and accessing some or all of its properties, it can become very redundant to type currentVehicle.manufacturer, currentVehicle.model, currentVehicle.engineType many times throughout the function.

To cut down on the noise, there are a few common approaches. One is to shorten the variable name to something like curVehicle or something even shorter and less readable. Perhaps a more sensible solution, and one that I often see used, is to pull the properties out into local variables:

let manufacturer = currentVehicle.manufacturer
let model = currentVehicle.model
let engineType = currentVehicle.engineType

This is ok, but there's a lot of repetition here as well. let is repeated once per assignment, the currentVehicle is repeated once per assignment, and the name of the property value is repeated twice per assignment. That's quite a bit of noise just to enable some cleaner / more concise code later in the function. Let's compare that to the destructuring version:

let [ manufacturer, model, engineType ] = currentVehicle

With this approach, let, currentVehicle, and each property name are all still present, so no information has been lost or hidden, and we're reusing information to eliminate the redundancy. When I say reusing information, I mean sharing a single let between 3 variables, and reusing the property name as the variable name. Obviously this is more terse than the other method, but I do not believe clarity has been sacrificed for terseness. In fact I think clarity has increased because you can more quickly parse the assignments in a single go, rather than having to visually parse multiple lines cluttered by redundant information.

But that's not all! The multiple assignment statements are prone to a common (for me) bug, which is accidentally assigning a property to the wrong variable of the same type.

let manufacturer = currentVehicle.manufacturer
let model = currentVehicle.manufacturer
let engineType = currentVehicle.engineType

It's easy to miss that model has been assigned to the currentVehicle.manufacturer value (or vice versa) because they are both Strings and are identical to the type checker. This bug can happen easily when copy-pasting. Destructuring ensures that your local variable matches the property name and they don't get mixed up. Of course, we'd like to allow users to provide a different variable name if they'd like and they assume the risk in doing so.

To summarize, the motivation for destructuring assignment is that it is more concise, more clear, and more likely to be correct than the usual methods for eliminating lots of redundant property accesses in a function body. I believe that this is a net win for both readers and writers of Swift code.

I hope that clears things up a bit. There's been a lot of bikeshedding on the syntax and related features like switching on a struct. These are no doubt very important and should be included in an updated pitch, but I haven't yet seen a strong consensus that the problems presented are actually problems and that proposed solution is desirable. If you don't feel that they are I'd love to hear why! Thanks everyone :slight_smile:

4 Likes

This is a very interesting point independent of the discussion about the best syntax, perhaps important enough to be worth fleshing out before even delving into syntax.

In Swift, tuple elements, enum cases, and properties (whether computed or stored) all share the same . operator to access them on a value. However, they carry very different semantics:

  • This can be seen in the case of enums, where you can add static properties that can be accessed with the same syntax as though a case, but they absolutely aren't cases, as revealed readily when it comes to exhaustive switching.

  • In the same vein, when tuples conform to Hashable (the proposal is accepted but not yet implemented), you'll be able to access their hashValue, but that absolutely won't be a tuple element (unless you happen to label an element hashValue shadowing the protocol implementation). And with variadic generics (type sequences) on the horizon, we may be even closer to conforming tuples to arbitrary protocols.

In both of these cases, there is a clear notion of the "structure" part of "destructuring," or to use terminology more commonly used in Swift, what constitutes a tuple's or an enum's value.

We already have similar notions for structures, but it doesn't hinge on whether something is a stored property or not. Indeed, it is actively desirable for the author of a type to be able to switch a property from stored to computed without breaking user code. Rather, we encounter this issue in several places; given a value x of type T with value semantics:

  • What arguments do I need to pass to an initializer T.init(...) in order to construct another value that's interchangeable with x?
  • What are the 'salient' properties of x that are compared with my newly constructed value to determine equivalence? Relatedly, what are the properties of x that are fed into the hasher for Hashable conformance?
  • What are the keys of x that need to be encoded and decoded for the purposes of Codable conformance in order to roundtrip?
  • If the type is LosslessStringConvertible, what properties of x are required to come up with the converted string?

I think there may be a role here for a Value protocol that formalizes and makes consistent the answer to these questions for any given value type. For tuples and enums, there will be one and only one way to conform to Value and it can be synthesized. But for structs, we already know (from existing conformances to Equatable, Hashable, Codable, and LosslessStringConvertible) that the answer to these questions will not correspond to stored versus computed properties but depends on the semantics of the particular type. Indeed, for a given type, there may be stored properties (for example, private ones) that don't participate in some or any of these conformances and computed properties that do.

Now, returning to this pitch, I think we have a distinction to make here: Is the goal here to have a shorthand to access any property, or is it to destructure a value?

If one wishes to destructure a value, then the syntax should very much care that you get all the properties that are part of the "structure" of the value and none that aren't. We would want to work through what that means and whether it merits formalizing in some way with a protocol to which all destructurable values (including tuples and enums by default) should conform.

By contrast, if one wishes to have a shorthand to access any property (and this would include, for example, computed properties such as hashValue on tuples that conform to Hashable), then the syntax should very much not enforce any such thing. But these are, to my mind, two entirely separate--and in some ways incompatible--features.

15 Likes

I can't say I agree with any of that but you should certainly include it as the Motivation in your proposal.

3 Likes

i do this all the time. note that by raw character count this:

var v = currentVehicle
print(v.manufacturer)
print(v.model)
print(v.engineType)

is shorter than this:

var [manufacturer, model, engineType] = currentVehicle
print(manufacturer)
print(model)
print(engineType)

also the former version is more powerful as it allows working with more than one vehicle variable and also allows modifications (v.model = 2021). considering short functions (which i believe is a good thing) normally there is no confusion what a short variable name like "v" means.

note that you can use a single var / let for a number of variables.

otherwise i understand the motivation. a +1/2 from me.

3 Likes

@tera Totally fair points! Agreed that shorter variable names strike a balance of brevity, usefulness (e.g. support modifications), with a hit on readability. Short functions do alleviate that, so perhaps it's clear that more than one principle must be brought to bear to balance all of these concerns. Also v is an extreme example but even curVehic would be really hard to mistake (silly I know, but I've seen worse names in the wild).

I think that a single let / var can be used to declare multiple variables but not assign them to their initial values.

Overall great alternative solution to my problems, and I'll take the +1/2!

One solution could be to introduce a kind of self-map:

let car = Car()
let (model, make) = car.map { ($0.model, $0.make) }
2 Likes

For my part, I see the goal as being to pattern-match based on the contents of properties. I don’t see this being a complete destructuring as especially crucial.

13 Likes

I agree with Becca. The goal should be to make it easier to pattern match on the contents of properties.

I don't think the community is likely to ever accept anything sensitive to the declaration order of properties. The properties you're matching against should be named in the syntax.

Also, it's okay — maybe better! — for the syntax to not look like tuple destructuring. In general, patterns look like the "literal" syntax for creating a value of that kind of type; in this case, that would suggest that it look like a use of the memberwise initializer, although that has the disadvantage of requiring the type name to be spelled out, e.g.:

switch foo {
case Car(manufacturer: let manufacturer, model: let model, ...)
  where manufacturer != "Ford" || model != "Edsel":
}

Perhaps .init would be fine as an alternative. Similarly, it would be nice to avoid repeating the property names, which is the main source of verbosity in that example.

11 Likes

Would SE-0315 placeholder types work here?

3 Likes

I liked the pitch, as pitched, because it was clean. It would serve a similar purpose as https://forums.swift.org/t/lets-fix-if-let-syntax/. We've already got wordy syntax to do this. It doesn't solve new problems. The benefit is in the feature seeming like it's thinking what you're thinking, removing indirection.

Having to specify key paths and new variable names, as Becca and John have suggested, makes me not like the pitch nearly as much. It's a good option, but not a good default. Realistically, you're not going to need to rename things often. The names you pick will match whatever is after the last dot in the keypath. The idea is that you're digging in to a deeper indirected scope, and pulling out to the local scope, not fully remapping.

e.g. We want this, almost always


convenience computer let (.name, .cpu.numberOfCores)


not this, always:

convenience computer (let name: .name, let numberOfCores: .cpu.numberOfCores)

I don't encounter tuples like the following often, but you do need the remapping/renaming feature for them because index numbers can't be used as variables names.

let tuple = ("🐾", dog: "đŸ¶", log: "đŸȘ”")
convenience tuple (var frog: .0, let .log)
1 Like

+1 <3

1 Like

This is why I thought it would be good to clarify goals here before delving into syntax.

For me, the Car(manufacturer...) notation makes a lot of sense for a destructuring feature that's intuitively about getting back all the salient properties of a value that one puts in, as a sort of counterpart to initializing.

But as a syntax for pattern matching on the contents of arbitrary properties, it's both heavy and kind of...odd. Consider: if case Double(isFinite: let x) = 42.0 { ... }. If this were some edge case, then sure, but we're talking about performing a plain-ol' pattern matching on the contents of a property using a feature whose primary purpose is for pattern matching on the contents of properties.

Now that we've clarified that many (most?) people on this thread just want less verbosity for let x = foo.x, it seems to me that this thread is boiling down to much the same issue as prior threads about shorthands for guard let x = x, etc. This comes up with such regularity that we ought to have some sort of nickname for it; I'll propose to refer to it as the x = x problem.

Perhaps we could look for some sort of generalizable solution to put the x = x problem to bed once and for all. For instance, we could come up with a sort of repetition mark (there isn't one used in English, unfortunately, but in CJK languages there's "々").

6 Likes

Wait, wait! I hope for more than a solution to the x = x problem. :-)

I'd like to do things like operate on a tree-shaped data structure made
from objects instead of enums. Off the cuff example in a variant of
Becca R-G's syntax,

func leftRotate(_ tree: BinaryTree) {
	(left: let l, _..., right: (left: let m, _..., right: let r)) = tree
	tree.left = BinaryTree(left: l, right: m)
	tree.right = r
}

Dave

2 Likes