-1

I'm starting using Golang as my main programming language to do exercises in LeetCode, and I get a strange result for Golang's := operator for multiple variables.

Let me say the question 104 to get the maxDepth of a binary tree.

My first solution is to use layer traversing, and below is my code.

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func maxDepth(root *TreeNode) int {
    if root == nil {
        return 0
    }
    var s []*TreeNode
    var cur *TreeNode
    depth := 0
    s = append(s, root)
    for len(s) != 0 {
        size := len(s)
        depth++
        for i := size; i > 0; i-- {
            s, cur = s[1:], s[0]
            if cur.Left != nil {
                s = append(s, cur.Left)
            }
            if cur.Right != nil {
                s = append(s, cur.Right)
            }
        }
    }
    return depth
}

The upper code snippet can pass the test. But the below code snippet can't, and I don't know why, I just can debug the for loop can't finish, and it becomes an infinite loop.

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func maxDepth(root *TreeNode) int {
    if root == nil {
        return 0
    }
    var s []*TreeNode
    depth := 0
    s = append(s, root)
    for len(s) != 0 {
        size := len(s)
        depth++
        for i := size; i > 0; i-- {
            s, cur := s[1:], s[0]
            if cur.Left != nil {
                s = append(s, cur.Left)
            }
            if cur.Right != nil {
                s = append(s, cur.Right)
            }
        }
    }
    return depth
}

I hope anyone can explain to me how Golang handles the := operators for multiple variables and at least one of which has already been declared in some other place. Also, you can put some official links which can prove your explanation.

2 Answers2

0

If any new variable is on the left of := (which is required by the compiler) then all variables are redefined. This shadows previous variables in previous scopes.

In the second code sample, you create a new s and append to it, but the s defined before the for was not modified.

Corey Ogburn
  • 24,072
  • 31
  • 113
  • 188
  • Weird... the documentation says the exact opposite. [Short variable declarations.](https://go.dev/ref/spec#Operators_and_punctuation) Where it says "Redeclaration does not introduce a new variable; it just assigns a new value to the original." I'm going to play around with the playground for a bit... – Corey Ogburn May 22 '22 at 07:40
  • [It behaves like all variables on the left side of := are reallocated and scoped.](https://go.dev/play/p/BkKQd1IiOQJ) – Corey Ogburn May 22 '22 at 07:45
  • For my second code snippet: ``` s, cur := s[1:], s[0] ``` why s doesn't assign a new value. Does it create a new local variable? – Jeffrey-Yang May 22 '22 at 07:47
  • It behaves that way yes. – Corey Ogburn May 22 '22 at 07:49
  • So If I want to reuse the previous s variable, I can only do ```cur := s[0] and s = s[1:]```. Is it correct ? – Jeffrey-Yang May 22 '22 at 07:53
  • That is one alternative. If you're in a situation where you can't split the assignment like this then you can `var cur *TreeNode / s, cur = s[0], s[1:]`. Such as if you were assigning from a function call returning multiple things. – Corey Ogburn May 22 '22 at 07:57
  • Yes, what you said is just my first code snippet. Anyway, thanks to all of you guys. – Jeffrey-Yang May 22 '22 at 07:59
  • The problem here is that you're in a new scope (due to the `for` loop). Short variable redeclaration occurs only if you're *not* in a new scope: `a := 3; a, b := 4, 5` overwrites the existing `a` while declaring a new `b`, but `a := 3; { a, b := 4, 5; }` creates a new `a` in the inner scope. (Personally, I pfind this particular peculiarity problematic. It's a little too easy to introduce a scope and change behavior.) – torek May 22 '22 at 09:14
0

The assignment operator := isn't that big of a villain as you think it is. The actual culprit in you code is the scope of variable s.

In the first code snippet, the var s is defined outside the first (outer) for-loop. Inside inner for-loop it was only assigned.

var s []*TreeNode           // Definition
s, cur = s[1:], s[0]        // Assignment

Whereas in second code snippet, both statements are defining the same variable. In other words, the second assignment is creating a different var s whose scope is limited to second (inner) for-loop.

var s []*TreeNode           // Definition
s, cur = s[1:], s[0]        // Definition + Assignment

"So why does this cause a difference?", is the question. I've added example data and printing logic to your code snippets in Playground. Check by running them both.
1st Snippet
2nd Snippet

If you see outputs of both, the first logic (with only 1 definition) gives the expected output. But second logic (2 definitions) goes into an infinite loop. This is because the scope of the second s which got defined inside the for-loop starts after the second definition and ends with iteration of the same for-loop.

For understanding, let's call the s defined above for-loop as s_outer and the s defined inside for-loop as s_inner. Logically both should mean the same thing, but due to difference in scope, they act as follows:

for i := size; i > 0; i-- {
    s_inner, cur := s_outer[1:], s_outer[0]
    if cur.Left != nil {
        s_inner = append(s_inner, cur.Left)
    }
    if cur.Right != nil {
        s_inner = append(s_inner, cur.Right)
    }
}

Since assignment statement is executed right-to-left, first the compiler checks for s at s[1:], s[0]. Since s_inner is yet to be defined, it will consider s to be s_outer. When it assigns, i.e., control is now on the left side of = sign in s, cur := s[1:], s[0], since it is a new definition as per := usage in Go, it takes new variable s_inner. But the scope of s_inner ends after the current iteration. i.e., after if cur.Right != nil {...}. And so, from 2nd iteration, the s value is taken as s_outer again and assigned to s_inner. This gets repeated, and hence, infinite loop!!


Baseline: If a variable is defined with the same name as an existing variable but in an inner scope, it acts like a different variable altogether.


I doubt these concepts cannot be efficiently explained using description. Moreover, this is more of a use-case than property of the := operator. So, the godoc and other pages must've explained it as definition+assignment. You face an issue, you research and you learn. That's the only mantra for these concepts.

vague
  • 409
  • 2
  • 5
  • Thanks for your wonderful reply. I checked the godoc about it. Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new. As a consequence, redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original. And I understand that only in the same block, it's a redeclaration, – Jeffrey-Yang May 22 '22 at 15:23
  • @Jeffrey-Yang: That's what I mentioned in my comment on Corey Ogburn's answer. You have a for loop, which has a new block scope, which causes the inner short-declaration to make a new `s` rather than redeclare the old `s`. – torek May 22 '22 at 21:05