Predeclared Types

Updated

September 13, 2026

Predeclared Types

Go gives you a short list of types with no import. The boring default is bool, int, and string for local work, int64 (or another sized type) when the width is part of the contract, and an explicit conversion whenever two names differ.

Mental model

A predeclared type is a named type that exists in every package. int and int64 are different types even when both hold eight bytes on a 64-bit machine. byte is an alias for uint8. rune is an alias for int32 and means “Unicode code point,” not “UTF-8 byte.”

A literal such as 4, true, or "toast" is untyped until you use it in a typed place. A variable always has a type. Conversions are written: int64(n), never implied.

Worked examples

Case 1: The three types you actually type all day

Save as desk_types.go. A desk is open or not (bool), it has a name (string), and it has a table count (int).

// desk_types.go
package main

import "fmt"

func main() {
    open := true
    name := "front"
    tables := 12
    fmt.Printf("open=%v (%T)\n", open, open)
    fmt.Printf("name=%q (%T)\n", name, name)
    fmt.Printf("tables=%d (%T)\n", tables, tables)
}

Run:

go run desk_types.go

Output:

open=true (bool)
name="front" (string)
tables=12 (int)

int is the default integer. Its size is the size of a register: 8 bytes on the 64-bit machine this book was checked on, 4 bytes on 32-bit. Use it for counts, indexes, and anything that never leaves this process.

Case 2: int vs int64 — convert on the boundary

Save as ticket_id.go. Ticket IDs are stored as int64 because they are a contract with another system. Table numbers stay int. You cannot add them until one side converts.

// ticket_id.go
package main

import "fmt"

func main() {
    var table int = 4
    var ticket int64 = 1_000_001
    fmt.Println(int64(table) + ticket)
    fmt.Printf("%T + %T → %T\n", int64(table), ticket, int64(table)+ticket)
}

Run:

go run ticket_id.go

Output:

1000005
int64 + int64 → int64

If you write table + ticket, the compile fails: invalid operation: table + ticket (mismatched types int and int64). That failure is the feature. Money in this book is integer cents (int or int64), not float64.

Case 3: byte and rune

Save as rune_byte.go. A byte is a small unsigned integer. A rune is a code point. The euro sign is one rune and three UTF-8 bytes.

// rune_byte.go
package main

import "fmt"

func main() {
    var mark byte = 'A'
    var euro rune = '€'
    fmt.Printf("mark %q %d %T\n", mark, mark, mark)
    fmt.Printf("euro %q %d %T %U\n", euro, euro, euro, euro)
}

Run:

go run rune_byte.go

Output:

mark 'A' 65 uint8
euro '€' 8364 int32 U+20AC

%T prints uint8 and int32 because those are the real types. The aliases exist so you can say what you mean.

Case 4: Untyped constants, typed variables

Save as untyped.go. An untyped constant can land in more than one type. A typed constant cannot.

// untyped.go
package main

import "fmt"

func main() {
    const seats = 4
    var asInt int = seats
    var asInt64 int64 = seats
    fmt.Println(asInt, asInt64)

    const typed int = 4
    var also int64 = int64(typed)
    fmt.Println(also)

    fmt.Printf("%T %T %T\n", true, 12, "toast")
}

Run:

go run untyped.go

Output:

4 4
4
bool int string

seats has no type of its own, so both assignments work. typed is an int; it needs int64(typed) to become int64. Passing 12 to fmt.Printf gives it the default type int — that is why %T prints int, not “untyped integer.”

Case 5: Sizes, once, so the names stop being magic

Save as sizes.go. unsafe.Sizeof reports how many bytes a variable occupies. For a string that is the header (pointer plus length), not the characters. This is a measuring tape, not a desk tool — do not sprinkle unsafe through business code.

// sizes.go
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    fmt.Println("bool", unsafe.Sizeof(false))
    fmt.Println("int", unsafe.Sizeof(int(0)))
    fmt.Println("int32", unsafe.Sizeof(int32(0)))
    fmt.Println("int64", unsafe.Sizeof(int64(0)))
    fmt.Println("rune", unsafe.Sizeof(rune(0)))
    fmt.Println("byte", unsafe.Sizeof(byte(0)))
    fmt.Println("string header", unsafe.Sizeof(""))
    fmt.Println("float64", unsafe.Sizeof(float64(0)))
}

Run:

go run sizes.go

Output on 64-bit:

bool 1
int 8
int32 4
int64 8
rune 4
byte 1
string header 16
float64 8

rune matches int32. byte matches uint8. int matches the machine. float64 exists; this book still keeps prices in cents.

The trap

Conversions compile whenever the types are numeric (or string/[]byte). They do not check that the value still means the same thing. Save as truncate.go:

// truncate.go
package main

import "fmt"

func main() {
    euro := '€'
    b := byte(euro)
    fmt.Printf("rune %U %d\n", euro, euro)
    fmt.Printf("as byte %d\n", b)

    minutes := 2.9
    fmt.Println("int(2.9) =", int(minutes))
}

Run:

go run truncate.go

Output:

rune U+20AC 8364
as byte 172
int(2.9) = 2

8364 does not fit in a byte, so you get 8364 % 256. int of a float truncates toward zero; it does not round. Convert when you know the value fits, or when truncation is the specified behaviour. Otherwise keep the wider type.

The boring rule

  • Use int for counts and indexes in this process.
  • Use int64 (or int32) when a file, a network, or another language must agree on width.
  • Store money as integer cents. Do not use float64 for prices.
  • Write conversions. If the compiler complains about mismatched types, do not silence it with a random cast — pick the type you meant.
  • Use rune for characters, byte for raw bytes. They are not interchangeable just because both are “small integers.”
  • Leave unsafe for diagnostics and the rare low-level package.

Try this

  1. In ticket_id.go, delete int64(table) and run. Read the compile error. Put the conversion back on the other side (int(ticket)) and decide which width you want.
  2. In untyped.go, try var also int64 = typed without the conversion. Confirm only the typed constant fails.
  3. In truncate.go, convert 'A' to byte and print it. Then convert a rune that does not fit ('本' or '€') and compare.
  4. Add fmt.Printf("%T\n", 1.5) to untyped.go. The default type of an untyped float is float64.