通过值传递导致sync.WaitGroup死锁?

There's 2 ways to write the repro:

The 1st way, the program exits clean.

func recurse(depth int, wg *sync.WaitGroup) {
    defer wg.Done()
    if depth == 0 {
        return
    }
    wg.Add(1)
    go recurse(depth - 1, wg)
}

func main() {
    wg := sync.WaitGroup{}
    wg.Add(1)
    go recurse(3, &wg)
    wg.Wait()
}

The 2nd way, the program gives "fatal error: all goroutines are asleep - deadlock!"

func recurse(depth int, wg sync.WaitGroup) {
    defer wg.Done()
    if depth == 0 {
        return
    }
    wg.Add(1)
    go recurse(depth - 1, wg)
}

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go recurse(3, wg)
    wg.Wait()
}

Would anyone kindly explain the intricate way in which the 2nd way differs from the 1st way so as to cause "Deadlock"?

WaitGroup under the hood is just a struct containing a counter guarded by mutex. Go provide arguments to functions copy_by_value way. So recurse( depth, wg) function receive only a copy of counter when passed by value. Like this:

counter := 5
func(counter){
    counter--
    fmt.Println(counter) //will print "4"
}(counter)
fmt.Println(counter) //will be "5" again