A custom type built on an existing type gets its own identity and its own methods. It's how a plain string or int becomes something the compiler treats as distinct.
There are a few ways to declare variables (and types) in Go. Here we print the Type and default value for each.
var active bool = true
var count int = 42
var price float64 = 19.99
name := "Go"
fmt.Printf("%T: %v\n", active, active)
fmt.Printf("%T: %v\n", count, count)
fmt.Printf("%T: %v\n", price, price)
fmt.Printf("%T: %v\n", name, name)%T prints the underlying type of a value and %v prints the value itself.
$ go run main.go
bool: true
int: 42
float64: 19.99
string: GoMaking your own types
Examples
type Name string declares a new, distinct type with string as its underlying type. Name and string aren't interchangeable without a conversion, even though they share the same representation.
type Name string
n := Name("Ferris")
fmt.Println(n)FerrisName satisfies Talker automatically once it has a matching Talk() string method. Go checks method sets structurally, at compile time.
type Talker interface {
Talk() string
}
func (n Name) Talk() string {
return "Hi, I'm " + string(n)
}
var t Talker = Name("Ferris")
fmt.Println(t.Talk())Hi, I'm FerrisAn equals sign creates a type alias instead of a distinct type. UserID and int stay fully interchangeable, no conversion needed either direction.
type UserID = int
func lookup(id UserID) string {
return fmt.Sprintf("user #%d", id)
}
fmt.Println(lookup(42)) // a plain int works: UserID is not a distinct typeuser #42Every type in Go
Every type below is either predeclared, meaning its name is always in scope, or a type literal you write out yourself. There is nothing else.
| Type | Size | Range or form | Notes |
|---|---|---|---|
| Booleans | |||
bool | 1 byte | true / false | The only type an if or for condition accepts. |
| Signed integers | |||
int8 | 8-bit | −128 to 127 | |
int16 | 16-bit | −32,768 to 32,767 | Rare outside binary formats. |
int32 | 32-bit | −2,147,483,648 to 2,147,483,647 | rune is an alias for this. |
int64 | 64-bit | −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | What time.Duration is built on. |
int | 32 or 64-bit | Same as int32 or int64, whichever the platform uses | The default for whole numbers. Use it unless you need a fixed width. |
| Unsigned integers | |||
uint8 | 8-bit | 0 to 255 | byte is an alias for this. |
uint16 | 16-bit | 0 to 65,535 | |
uint32 | 32-bit | 0 to 4,294,967,295 | |
uint64 | 64-bit | 0 to 18,446,744,073,709,551,615 | |
uint | 32 or 64-bit | Same as uint32 or uint64 | Subtracting past 0 wraps to a huge number rather than going negative. |
uintptr | pointer-sized | Large enough to hold any pointer's bits | For unsafe pointer arithmetic. Not a pointer the collector tracks. |
| Aliases | |||
byte | 8-bit | Identical to uint8 | An alias, not a separate type. Used when the bytes are data, not numbers. |
rune | 32-bit | Identical to int32 | An alias. Holds one Unicode code point. |
| Floating point | |||
float32 | 32-bit | ±1.18e−38 to ±3.40e38 | About 7 decimal digits of precision. |
float64 | 64-bit | ±2.23e−308 to ±1.80e308 | About 15 digits. The default for decimals. |
| Complex | |||
complex64 | 64-bit | Two float32 values | Real and imaginary parts. |
complex128 | 128-bit | Two float64 values | The default for complex literals. |
| Strings | |||
string | 16-byte header | Immutable bytes, conventionally UTF-8 | Indexing gives a byte. Ranging gives a rune. |
| Predeclared interfaces | |||
error | interface | Any type with Error() string | An ordinary interface, not built into the language. |
any | interface | Alias for interface{} | Added in Go 1.18. Every type satisfies it. |
comparable | constraint | Types usable with == | Only valid as a type parameter constraint. |
| Composite types | |||
[N]T | N × size of T | Fixed-length array | The length is part of the type: [3]int and [4]int differ. |
[]T | 24-byte header | Slice: pointer, length, capacity | A view into an array. |
map[K]V | pointer-sized | Hash table | The zero value is nil, and writing to a nil map panics. |
chan T | pointer-sized | Channel | <-chan T receives only, chan<- T sends only. |
*T | 8 bytes on 64-bit | Address of a T | No pointer arithmetic. |
struct{...} | sum of fields, plus padding | Fields in declaration order | Field order affects size, because of alignment. |
func(...) | pointer-sized | Function value | Functions are values and can be passed and stored. |
interface{...} | 16 bytes | Method set | Holds a type word and a value word. |
Zero values
| Declared as | Zero value |
|---|---|
var b bool | false |
Any integer type | 0 |
Any float or complex type | 0 / 0.0 |
var s string | "", the empty string |
fmt verbs
| Verb | Prints | Example |
|---|---|---|
%v | Default format for the value | fmt.Printf("%v", 42) → 42 |
%T | Go-syntax type of the value | fmt.Printf("%T", 42) → int |
%d | Base-10 integer | fmt.Printf("%d", 255) → 255 |
%x / %X | Lower/upper-case hexadecimal | fmt.Printf("%x", 255) → ff |
%f | Decimal, no exponent | fmt.Printf("%.2f", 3.14159) → 3.14 |
%t | The word true or false | fmt.Printf("%t", true) → true |
%s | The uninterpreted string | fmt.Printf("%s", "go") → go |
%q | Double-quoted, safely escaped | fmt.Printf("%q", "hi\n") → "hi\n" |
%c | The character represented by a rune | fmt.Printf("%c", 65) → A |
Defined types vs. type aliases
| Declaration | Distinct type? | Can attach methods? | Interchangeable without conversion? |
|---|---|---|---|
type Name string | Yes | Yes | No |
type UserID = int | No | No (would attach to int itself, illegal outside its package) | Yes |
Conversion rules
| From → To | Allowed | Notes |
|---|---|---|
| Named type ↔ its underlying type | Explicit conversion, e.g. Celsius(f) | Always allowed; never happens implicitly. |
| Numeric type ↔ numeric type | Explicit conversion, e.g. int64(x) | May truncate or overflow silently; no runtime check. |
string ↔ []byte | Explicit conversion, e.g. []byte(s) | Copies the underlying bytes. |
string ↔ []rune | Explicit conversion, e.g. []rune(s) | Decodes/encodes UTF-8; length can change from byte count to rune count. |
| Concrete type → interface it implements | Implicit, no conversion syntax | Happens automatically on assignment if the method set matches. |
| Type alias ↔ its target type | Implicit, no conversion syntax | They're the same type at compile time. |
Behavior and edge cases
A bool holds true or false. It's the only type if, for, and &&/|| accept. Go has no numeric or pointer truthiness, so a nil check has to spell out != nil.
var active bool // false, the zero value
enabled := true
// Go has no truthy values. This does not compile:
// if 1 { }
if ptr != nil {
fmt.Println("got a pointer")
}Sized integers (int8 through int64, and their uint counterparts) pick an exact width. Converting between integer types requires explicit casting.
var count int // 64-bit on most modern systems
var age uint8 = 30
var offset int64 = -1_000_000 // underscores group digits
hex := 0xFF
bin := 0b1010
// Conversion is always explicit
var x int32 = 42
var y int64 = int64(x)len() on a string counts bytes, not characters. Multi-byte UTF-8 runes like é or 世 take more than one byte, so byte (an alias for uint8) and rune (an alias for int32) exist to be explicit about which unit of text is meant.
word := "héllo"
fmt.Println(len(word)) // 6 bytes, not 5 characters
var b byte = 'A' // byte is an alias for uint8
var r rune = '世' // rune is an alias for int32
for i, ch := range word {
fmt.Printf("%d: %c\n", i, ch) // indices skip a byte at multi-byte runes
}The rune prints as int32 under %T, since rune is an alias not a type. %v prints the decimal value; %c further down prints it as a character.
$ go run basic_types.go
Type: bool Value: true
Type: string Value: Hello, Go!
Type: int Value: 42
Type: float64 Value: 3.14159
Type: complex128 Value: (5+7i)
Type: int32 Value: A