Constants, typed vs untyped

February 3, 2023


Constants may be typed or untyped.

Now that’s interesting.

And powerful.

This is one of the features of Go I featured in my recent video 10 Reasons I Like Go.

This feature really helps bridge the gap between the convenience of dynamically typed languages, and the safety of statically typed languages.

Let’s look at the details…

Literal constants, true, false, iota, and certain constant expressions containing only untyped constant operands are untyped.

It might be surprising that true and false are untyped. Shouldn’t they be of type bool? Well, consider that in Go we can define custom types based on bool, and we may want to assign these literals to such a value/constant. For example:

// SpanishBool works just like bool, but renders as "verdad"/"falso" rather than "true"/"false"
type SpanishBool bool

func (b SpanishBool) MarshalText() ([]byte, error) {
  if b {
    return []byte("verdad"), nil
  return []byte("falso"), nil

const isValid = true  // isValid is untyped, not of type bool

/* then later ... */

var accountIsValid SpanishBool = isValid // assign untyped isValid constant to
                                         // variable of type SpanishBool

A constant may be given a type explicitly by a constant declaration or conversion, or implicitly when used in a variable declaration or an assignment statement or as an operand in an expression. It is an error if the constant value cannot be represented as a value of the respective type. If the type is a type parameter, the constant is converted into a non-constant value of the type parameter.

But of course, you can have typed constants, too. This happens when you explicitly give a constant a type, or when the type is inferred from the constant expression.

const isValid = bool(true)

In this case, it is impossible to assign the constant to a variable/constant of another type, without explicit conversion:

const isValid = bool(true)

var x SpanishBool = isValid

will result in:

cannot use isValid (constant true of type bool) as type SpanishBool in variable declaration

An untyped constant has a default type which is the type to which the constant is implicitly converted in contexts where a typed value is required, for instance, in a short variable declaration such as i := 0 where there is no explicit type. The default type of an untyped constant is bool, rune, int, float64, complex128 or string respectively, depending on whether it is a boolean, rune, integer, floating-point, complex, or string constant.

And finally, untyped constants have a default type, which is used whenever an exact type isn’t otherwise specified. To demonstrate:

const x = 123
const y = 123.0

fmt.Printf("x's type: %T\ny's type: %T\n", x, y)

will print:

x's type: int
y's type: float64

Quotes from The Go Programming Language Specification, Version of June 29, 2022

Share this

Related Content

Empty structs

We finally we have enough knowledge for the EBNF format not to seem completely foreign, so let’s jump back and take a look at that, with the examples provided in the spec… Struct types … StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ "*" ] TypeName [ TypeArgs ] . Tag = string_lit . // An empty struct.

Struct tags

Struct types … A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored. struct { x, y float64 "" // an empty tag string is like an absent tag name string "any string is permitted as a tag" _ [4]byte "ceci n'est pas un champ de structure" } // A struct corresponding to a TimeStamp protocol buffer.

Struct method promotion

Yesterday we saw an example of struct field promotion. But methods (which we haven’t really discussed yet) can also be promoted. Struct types … Given a struct type S and a named type T, promoted methods are included in the method set of the struct as follows: If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.