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:
sync or sync/atomic type: Mutex, RWMutex, WaitGroup, Once,
Cond, Map, Pool, atomic.Int64, atomic.Value, …;Lock() and Unlock() methods its value lacks;noCopy, the conventional marker;strings.Builder or bytes.Buffer.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:
Lock and Unlock each get their own copy of a Mutex, so nothing is
locked, and the Unlock is a fatal runtime error (sync: unlock of unlocked
mutex);WaitGroup.Wait returns at once, and Done panics on a negative counter;Add and Store are lost;bytes.Buffer loses every write, and a strings.Builder loses them or
panics (“illegal use of non-zero Builder copied by value”).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.