When functional design encounters slices

Permalink to this article – https://ift.tt/MaYNpds

Slice is an important and most commonly used isomorphic data type in Go language. In the Go language coding process, we will use slices instead of arrays in most cases, firstly because of its dynamic scalability, and secondly, the overhead of passing slices is smaller than passing arrays in most cases ( there are some exceptions ).

Slices count as a type where “half” zero values ​​are available , so why do you say that?

When we declare an instance of a slice type without explicitly initializing it, we cannot subscript it directly, for example:

 var sl []int sl[0] = 5 // 错误:引发panic

But we can append it with Go’s built-in append function, even if the current value of sl is nil:

 var sl []int sl = append(sl, 5) // ok

At this point, I want to propose the topic to be discussed in this article: why does the append function return the slice result through the return value ? A little more generalization: when you encounter the incoming and outgoing slice types in the function design process, how do you design the parameters and return values ​​of the function ? Let’s discuss it below.

We found the prototype of the append preset function in $GOROOT/src/builtin/builtin.go:

 func append(slice []Type, elems ...Type) []Type

Obviously, referring to the design of the “append” function, passing in slices through parameters and passing out the updated slices through return values ​​is definitely a correct solution , such as the first version of the MyAppend function below:

 func myAppend1(sl []int, elems ...int) []int { return append(sl, elems...) } func main() { var in = []int{1, 2, 3} fmt.Println("in slice:", in) // 输出:in slice: [1 2 3] fmt.Println("out slice:", myAppend1(in, 4, 5, 6)) // 输出:out slice: [1 2 3 4 5 6] }

At this point, some beginners will ask: Isn’t slice a dynamic array? Can it be used as both an input parameter and an output parameter? I understand that the little friends who asked this question want to design a function prototype like the following:

 func myAppend2(sl []int, elems ...int)

Here sl is passed into myAppend2 as an input parameter, then after myAppend2 updates it, the caller of the myAppend2 function will get the updated sl. But is this actually the case? Let’s take a look:

 func myAppend2(sl []int, elems ...int) { sl = append(sl, elems...) } func main() { var inOut = []int{1, 2, 3} fmt.Println("in slice:", inOut) myAppend2(inOut, 4, 5, 6) fmt.Println("out slice:", inOut) }

Running this program, we get the following results:

 in slice: [1 2 3] out slice: [1 2 3]

We see that myAppend2 is not working as we expected, incoming slices are not getting updated as expected in myAppend2, why is that? The first is related to the representation of slices at runtime. There are detailed explanations of slice representation at runtime in my column and the book “The Road to Go Language Improvement” . Here is a brief description:

Slices are made up of three fields at runtime, and the reflect package has the corresponding definitions for how slices are represented in the type system:

 // $GOROOT/src/reflect/value.go type SliceHeader struct { Data uintptr // 指向底层数组的指针Len int // 切片长度Cap int // 切片容量}

In addition, the Go function adopts a “copy-by-value” parameter passing method, which means that the slice sl passed by myAppend2 essentially only passes the slice “descriptor” – SliceHeader. The body of the myAppend2 function changes the values ​​of the fields of the formal parameter sl, but the actual parameters of myAppend2 are not affected in any way, that is, after the execution of myAppend2, the len and cap of inOut remain unchanged, and whether the underlying array has changed? ? In this example, it must be “changed”, but the changes are the elements outside the inOut length (len) and within the cap, which cannot be obtained through regular access to inOut.

So how do we make slices as in/out parameters? The answer is to use a pointer to a slice, let’s look at the following example:

 func myAppend3(sl *[]int, elems ...int) { (*sl) = append(*sl, elems...) } func main() { var inOut = []int{1, 2, 3} fmt.Println("in slice:", inOut) // in slice: [1 2 3] myAppend3(&inOut, 4, 5, 6) fmt.Println("out slice:", inOut) // out slice: [1 2 3 4 5 6] }

We see that myAppend3 function uses *[]int type parameters to solve the problem of slice parameters as input and output parameters: the changes to the slice by myAppend3 are reflected on the slice represented by the inOut variable, even if the slice is sliced ​​in myAppend3 After dynamic expansion, inOut can also “capture” this.

But I looked in the Go standard library and there are “poorly few” functions that take a pointer to a slice as an argument:

 $grep "*\[\]" */*go|grep func grep: cmd/cgo: Is a directory grep: cmd/go: Is a directory grep: runtime/cgo: Is a directory log/log.go:func itoa(buf *[]byte, i int, wid int) { log/log.go:func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { regexp/onepass.go:func mergeRuneSets(leftRunes, rightRunes *[]rune, leftPC, rightPC uint32) ([]rune, []uint32) { regexp/onepass.go: extend := func(newLow *int, newArray *[]rune, pc uint32) bool { runtime/mstats.go:func readGCStats(pauses *[]uint64) { runtime/mstats.go:func readGCStats_m(pauses *[]uint64) { runtime/proc.go:func saveAncestors(callergp *g) *[]ancestorInfo {

To sum up, when we encounter slice type data in function design, if we want to update the slice, we should first refer to the design scheme of the append function, that is, the operation logic is realized by using the slice as an input parameter and return value. Slices can also be passed as pointers to slices if necessary, as myAppend3 does.


“Gopher Tribe” Knowledge Planet aims to create a high-quality Go learning and advanced community! High-quality first published Go technical articles, “three-day” first published reading rights, analysis of the current situation of Go language development twice a year, reading the fresh Gopher daily 1 hour in advance every day, online courses, technical columns, book content preview, must answer within 6 hours Guaranteed to meet all your needs about the Go language ecosystem! In 2022, the Gopher tribe will be fully revised, and will continue to share knowledge, skills and practices in the Go language and Go application fields, and add many forms of interaction. Everyone is welcome to join!

img{512x368}

img{512x368}

img{512x368}

img{512x368}

I love texting : Enterprise-level SMS platform customization development expert https://51smspush.com/. smspush : A customized SMS platform that can be deployed within the enterprise, with three-network coverage, not afraid of large concurrent access, and can be customized and expanded; the content of the SMS is determined by you, no longer bound, with rich interfaces, long SMS support, and optional signature. On April 8, 2020, China’s three major telecom operators jointly released the “5G Message White Paper”, and the 51 SMS platform will also be newly upgraded to the “51 Commercial Message Platform” to fully support 5G RCS messages.

The famous cloud hosting service provider DigitalOcean released the latest hosting plan. The entry-level Droplet configuration is upgraded to: 1 core CPU, 1G memory, 25G high-speed SSD, and the price is 5$/month. Friends who need to use DigitalOcean can open this link : https://ift.tt/rfhkPmM to open your DO host road.

Gopher Daily Archive Repository – https://ift.tt/DawUEsY

my contact information:

  • Weibo: https://ift.tt/JkryQBD
  • Blog: tonybai.com
  • github: https://ift.tt/xIj24af

Business cooperation methods: writing, publishing books, training, online courses, partnership entrepreneurship, consulting, advertising cooperation.

© 2022, bigwhite . All rights reserved.

This article is reprinted from https://tonybai.com/2022/10/27/when-encountering-slice-during-function-design/
This site is for inclusion only, and the copyright belongs to the original author.