Ambiguous conversion expressions

February 27, 2024

There aren’t many places where Go syntax is ambiguous, but here’s one:

Conversions

If the type starts with the operator * or <-, or if the type starts with the keyword func and has no result list, it must be parenthesized when necessary to avoid ambiguity:

*Point(p)        // same as *(Point(p))
(*Point)(p)      // p is converted to *Point
<-chan int(c)    // same as <-(chan int(c))
(<-chan int)(c)  // c is converted to <-chan int
func()(x)        // function signature func() x
(func())(x)      // x is converted to func()
(func() int)(x)  // x is converted to func() int
func() int(x)    // x is converted to func() int (unambiguous)

Of these three ambiguous cases, * is the one you’re most likely to run into, so let’s look at an example.

Let’s imagine we have two types that are identical, except for their struct tags. This might happen if you want to use one for marshaling JSON, and the other for unmarshaling:

// User is when the user sets their password.
type User struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

// UserDisplay supresses the password for display purposes.
type UserDisplay struct {
	Username string `json:"username"`
	Password string `json:"-"`
}

Now let’s further imagine we have a *User value that we want to convert to a *UserDisplay value. This can be done with a simple conversion between struct types, since struct tags are ignored (as we’ll see shortly). But to convert from a pointer of one struct to a pointer of another, we need to use the * operator, which introduces ambiguity:

u := &User{Username: "bob", Password: "abracadabra"}
ud := *UserDisplay(u) // cannot convert u (variable of type *User) to type UserDisplay

So let’s try the prescribed solution instead:

u := &User{Username: "bob", Password: "abracadabra"}
ud := (*UserDisplay)(u)
j, _ := json.Marshal(ud)
fmt.Println(string(j)) // Prints: {"username":"bob"}

Quotes from The Go Programming Language Specification Language version go1.22 (Feb 6, 2024)


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Non-constant conversions

Yesterday we looked at conversions of constants. Let’s now consider the more common conversion scenarios of variables and other non-constant expressions. Conversions … A non-constant value x can be converted to type T in any of these cases: x is assignable to T. ignoring struct tags (see below), x’s type and T are not type parameters but have identical underlying types. ignoring struct tags (see below), x’s type and T are pointer types that are not named types, and their pointer base types are not type parameters but have identical underlying types.


Type Conversion & struct tgs

We’ve already mentioned that struct tags are ignored when doing conversion between struct types. Now we see where that’s defined, along with an example: Conversions … Struct tags are ignored when comparing struct types for identity for the purpose of conversion: type Person struct { Name string Address *struct { Street string City string } } var data *struct { Name string `json:"name"` Address *struct { Street string `json:"street"` City string `json:"city"` } `json:"address"` } var person = (*Person)(data) // ignoring tags, the underlying types are identical I don’t think there’s much else to say about that, so let’s move on, and finish up this section.


Case expressions

I’m back live streaming again! Join me in just over an hour! Bring your questions, too! Expression switches … If a case expression is untyped, it is first implicitly converted to the type of the switch expression. For each (possibly converted) case expression x and the value t of the switch expression, x == t must be a valid comparison. In other words, the switch expression is treated as if it were used to declare and initialize a temporary variable t without explicit type; it is that value of t against which each case expression x is tested for equality.

Get daily content like this in your inbox!

Subscribe