I am using Golang1.14.
Here is the test code.
package main
import "time"
func checkData(data interface{}) {
if _, ok := data.([]string); ok {
println("Assert true.")
} else {
println("Assert false.")
}
}
func main() {
var years [20]string
for i := 0; i < 20; i++ {
years[i] = string(time.Now().Year() - 10 + i)
}
checkData(years)
foods := []string{"Fruit", "Grass", "Fish", "Meat"}
checkData(foods)
}
The output is:
Assert false.
Assert true.
I am new to Golang and really confusing that [20]string is not a []string.Can someone tell me why?Thanks.
[20]string is an array. It is a type that contains 20 strings, and if you pass it as an interface{}, you can recover it using intf.([20]string).
[]string is a slice. It has a backing array, but it is essentially a view over an array. You assertion checks if the interface is a slice, so this one works.
Arrays and slices are different things in Go. An array is a data type with a fixed size. For instance:
func f(arr [10]int) {...}
You can only call f with an int array of size 10. When you do call it, the array will be passes as value, so the function will get a copy of the array, all 10 members of it. But:
func f(arr []int) {...}
You can call f with any size of slice. A slice contains a reference to its underlying array, so an array copy will not take place here. You cannot call thisf` with an array.
Related
This question already has answers here:
Assign a new value to a struct field
(2 answers)
Closed 10 months ago.
The output of the following code surprises me:
package main
import (
"fmt"
)
type Thing struct {
mappings map[string]int
orderings []string
}
func NewThing() Thing {
t := Thing{}
t.mappings = make(map[string]int)
return t
}
func (t Thing) Add(s string) {
t.mappings[s] = 1
t.orderings = append(t.orderings, s)
}
func main() {
t := NewThing()
t.Add("foo")
if len(t.mappings) == len(t.orderings) {
fmt.Printf("Equal lengths: %v versus %v", t.mappings, t.orderings)
} else {
fmt.Printf("Unequal lengths: %v versus %v", t.mappings, t.orderings)
}
}
When run on the playground (https://play.golang.org/p/Ph67tHOt2Z_I) the output is this:
Unequal lengths: map[foo:1] versus []
I believe I'm treating the slice correctly; from my understanding it is initialized to nil in NewThing(), and is appended to in Add() (ensuring that the value returned from append is only assigned to its first argument).
Am I missing something incredibly obvious?
I looked at the following resources for an explanation:
https://gobyexample.com/slices - only uses either slice literals (i.e. not a struct field) or slices with set capacities, and I will not know the final size of t.orderings. It's my understanding that append should perform the extension and allocation automatically.
https://go.dev/blog/slices-intro - again, all demonstrations use slice literals. If the fields are moved out of the struct things work as expected. It's only once in the struct that this behavior occurs.
https://yourbasic.org/golang/gotcha-append/ - while it does describe behavior where append does not work as expected, the explanation involves append reusing memory when the slice has enough capacity for a new element, causing unexpected behavior when attempts to append the same array to two different copies. In my case, there is no reassignment of slice operations such as the one in this article, which is discouraged (some_var = append(some_other_var, elem)).
And I looked at the following questions for inspiration:
Go - append to slice in struct: the solution to this question was to assign the result of append back to the field, which I have done.
Correct way to initialize empty slice: the explanation is that slices don't have to be initialized, and can be left as nil and "appended to with allocation", so I believe I'm fine not initializing Thing.orderings.
Incase you don't want to use a pointer ,you can declare a global variable for Thing struct and assign it with the value of t from add function.Here is the code for the same logic :
package main
import (
"fmt"
)
var thing Thing
type Thing struct {
mappings map[string]int
orderings []string
}
func NewThing() Thing {
t := Thing{}
t.mappings = make(map[string]int)
return t
}
func (t Thing) Add(s string) {
t.mappings[s] = 1
t.orderings = append(t.orderings, s)
thing = t
}
func main() {
t := NewThing()
t.Add("foo")
if len(thing.mappings) == len(thing.orderings) {
fmt.Printf("Equal lengths: %v versus %v", thing.mappings, thing.orderings)
} else {
fmt.Printf("Unequal lengths: %v versus %v", thing.mappings, thing.orderings)
}
}
Output:
Equal lengths: map[foo:1] versus [foo]
I've started learning Go these days and got stuck in trying to pass a struct property's value (a slice) to a function. Apparently it's being passed as a reference (or it holds a pointer to its slice) and changes made inside the function affect it.
Here is my code, in which testFunction is supposed to receive a slice, remove its first 3 elements and print the updated values, but without affecting it externally:
package main
import (
"fmt"
)
type testStruct struct {
testArray []float64
}
var test = testStruct {
testArray: []float64{10,20,30,40,50},
}
func main() {
fmt.Println(test.testArray)
testFunction(test.testArray)
fmt.Println(test.testArray)
}
func testFunction(array []float64) {
for i:=0; i<3; i++ {
array = removeFrom(array, 0)
}
fmt.Println(array)
}
func removeFrom(array []float64, index int) []float64 {
return append(array[:index], array[index+1:]...)
}
That outputs:
[10 20 30 40 50]
[40 50]
[40 50 50 50 50]
My question is: what is causing the third fmt.Println to print this strange result?
Playground: https://play.golang.org/p/G8W3H085In
p.s.: This code is only an example. It's not my goal to remove the first elements of something. I just wanna know what is causing this strange behaviour.
Usually we don't know whether a given call to append will cause a reallocation, so we can't assume that the original slice refers to the same array as the resulting slice, nor that it refers to a different one.
To use slices correctly, it's important to remember that although the elements of the underlying array are indirect, the slice's pointer, length and capacity are not.
As a result, it's usual to assign the result of a call to append to the same slice variable:
array = append(array, ...)
So to sum up, to receive the desired result always remember to assign the append function to a new or the same slice variable.
Here is the corrected and working code:
package main
import (
"fmt"
)
type testStruct struct {
testArray []float64
}
var test = testStruct {
testArray: []float64{10,20,30,40,50},
}
func main() {
fmt.Println(test.testArray)
a := testFunction(test.testArray)
fmt.Println(a)
}
func testFunction(array []float64)[]float64 {
for i:=0; i<3; i++ {
array = removeFrom(array, 0)
}
fmt.Println(array)
return array
}
func removeFrom(array []float64, index int) []float64 {
return append(array[:index], array[index+1:]...)
}
Check it the working code on Go Playground.
Another solution is to pass the array argument via pointer reference:
func testFunction(array *[]float64) {
for i:=0; i<3; i++ {
*array = removeFrom(*array, 0)
}
fmt.Println(*array)
}
Go Playground
The slice is a composite type. It has a pointer to the data, the length and the capacity. When you pass it as an argument you're passing those values, the pointer, the length and the capacity; they are copies, always.
In your case you modify the data within the slice when you call removeFrom(), which you can do because you've copied the value of a pointer to the original data into the func, but the length and capacity remain unchanged outside the scope of that function as those are not pointers.
So, when you print it again from main() you see the altered values but it still uses the original length and capacity as any changes made to those within the scope of the other funcs were actually on copies of those values.
Here is a useful blog post about slices https://blog.golang.org/slices. It states this in particular.
It's important to understand that even though a slice contains a
pointer, it is itself a value. Under the covers, it is a struct value
holding a pointer and a length. It is not a pointer to a struct.
The reason you see [40 50 50 50 50] is because you changed the values in the slice, but you did not alter the slice itself(it's cap and len)
I am trying to create a generic function that can handle actions on slices in Go... for instance, append an item of any type to a slice of that same type. This is simply a generic purpose for a more complex solution, but overall the issue boils down to this example:
package main
type car struct {
make string
color string
}
type submarine struct {
name string
length int
}
func genericAppender(thingList interface{}, thing interface{}) []interface{} {
return append(thingList, thing)
}
func main() {
cars := make([]car, 0, 10)
cars[0] = car{make: "ford", color: "red"}
cars[1] = car{make: "chevy", color: "blue"}
subs := make([]submarine, 0, 10)
subs[0] = submarine{name: "sally", length: 100}
subs[1] = submarine{name: "matilda", length: 200}
newCar := car{make: "bmw", color: "white"}
genericAppender(&cars, newCar)
}
The code playground is at this location
The above errors as follows:
prog.go:14: first argument to append must be slice; have interface {}
After this change you're still getting a runtime error (index out of range) however the problem is that thingList is not of type []interface{} but rather interface{} so you can't append to it. Here's an updated version of your code on playground that does a type assertion to convert it to an []interface{} in line with the append. In reality you need to do that on a separate line and check for errors.
https://play.golang.org/p/YMed0VDZrv
So to put some code here;
func genericAppender(thingList interface{}, thing interface{}) []interface{} {
return append(thingList.([]interface{}), thing)
}
will solve the basic problem you're facing. As noted, you still get runtime errors when indexing into the slice. Also, you could change the argument to avoid this by making it;
func genericAppender(thingList []interface{}, thing interface{}) []interface{} {
return append(thingList, thing)
}
Here's a complete example of the second type; https://play.golang.org/p/dIuW_UG7XY
Note I also corrected the runtime error. When you use make with 3 args they are, in this order, type, length, capacity. This means the length of the array is 0 so when you try to assign to indexes 0 and 1 it was causing a panic for IndexOutoFRange. Instead I removed the middle argument so it's make([]interface{}, 10) meaning the length is initially set to 10 so you can assign to those indexes.
In the answer above if you do the following then it throws error. This is what the original question was about:
//genericAppender(subs, newCar). // Throws "cannot use subs (type []submarine) as type []interface {} in argument to genericAppender"
The trick is to convert your slice of specific type into a generic []interface{}.
func convertToGeneric(thingList interface{}) []interface{} {
input := reflect.ValueOf(thingList)
length := input.Len()
out := make([]interface{},length)
for i:=0 ;i < length; i++ {
out[i] = input.Index(i).Interface()
}
return out
}
This you can call the function like this:
genericAppender(convertToGeneric(subs), newCar)
You can check modified working code here: https://play.golang.org/p/0_Zmme3c8lT
With Go 1.19 (Q4 2022), no need for interface, or "convert your slice of specific type into a generic []interface{}"
CL 363434 comes with a new slices packages:
// Package slices defines various functions useful with slices of any type.
// Unless otherwise specified, these functions all apply to the elements
// of a slice at index 0 <= i < len(s).
package slices
import "constraints"
// Grow increases the slice's capacity, if necessary, to guarantee space for
// another n elements. After Grow(n), at least n elements can be appended
// to the slice without another allocation. If n is negative or too large to
// allocate the memory, Grow panics.
func Grow[S ~[]T, T any](s S, n int) S {
return append(s, make(S, n)...)[:len(s)]
}
// Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in index order, and the
// comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal.
func Equal[T comparable](s1, s2 []T) bool {
if len(s1) != len(s2) {
return false
}
for i, v1 := range s1 {
v2 := s2[i]
if v1 != v2 {
return false
}
}
return true
}
// ...
Ian Lance Taylor confirms in issue 45955:
This package is now available at golang.org/x/exp/slices.
Per this thread, it will not be put into standard library until the 1.19 release.
We may of course adjust it based on anything we learn about having it in x/exp.
Let's say a function takes a slice of strings:
func Join(strs []string) {
...
}
I have a single string:
a := "y'all ain't got the honey nut?"
How can I convert that string into a slice?
You can create a slice of one item using the following convention:
a := "y'all ain't got the honey nut?"
singleItemArray := []string{a}
strings.Join(singleItemArray);
The actual answer to your question is as simple as []string{"string"}, as miltonb said.
But what I wanted to point out is how easy it is to write and use a variadic function in Go, a function with a variable number of arguments.
You can change signature of your function to F(a ...string). Then, a is slice in the function F, and you can call it like F("a") and F("a", "b"). And when you actually have a slice or array, you can pass it to F by calling F(a...).
Not sure if this syntax fits your job, but I wanted to let you know about it as an option.
The question as phrased actually references Arrays and Slices. The question text is about an array and the code is illustrating using a slice. Therefore there two questions are implied; pass a single item slice, and pass a single item array.
An array: var a [1]string
A slice: var s []string
Passing a single item slice to the function:
func SliceFunc( slc []string) {
fmt.Println(slc)
}
func main() {
a := "stringy"
SliceFunc( []string{a} )
// or an actual array to the same function
b := [...]string { "thingy" }
SliceFunc( []string{b[0] )
}
Passing a single item array to the function.
Here there is an issue, as an array has a fixed length and as a parameter to a function it cannot accept different length arrays so we are left with working function which has limited flexibility:
func ArrayFunc( arr [1]string) {
fmt.Println(slc)
}
func main() {
var a [1]string
a[0] = "stringy"
ArrayFunc( a )
}
It seems that as a generalization sticking to slices is a more flexible solution.
(If you would like more on Slices and Arrays here one blog by Andrew Gerrand covering go slices usage and internals.)
You can utilize append or make:
package main
import "fmt"
func main() {
{
var a []string
a = append(a, "north")
fmt.Println(a)
}
{
a := make([]string, 1)
a[0] = "north"
fmt.Println(a)
}
}
https://golang.org/pkg/builtin
Go has stumped me again. Hopefully someone can help. I've created a slice (mySlice) that contains pointers to structs (myStruct).
The problem is the "Remove" method. When we're inside "Remove" everything is fine, but once we return, the slice size hasn't changed, and so we see the last element listed twice.
I originally tried writing "Remove" using the same pattern used in the "Add" method, but it wouldn't compile and has been commented out.
I can get it to work by returning the newly created slice to the calling function, but I don't want to do this because mySlice (ms) is a singleton.
And if I hadn't asked enough already...
The code for the "Add" method is working, although I'm not sure how. From what I can gather "Add" is receiving a pointer to the slice header (the 3 item "struct"). From what I've read, the length and capacity of an slice don't get passed to methods (when passing by value), so perhaps passing a pointer to the slice allows the method to see and use the length and capacity thereby allowing us to "append". If this is true, then why doesn't the same pattern work in "Remove"?
Thanks very much for everyone's insights and help!
package main
import (
"fmt"
)
type myStruct struct {
a int
}
type mySlice []*myStruct
func (slc *mySlice) Add(str *myStruct) {
*slc = append(*slc, str)
}
//does not compile with reason: cannot slice slc (type *mySlice)
//func (slc *mySlice) Remove1(item int) {
// *slc = append(*slc[:item], *slc[item+1:]...)
//}
func (slc mySlice) Remove(item int) {
slc = append(slc[:item], slc[item+1:]...)
fmt.Printf("Inside Remove = %s\n", slc)
}
func main() {
ms := make(mySlice, 0)
ms.Add(&myStruct{0})
ms.Add(&myStruct{1})
ms.Add(&myStruct{2})
fmt.Printf("Before Remove: Len=%d, Cap=%d, Data=%s\n", len(ms), cap(ms), ms)
ms.Remove(1) //remove element 1 (which also has a value of 1)
fmt.Printf("After Remove: Len=%d, Cap=%d, Data=%s\n", len(ms), cap(ms), ms)
}
and the results...
Before Remove: Len=3, Cap=4, Data=[%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{1}) %!s(*main.myStruct=&{2})]
Inside Remove = [%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{2})]
After Remove: Len=3, Cap=4, Data=[%!s(*main.myStruct=&{0}) %!s(*main.myStruct=&{2}) %!s(*main.myStruct=&{2})]
You were right the first time with Remove1(). Remove gets a copy of the slice and therefore cannot change the length of the slice.
The issue in your remove function is that according to order of operations in Go, slicing comes before dereferencing.
The fix is to change *slc = append(*slc[:item], *slc[item+1:]...) to *slc = append((*slc)[:item], (*slc)[item+1:]...).
However I would recommend the following for readability and maintainability:
func (slc *mySlice) Remove1(item int) {
s := *slc
s = append(s[:item], s[item+1:]...)
*slc = s
}
Because append would not necessarily return the same address of reference to the slice, as Stephen Weinberg has pointed out.
Another way to workaround with this limitation is defining a struct that wraps the slice.
for example:
package main
import "fmt"
type IntList struct {
intlist []int
}
func (il *IntList) Pop() {
if len(il.intlist) == 0 { return }
il.intlist = il.intlist[:len(il.intlist)-1]
}
func (il *IntList) Add(i... int) {
il.intlist = append(il.intlist, i...)
}
func (il *IntList) String() string {
return fmt.Sprintf("%#v",il.intlist)
}
func main() {
intlist := &IntList{[]int{1,2,3}}
fmt.Println(intlist)
intlist.Pop()
fmt.Println(intlist)
intlist.Add([]int{4,5,6}...)
fmt.Println(intlist)
}
output:
[]int{1, 2, 3}
[]int{1, 2}
[]int{1, 2, 4, 5, 6}