GALA-E0053 — pointer method on a copy of a value that must not be copied

When it fires. A method with a pointer receiver is called on a value Go cannot address, and the value’s type must not be copied:

struct Counter(mu sync.Mutex, var n int)

func (c *Counter) Inc() {
    c.mu.Lock()   // mu is a val field
    c.mu.Unlock()
}

A val, a val struct field (fields are val unless marked var), a call result, a literal and a type assertion cannot be addressed. GALA calls a pointer method on such a receiver through a fresh copy, which is fine for url.URL.String but wrong for these types, so the call is rejected.

Printing calls a method too. Println, Print, s"..." and f"..." print a value whose String() is declared on a pointer receiver by calling that String, and on a value that cannot be addressed they call it on a copy. So printing such a value is rejected the same way:

package main

import "sync"

struct Guarded(var mu sync.Mutex, var n int)

func (g *Guarded) String() string = s"guarded ${g.n}"

func main() {
    val g = Guarded(sync.Mutex{}, 1)
    Println(g)
}
error[GALA-E0053]: cannot print this Guarded: it cannot be addressed here, so its String would run on a copy, and Guarded contains sync.Mutex, which must not be copied
  --> main.gala:11:13
   |
11 |     Println(g)
   |             ^ declare it with `var`, or hold a pointer
   |
   = hint: declare it with `var`, or hold a pointer (`&T{...}`)

Declare it with var g = ... and String runs on g itself. Printing a struct or a sealed value whose generated String prints a field of such a type is rejected too: the generated String prints each field through a copy of it, even when the value printed is a var or a pointer. Hold the field as a pointer (g *Guarded), or declare the outer type’s own String.

A type must not be copied when it, or any field reached through struct fields (and, for a Go type, array elements; not through pointers, slices, maps or collections), is one of the following. For a generic struct, its type arguments count as its fields. This follows go vet’s copylocks check:

Minimal repro.

package main

import "sync"

struct Counter(mu sync.Mutex, var n int)

func (c *Counter) Inc() {
    c.mu.Lock()
    c.n = c.n + 1
    c.mu.Unlock()
}

func main() {
    var c = Counter(sync.Mutex{}, 0)
    c.Inc()
    Println(c.n)
}

Error output.

error[GALA-E0053]: cannot call Lock on this sync.Mutex: it cannot be addressed here, so the call would run on a copy, and sync.Mutex must not be copied
  --> main.gala:8:10
  |
8 |     c.mu.Lock()
  |          ^^^^ declare it with `var`, or hold a pointer
  |
  = hint: declare it with `var`, or hold a pointer (`&T{...}`)

Fix. Keep the value somewhere Go can address. Mark the field (or local) var:

struct Counter(var mu sync.Mutex, var n int)

or hold a pointer to it:

struct Counter(mu *sync.Mutex, var n int)

var c = Counter(&sync.Mutex{}, 0)

Rationale. A val is stored as Immutable[T] and read through Get(), which returns a copy. Go calls a pointer-receiver method only on an addressable value, so the call runs on a copy made for it. For most types a method that only reads its receiver may run on that copy, and one that changes it is GALA-E0110. For these types the copy breaks the program whatever the method does:

Before this check, such a call failed in go build with “cannot call pointer method Lock on sync.Mutex”; this code reports it at the GALA source instead.

Where it stands down. An addressable receiver is called directly, so the method works on the value itself: a var, a function parameter, a pattern binding, and a var field (of a var, or reached through a pointer). A val field is read through a copy even behind a pointer, which is why the repro above is rejected. Types that are fine to copy (url.URL, time.Time) still run a pointer method that only reads its receiver, such as url.URL.String, on a copy of a val; one that changes its receiver is GALA-E0110.