When it fires. A value would have to convert implicitly between an opaque
type and its underlying type, or between two opaque types, to fill a typed
slot: a function or method argument, a val / var declaration with a type, an
assignment, a return value, or a field of a constructor call. An opaque type
never converts implicitly, in either direction.
Minimal repro.
package main
opaque type UserID int64
func loadUser(id UserID) string = s"user ${int64(id)}"
func main() {
val raw int64 = 42
Println(loadUser(raw))
}
Error output.
error[GALA-E0064]: cannot use raw (int64) as UserID: an opaque type never converts implicitly
--> main.gala:9:22
|
9 | Println(loadUser(raw))
| ^^^ convert explicitly: UserID(raw)
|
= hint: convert explicitly: UserID(raw)
Fix. Convert explicitly:
package main
opaque type UserID int64
func loadUser(id UserID) string = s"user ${int64(id)}"
func main() {
val raw int64 = 42
Println(loadUser(UserID(raw)))
}
The other direction. An opaque value where its underlying type is expected needs the conversion too — here in a return:
package main
opaque type Cents int64
func total(a Cents, b Cents) int64 = a + b
func main() {
Println(total(Cents(250), Cents(100)))
}
error[GALA-E0064]: cannot use a + b (Cents) as int64: an opaque type never converts implicitly
--> main.gala:5:38
|
5 | func total(a Cents, b Cents) int64 = a + b
| ^^^^^ convert explicitly: int64(a + b)
|
= hint: convert explicitly: int64(a + b)
Write int64(a + b), or return a Cents.
Two opaque types. Passing a UserID where an OrderID is expected is the
same error; the hint converts through the underlying type,
OrderID(int64(id)).
What still works. Untyped constants mix with an opaque type exactly as
they do with a Go defined type: loadUser(42), val zero UserID = 0,
id == 42 and id + 1 need no conversion. A slot of type any, an interface
or a type parameter takes an opaque value as it is.
Rationale. An opaque type is a distinct type over an existing
representation; the point is that a bare int64 cannot pass for a UserID,
nor a UserID for an OrderID. Go rejects these assignments too, but its
message describes the generated code, including the .Get() the transpiler
inserts to read a val. GALA reports it in the source’s own terms,
with the conversion to write. go build remains the backstop for a position
this check does not cover.
Related. Opaque Types, GALA-E0063 for an explicit conversion between two opaque types.