37
votes

I'm trying to write a function that modifies original map that is passed by pointer but Go does not allow it. Let's say I have a big map and don't want to copy it back and forth.

The code that uses passing by value is working and is doing what I need but involves passing by value (playground):

package main

import "fmt"

type Currency string

type Amount struct {
    Currency Currency
    Value float32
}

type Balance map[Currency]float32

func (b Balance) Add(amount Amount) Balance {
    current, ok := b[amount.Currency]
    if ok {
        b[amount.Currency] = current + amount.Value
    } else {
        b[amount.Currency] = amount.Value
    }
    return b
}

func main() {
    b := Balance{Currency("USD"): 100.0}
    b = b.Add(Amount{Currency: Currency("USD"), Value: 5.0})

    fmt.Println("Balance: ", b)
}

But if I try to pass parameter as pointer like here (playground):

func (b *Balance) Add(amount Amount) *Balance {
    current, ok := b[amount.Currency]
    if ok {
        b[amount.Currency] = current + amount.Value
    } else {
        b[amount.Currency] = amount.Value
    }
    return b
}

I'm getting compilation error:

prog.go:15: invalid operation: b[amount.Currency] (type *Balance does not support indexing)
prog.go:17: invalid operation: b[amount.Currency] (type *Balance does not support indexing)
prog.go:19: invalid operation: b[amount.Currency] (type *Balance does not support indexing)

How should I deal with this?

2

2 Answers

52
votes

You are trying to index on the pointer rather than the map itself. Kind of confusing because usually with pointers vs. values dereferencing is automatic for structs. If your struct is just a map, however, it's only passed in by reference anyway so you don't have to worry about creating methods that act on pointers to avoid copying the entire structure every time. The following code is equivalent to your first snippet but using a pointer type.

package main

import "fmt"

type Currency string

type Amount struct {
    Currency Currency
    Value float32
}

type Balance map[Currency]float32

func (b *Balance) Add(amount Amount) *Balance {
    current, ok := (*b)[amount.Currency]
    if ok {
        (*b)[amount.Currency] = current + amount.Value
    } else {
        (*b)[amount.Currency] = amount.Value
    }
    return b
}

func main() {
    b := &Balance{Currency("USD"): 100.0}
    b = b.Add(Amount{Currency: Currency("USD"), Value: 5.0})

    fmt.Println("Balance: ", (*b))
}

But to answer how to deal with it: if your struct is just of type map, I wouldn't worry about writing your receiving functions to take pointers, and just receive the value since the value is only a reference anyways. Do like in your original snippet.

15
votes

You can simply dereference b: (*b)

https://play.golang.org/p/Xq6qFy4_PC

func (b *Balance) Add(amount Amount) *Balance {
    current, ok := (*b)[amount.Currency]
    if ok {
        (*b)[amount.Currency] = current + amount.Value
    } else {
        (*b)[amount.Currency] = amount.Value
    }
    return b
}

Update

@Serdmanczyk makes a good point... you can safely pass a map around by value, the underlying map will be updated, not a copy of the map. That is to say; pass-by-value in the case of a map means passing the address of the map, not the contents of the map.

See https://play.golang.org/p/i7Yz4zMq4v

type foo map[string]string

func main() {
    a := foo{}
    a["hello"] = "world"
    fmt.Printf("%#v\n", a)
    mod(a)
    fmt.Printf("%#v\n", a)

}

func mod(f foo) {
    f["hello"] = "cruel world"
}

Which outputs:

main.foo{"hello":"world"}
main.foo{"hello":"cruel world"}