Passing an array as an argument in golang

77,854

Solution 1

You have defined your function to accept a slice as an argument, while you're trying to pass an array in the call to that function. There are two ways you could address this:

  1. Create a slice out of the array when calling the function. Changing the call like this should be enough:

    nameReader(a[:])
    
  2. Alter the function signature to take an array instead of a slice. For instance:

    func nameReader(array [3]name) {
        ...
    }
    

    Downsides of this solution are that the function can now only accept an array of length 3, and a copy of the array will be made when calling it.

You can find a more details on arrays and slices, and common pitfalls when using them here: http://openmymind.net/The-Minimum-You-Need-To-Know-About-Arrays-And-Slices-In-Go/

Solution 2

Since @james-henstridge's answer already covered how you could make it work, I won't duplicate what he said, but I will explain why his answer works.

In Go, arrays work a bit differently than in most other languages (yes, there are arrays and slices. I'll discuss slices later). In Go, arrays are fixed-size, as you use in your code (so, [3]int is a different type than [4]int). Additionally, arrays are values. What this means is that if I copy an array from one place to another, I'm actually copying all of the elements of the array (instead of, as in most other languages, just making another reference to the same array). For example:

a := [3]int{1, 2, 3} // Array literal
b := a               // Copy the contents of a into b
a[0] = 0
fmt.Println(a)       // Prints "[0 2 3]"
fmt.Println(b)       // Prints "[1 2 3]"

However, as you noticed, Go also has slices. Slices are similar to arrays, except in two key ways. First, they're variable length (so []int is the type of a slice of any number of integers). Second, slices are references. What this means is that when I create a slice, a piece of memory is allocated to represent the contents of the slice, and the slice variable itself is really just a pointer to that memory. Then, when I copy that slice around, I'm really just copying the pointer. That means that if I copy the slice and then change one of the values, I change that value for everybody. For example:

a := []int{1, 2, 3} // Slice literal
b := a              // a and b now point to the same memory
a[0] = 0
fmt.Println(a)      // Prints "[0 2 3]"
fmt.Println(b)      // Prints "[0 2 3]"

Implementation

If that explanation was pretty easily understandable, then you might also be curious to know how this is implemented (if you had trouble understanding that, I'd stop reading here because the details will probably just be confusing).

Under the hood, Go slices are actually structs. They have a pointer to the allocated memory, like I mentioned, but they also have two other key components: length and capacity. If it were described in Go terms, it'd look something like this:

type int-slice struct {
    data *int
    len  int
    cap  int
}

The length is the length of the slice, and it's there so that you can ask for len(mySlice), and also so that Go can check to make sure you're not accessing an element that's not actually in the slice. The capacity, however, is a bit more confusing. So let's dive a bit deeper.

When you first create a slice, you give a number of elements that you want the slice to be. For example, calling make([]int, 3) would give you a slice of 3 ints. What this does is allocate space in memory for 3 ints, and then give you back a struct with a pointer to the data, the length of 3, and the capacity of 3.

However, in Go, you can do what's called slicing. This is basically where you create a new slice out of an old slice that represents only part of the old slice. You use the slc[a:b] syntax to refer to the sub-slice of slc starting at index a and ending just before index b. So, for example:

a := [5]int{1, 2, 3, 4, 5}
b := a[1:4]
fmt.Println(b) // Prints "[2 3 4]"

What this slicing operation does under the hood is to make a copy of the struct that corresponds to a, and to edit the pointer to point 1 integer forward in memory (because the new slice starts at index 1), and edit the length to be 2 shorter than before (because the old slice had length 5, while the new one has length 3). So what does this look like in memory now? Well, if we could visualize the integers laid out, it'd look something like this:

  begin     end  // a
  v         v
[ 1 2 3 4 5 ]
    ^     ^
    begin end    // b

Notice how the there's still one more int after the end of b? Well that's the capacity. See, so long as the memory's going to be sticking around for us to use, we might as well be able to use all of it. So even if you only have a slice whose length is small, it will remember that there's more capacity in case you ever want it back. So, for example:

a := []int{1, 2, 3}
b := a[0:1]
fmt.Println(b) // Prints "[1]"
b = b[0:3]
fmt.Println(b) // Prints "[1 2 3]"

See how we do b[0:3] at the end there? The length of b is actually less than 3 at this point, so the only reason we're able to do that is that Go has kept track of the fact that, in the underlying memory, we've actually got more capacity reserved. That way, when we ask for some of it back, it can happily oblige.

Solution 3

An alternative approach

One could invoke a variadic function on the a slice to input the list of names as individual arguments to the nameReader function, eg:

package main

import "fmt"

type name struct {
    X string
}

func main() {
    a := [3]name{{"Abbed"}, {"Ahmed"}, {"Ghassan"}}
    nameReader(a[:]...)
}

func nameReader(a ...name) {
    for _, n := range a {
        fmt.Println(n.X)
    }
}

Solution 4

Instead of declaring a sized array, declare a slice. Allocate memory to it and fill it in. Once you have that, the original reference is still to the slice, which can be passed around to a function. Consider this simple example

package main

import "fmt"

func main() {
  // declare a slice
  var i []int

  i = make([]int, 2)
  i[0] = 3
  i[1] = 4

  // check the value - should be 3
  fmt.Printf("Val - %d\n", i[0])

  // call the function  
  a(i)

  // check the value again = should be 33
  fmt.Printf("Val - %d\n", i[0])  
}

func a(i []int) {
  // check the value - should be 3
  fmt.Printf("Val - %d\n", i[0])  

  // change the value
  i[0] = 33

  // check the value again = should be 33
  fmt.Printf("Val - %d\n", i[0])  
}

As you can see the array got passed (as a reference) and can be modified by the corresponding function.

Output looks like this:

Val - 3
Val - 3
Val - 33
Val - 33

The whole program can also be found at: http://play.golang.org/p/UBU56eWXhJ

Solution 5

Passing Arrays as parameters.

Arrays values are treated as a single unit. An array variable is not a pointer to a location in memory, but rather represents the entire "Block of Memory" containing the array elements. This has the implications of creating a new copy of an array value when the array variable is reassigned or passed in as a function parameter. Learning Go Programming, By Vladimir Vivien

This could have unwanted side effects on memory consumption for a program. You can fix this using "pointer types" to reference array values. For instance:

instead do this:

var numbers [1024*1024]int

you must do:

type numbers [1024*1024]int
var nums *numbers = new(numbers)

Remember that:

https://golang.org/pkg/builtin/#new

The new built-in function allocates memory. The first argument is a type, not a value, and the value returned is a pointer to a newly allocated zero value of that type.

Now you could pass the array pointer to the function without the side effect of memory consumption and use it as you want.

nums[0] = 10
doSomething(nums)

func doSomething(nums *numbers){
  temp := nums[0]
  ...
}

One thing to keep on mind is that array type is a low-level storage construct in Go and is used as the basis for storage primitives, where there are strict memory allocation requirements to minimize space consumption. For those cases where your requirement rely on the performance your must choose to work with arrays (like the previous example) instead of slices.

Share:
77,854
user1721803
Author by

user1721803

Updated on July 08, 2022

Comments

  • user1721803
    user1721803 almost 2 years

    Why does this not work?

    package main
    
    import "fmt"
    
    type name struct {
        X string
    }
    
    func main() {
        var a [3]name
        a[0] = name{"Abbed"}
        a[1] = name{"Ahmad"}
        a[2] = name{"Ghassan"}
    
        nameReader(a)
    } 
    
    func nameReader(array []name) {
        for i := 0; i < len(array); i++ {
            fmt.Println(array[i].X)
        }
    }
    

    Error:

    .\structtest.go:15: cannot use a (type [3]name) as type []name in function argument
    
  • user1721803
    user1721803 over 10 years
    Is there a way at all to make the function accept an array?
  • James Henstridge
    James Henstridge over 10 years
    That's what the second option I outline does. But as I said, the downside is that it will only accept arrays of length 3 with that change: if you want to accept sequences of arbitrary length, you'll need to work with slices.
  • Paul Hankin
    Paul Hankin over 10 years
    Upvoted, but a := []name{"Abbed", "Ahmad", "Ghassan"} is how I'd have defined a as a slice in the first place.
  • peterSO
    peterSO over 10 years
    Or people could simply read Rob's blog article: Arrays, slices (and strings): The mechanics of 'append'.
  • ymg
    ymg over 10 years
    darn we should have [...]type in func signatures in next releases :(
  • James Henstridge
    James Henstridge over 10 years
    @YasirG.: it isn't clear how that could be done safely, given that arrays are passed by value and don't store their size (it is implicit from the type). And we've already got slices to handle the arbitrary length sequences.
  • joshlf
    joshlf over 10 years
    ah, yes, that does seem more reasonable... I'd forgotten about that.
  • Hamza Anis
    Hamza Anis about 7 years
    Yes, It is the best method but I think the question asked was not addressing this method.
  • Stefan Pochmann
    Stefan Pochmann almost 7 years
    You say sub-slicing b := a[1:4] increases the pointer by 1 and decreases the length by 2. But the capacity remains the same? Shouldn't it decrease by 1 as well?
  • George Stocker
    George Stocker about 6 years
    So what's the right way to have a function take an array of {type} without having to know the size of the array at compile time?
  • George Stocker
    George Stocker about 6 years
    So what's the right way to have a function take an array of {type} without having to know the size of the array at compile time?
  • Grijesh Chauhan
    Grijesh Chauhan over 5 years
    @StefanPochmann yes, b's capacity is decreased by 1, and as per the declaration of b := a[1: 4], b's length should be 3 and capacity should be 4. The diagram and some information in the answer is confusing and misleading, though the first part is interesting