Hello Ding Actually you don't need unsafe or reflect to access the 4th element : https://play.golang.org/p/wZAkKtAZei See section "Full slice expressions" <https://golang.org/ref/spec#Slice_expressions> in spec about 3-index slicing like a[low : high : max]
You were correct in deducing that slices are descriptor values "passed around" by value, while the backing array contents are accessed by reference through the pointer contained in the slice descriptor. This structure must be well understood by the programmer to avoid headaches. My rules of thumb are : - avoid pointer to slice e.g. *[]int - never call append without assigning back to the same variable - be careful with concurrency e.g. append is not safe to call concurrently - be careful about which piece of code "owns" the backing array. This is important when several functions try to read, write, grow a slice. Problems may arise even without goroutines. - the result of append may use the same backing array as its argument, or not. You shouldn't rely on either way. Cheers Valentin On Tuesday, September 13, 2016 at 10:15:19 AM UTC+2, Fei Ding wrote: > > 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 <edwa...@interlix.com > <javascript:>>: > >> >> >> On Mon, Sep 12, 2016 at 8:17 PM Fei Ding <fdin...@gmail.com <javascript:>> >> 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...@googlegroups.com <javascript:>. >>> 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.