Thanks, Edward, you make a good point.

What's more, I wrote more code yesterday, and found that we can in fact get
the 4th element even if the descriptor tells us there are only 3, by
using package
unsafe <https://golang.org/pkg/unsafe/>, and doing some simple pointer
calculation. It shows that function calls may modify the slice in
parameters under the cover, which is a bad thing if it is not on purpose. I
will keep this in mind.

Thanks.




2016-09-13 12:09 GMT+08:00 Edward Muller <edwar...@interlix.com>:

>
>
> On Mon, Sep 12, 2016 at 8:17 PM Fei Ding <fding...@gmail.com> wrote:
>
>> Hi guys:
>>
>> I met a problem when I try to understand slice when using as function
>> parameters. After writing some code, I made a conclusion by myself, but
>> need some real check and explanation here, with your help.
>>
>> As far as I know, Golang's function parameters are passed by value, and
>> slice could be treated as a descriptor, which seems like *{pointer to
>> first element of underlying array, length of element, capacity of array}*
>> according to Go Slices: usage and internals
>> <https://blog.golang.org/go-slices-usage-and-internals>. So, can I say:
>>
>> *When passing a slice as a function parameter, it is, in fact, passing a
>> pointer, and two int values (or as a whole struct, or maybe more parameters
>> to describe the slice, whatever).*
>>
>> To make it clear using code:
>>
>> func foo(s []T) {...}
>> // can this function be treated as following 3-parameter function?
>> func foo(p Pointer, length, capa int) {...}
>>
>> // Note: type is not the point here,
>> //       and we just need 3 parameters, while there maybe more
>>
>> You may wonder why I have this question? Here is where everything started:
>>
>> func foo(s []int) {
>>  s := []int{1,2,3}
>>  foo(s)
>>  fmt.Println(s)
>> }
>>
>> Everyone know it will print [1 2 3], not [1 2 3 100]. But what I am
>> interested in, is *whether the function call does modify the memory*
>> just after the tail of element 3, to an int of 100. If my assumption above
>> is right, then the modification may happen.
>>
>> So, I made some experiments.
>>
>> package main
>>
>> import "fmt"
>> import "strconv"
>>
>> type Node struct {
>> val int
>> left *Node
>> right *Node
>> }
>>
>> func (n Node) String () string {
>> return strconv.Itoa(n.val)
>> }
>>
>> func newNode(val int) *Node {
>> return &Node{val, nil, nil}
>> }
>>
>> // Given a binary tree and a target sum, find all root-to-leaf paths
>> // where each path's sum equals the given sum
>> func bs(root *Node, path []*Node, now int, target int, ret *[][]*Node) {
>> if root == nil {
>> return
>> }
>> path = append(path, root)
>> now += root.val
>> if now == target && root.left == nil && root.right == nil {
>> *ret = append(*ret, path)
>> return
>> }
>> bs(root.left, path, now, target, ret)
>> bs(root.right, path, now, target, ret)
>> }
>>
>> func main() {
>> // a simple tree like:
>> //    0
>> //   / \
>> //  1   2
>> root := newNode(0)
>> left := newNode(1)
>> right := newNode(2)
>> root.left = left
>> root.right = right
>>
>> ret := [][]*Node{}
>> bs(root, make([]*Node, 0, 10), 0, 1, &ret)
>> fmt.Println(ret)
>> }
>>
>>
>> As the code above, it is a function to find all root-to-leaf paths where
>> each path's sum equals the given sum in a binary tree, and, I make a simple
>> test case which there are only 3 nodes: one root, two children, with values
>> of 0, 1 and 2.
>>
>> Say, I want to find the paths of sum of 1. So, I call this function as:
>>
>> bs(root, make([]*Node, 0, 10), 0, 1, &ret)
>>
>> It is petty common to make a guess that, this call will give us a final
>> result of [0, 1], which is obviously the correct answer, however, it gives
>> us [0, 2], try yourself if you don't believe:
>>
>> https://play.golang.org/p/hSKIOaVK2S
>>
>> The algorithm here is correct, don't worry about it. Instead, please pay
>> attention to the second parameter of bs(). It makes a slice which has no
>> element in it, and has a capacity of 10. What if we change this parameter
>> to:
>>
>> bs(root, make([]*Node, 0, 1), 0, 1, &ret)
>>
>> Yes, just make the slice's capacity as 1. This time you will get the
>> right answer, [0, 1]. Try yourself if you are interested.
>>
>> Here is my understanding of this strange problem, feel free to point out
>> anything wrong:
>>
>> Still the simplest code:
>>
>> package main
>>
>> import (
>> "fmt"
>> )
>>
>> func foo(s []int) {
>> fmt.Println(len(s), cap(s))   // 3 3
>> s = append(s, 100)
>> fmt.Println(len(s), cap(s))   // 4 8
>> }
>>
>> func main() {
>> s := []int{1,2,3}
>> foo(s)
>>         fmt.Println(len(s), cap(s))   // 3 3
>> fmt.Println(s)                // [1 2 3]
>> }
>>
>> When the function foo() called, it has 3 parameters: a pointer of the
>> first element of the array, which points to the element 1, an int for array
>> length: 3, an int for array capacity: 3.  In foo(), it tries to append 100
>> at the tail, but there is no room for it, according to the parameter of
>> capacity it receive, so, it makes a larger array, which capacity is 8, then
>> do some copy-and-append job. Unfortunately, all the work it does is useless
>> because all three parameters is still themselves. So, at last, it prints [1
>> 2 3]
>>
>> But, what will happen if the slice passed into foo() is large enough
>> already?
>>
>> package main
>>
>> import (
>> "fmt"
>> )
>>
>> func foo(s []int) {
>> fmt.Println(len(s), cap(s))   // 3 10
>> s = append(s, 100)
>> fmt.Println(len(s), cap(s))   // 4 10
>> }
>>
>> func main() {
>> s := make([]int, 3, 10)
>> s[0], s[1], s[2] = 1, 2, 3
>> foo(s)
>>         fmt.Println(len(s), cap(s))   // 3 10
>> fmt.Println(s)                // [1 2 3]
>> }
>>
>> Now, foo() is lucky, he does not need to allocate any memory, he just
>> append 100 at the tail, the descriptor now becomes {address somewhere, 4,
>> 10}. However, in main, it is still {address somewhere, 3, 10} on account of
>> the pass-by-value-calling-rule, *so, we just care about the first 3
>> elements of the array, and ignore others because the descriptor tells us
>> to, even though there does exist the 4th one.*
>>
>> How to prove there is a 4th one? Which is equal to ask, how to prove the
>> function call modify the passed slice? Check the path-of-sum algorithm
>> above, it may show something indirectly.
>>
>> The algorithm starts from the root, 0, then goes to his left child, 1,
>> there it meets the target sum,  1 (0+1), so it append 1 into the path slice
>> (which now is [0,1], descriptor as {address, 2, 10}) and append the path
>> slice into ret. After this, it will find no child exists, so it returns
>> from current position and goes back to the root to check root's right
>> child. Now, the path slice's descriptor is {address, 1, 10}. When checking
>> the right child, it append 2 into path, which does modify the memory where
>> 1 sits already. At last, it will print [0, 2] as the wrong result.
>>
>> This tells us one thing: function calls may modify your passed slice, but
>> the slice descriptor keeps you from knowing it.
>>
>
> Yes, the slice header is copied when passed to a function, not the backing
> array. Also note, `append` return a modified slice as it may need to create
> a new backing array and copy over the contents of the existing backing
> array if needed to ensure sufficient capacity. Similarly if you write a
> function that modifies a slice in some way you should return a slice (could
> be the same slice).
>
>
>>
>> My personal explanation is done, and here are my questions:
>>
>> 1. Am I right? What's wrong?
>> 2. If I am right, mostly, how to prevent it gracefully? Any rules, or any
>> blogs I can learn from?
>>
>
> Prevent what exactly? If you know the eventual capacity of your slice,
> just create one with that capacity to start with. The go blog you listed
> above is good.
>
>
>> 3. Any better code snippet to do some experiment to make it more
>> convincing? Does packet reflect <https://golang.org/pkg/reflect/>
>> helpful? can I just print the 4th element even though the slice descriptor
>> tell me its length is 3?
>>
>
> You can't, or at the very least shouldn't try. These are effectively
> implementation details of how slices work. Why do you feel the need to
> prevent/work against slices?
>
>
>> 4. Where can I find the code of implementation of slice?
>>
>
>  https://golang.org/src/runtime/slice.go
>
>
>> Thanks a lot.
>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "golang-nuts" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to golang-nuts+unsubscr...@googlegroups.com.
>> For more options, visit https://groups.google.com/d/optout.
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to