Variables and Data Types
Imagine you're cooking in the kitchen: variables are labeled storage boxes — the label is the name, and the box contains data; data types are the shapes of the boxes — round boxes hold soup, square boxes hold dishes, and you can't mix them up. Go is very strict about "box" management: you must declare what box (type) you need, what content to put in (assignment), and even empty boxes have default contents (zero values).
1. Core Concepts
1.1 Variable Declaration: var vs :=
Go provides two ways to declare variables:
| Method | Syntax | Use Case | Scope |
|---|---|---|---|
var |
var name int |
Package-level variables, explicit types needed | Inside/outside functions |
Short declaration := |
name := 10 |
Local variables, type inference | Inside functions only |
Key differences:
varcan be declared without assignment (uses zero value automatically);:=requires assignment.varcan be declared outside functions;:=can only be used inside functions.:=is shorthand for declaration + assignment; at least one variable on the left must be new.
1.2 Basic Data Types
| Category | Type | Description | Example |
|---|---|---|---|
| Integer | int, int8, int16, int32, int64 |
Signed integers | var a int = 42 |
| Unsigned integer | uint, uint8, uint16, uint32, uint64 |
Non-negative integers | var b uint = 100 |
| Floating point | float32, float64 |
Decimals (default float64) |
var c float64 = 3.14 |
| Boolean | bool |
true / false |
var d bool = true |
| String | string |
Immutable byte sequence | var e string = "hello" |
| Byte | byte |
Alias for uint8, represents one byte |
var f byte = 'A' |
| Unicode character | rune |
Alias for int32, represents one Unicode code point |
var g rune = '€' |
Platform-dependent types: int and uint are 32-bit on 32-bit systems and 64-bit on 64-bit systems. In daily development, int is sufficient in most cases.
1.3 Zero Value Mechanism
When a variable is declared but not assigned in Go, it is automatically initialized to a zero value, not null or undefined:
| Type | Zero Value |
|---|---|
Integer (int, uint, etc.) |
0 |
Floating point (float32, float64) |
0.0 |
Boolean (bool) |
false |
String (string) |
"" (empty string) |
| Pointer, slice, map, channel, function, interface | nil |
This is Go's safety mechanism — you never get a variable with "no value", avoiding many null pointer errors.
1.4 Constants and iota
constdeclares constants whose values are determined at compile time and cannot be changed afterward.iotais a constant generator that auto-increments from 0 within aconstblock, suitable for defining enums.
1.5 Type Conversion
Go does not support implicit type conversion; different types must be explicitly converted:
var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(f) // float64 → uint
1.6 fmt Package Formatted Output
fmt is Go's most commonly used formatting package. Key functions:
| Function | Purpose | Example |
|---|---|---|
fmt.Println |
Print with newline | fmt.Println("hello") |
fmt.Printf |
Formatted print (no newline) | fmt.Printf("value=%d", 42) |
fmt.Sprintf |
Format to string (no print) | s := fmt.Sprintf("%d", 42) |
Common format specifiers: %d (integer), %f (float), %s (string), %t (boolean), %T (type), %v (generic value), %q (quoted string).
2. Basic Syntax/Usage
Variable Declaration
// Method 1: var declaration (inside/outside functions)
var name string // Declaration, zero value ""
var age int = 25 // Declaration with assignment
var score = 98.5 // Type inference as float64
// Method 2: short declaration := (inside functions only)
city := "Beijing" // Declaration with assignment, inferred as string
count := 100 // Type inference as int
// Batch declaration
var (
width int = 10
height int = 20
area float64
)
var but don't use it, the Go compiler will report declared and not used. This is Go's design philosophy: no dead code allowed. Same applies to short declaration :=.
Constant Declaration
// Single constant
const Pi = 3.14159
const Language string = "Go"
// Batch constants + iota enumeration
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
iota is only valid within a const block and auto-increments by 1 for each new line. If a line uses _ to skip, iota still increments.
Type Conversion
var i int = 100
var f float64 = float64(i) // Explicit conversion: int → float64
var s string = string(rune(i)) // int → rune → string (note: doesn't turn number to string)
// Correct way to convert number to string:
s2 := fmt.Sprintf("%d", i) // "100"
string(65) produces the character "A" (ASCII code), not "65". To convert a number to a decimal string, use fmt.Sprintf or strconv.Itoa.
3. Example Code
Example 1: Basic Usage (Difficulty ⭐)
Scenario: Declare variables of different types, print their values and types.
package main
import "fmt"
func main() {
// ========== Variable Declaration ==========
// 1. var declaration + explicit type
var name string = "Alice"
var age int = 28
// 2. var declaration + type inference (compiler infers type from value)
salary := 15000.5 // Inferred as float64
// 3. Short variable declaration (most common)
isEmployed := true
city := "New York"
// 4. Zero value demo — declare without assignment
var defaultInt int // Zero value: 0
var defaultFloat float64 // Zero value: 0
var defaultBool bool // Zero value: false
var defaultString string // Zero value: ""
// ========== Print Output ==========
fmt.Println("=== Personal Info ===")
fmt.Printf("Name: %s (Type: %T)\n", name, name)
fmt.Printf("Age: %d (Type: %T)\n", age, age)
fmt.Printf("Salary: %.1f (Type: %T)\n", salary, salary)
fmt.Printf("Employed: %t (Type: %T)\n", isEmployed, isEmployed)
fmt.Printf("City: %s (Type: %T)\n", city, city)
fmt.Println("\n=== Zero Value Demo ===")
fmt.Printf("int zero value: %d\n", defaultInt)
fmt.Printf("float64 zero value: %v\n", defaultFloat)
fmt.Printf("bool zero value: %t\n", defaultBool)
fmt.Printf("string zero value: %q\n", defaultString) // %q shows quoted string
}
Output:
=== Personal Info ===
Name: Alice (Type: string)
Age: 28 (Type: int)
Salary: 15000.5 (Type: float64)
Employed: true (Type: bool)
City: New York (Type: string)
=== Zero Value Demo ===
int zero value: 0
float64 zero value: 0
bool zero value: false
string zero value: ""
Key Points:
%Tprints the underlying type of a variable.%qadds quotes to strings, making it easy to distinguish empty strings from non-empty ones during debugging.- The zero value mechanism ensures variables always have a definite value, which is an important safety guarantee in Go.
Example 2: Intermediate Usage (Difficulty ⭐⭐)
Scenario: Using constants, iota to define enums, and practical type conversion.
package main
import "fmt"
// ========== iota Enum: Define Role Permissions ==========
type Role int // Custom type with int as underlying type
const (
Guest Role = iota // 0: Guest
Member // 1: Member
Admin // 2: Admin
SuperAdmin // 3: Super Admin
)
// Implement String() method for Role for easy printing
func (r Role) String() string {
names := [...]string{"Guest", "Member", "Admin", "Super Admin"}
if int(r) < len(names) {
return names[r]
}
return "Unknown Role"
}
// ========== Advanced iota Usage: Bitwise Permission Flags ==========
type Permission int
const (
Read Permission = 1 << iota // 1 (001)
Write // 2 (010)
Execute // 4 (100)
)
// ========== Type Conversion in Practice ==========
func main() {
// --- iota enum usage ---
var userRole Role = Admin
fmt.Printf("User role: %s (Value: %d)\n", userRole, userRole)
// --- Bitwise permission combination ---
userPerm := Read | Write // Combined permissions: Read + Write = 3 (011)
fmt.Printf("\nUser permission value: %d (Binary: %03b)\n", userPerm, userPerm)
fmt.Printf("Has read permission? %t\n", userPerm&Read != 0)
fmt.Printf("Has execute permission? %t\n", userPerm&Execute != 0)
// --- Type conversion ---
var temperature float64 = 36.6
var intTemp int = int(temperature) // float64 → int (truncates, doesn't round)
fmt.Printf("\nBody temp: %.1f°C → Integer part: %d\n", temperature, intTemp)
// Number → string (two ways)
var code int = 65
char := string(rune(code)) // Method 1: Convert to corresponding Unicode character → "A"
numStr := fmt.Sprintf("%d", code) // Method 2: Convert to numeric string → "65"
fmt.Printf("Number %d → Character: %s, String: %q\n", code, char, numStr)
// --- Strings and bytes/characters ---
s := "Hello,Mundo"
fmt.Printf("\nString %q length: %d bytes\n", s, len(s))
fmt.Println("Byte-by-byte traversal (all ASCII here, but CJK differs):")
for i := 0; i < len(s); i++ {
fmt.Printf(" [%d] %d %c\n", i, s[i], s[i])
}
fmt.Println("Rune-by-rune traversal (same for ASCII):")
for i, r := range s {
fmt.Printf(" [%d] %U '%c'\n", i, r, r)
}
}
Output:
User role: Admin (Value: 2)
User permission value: 3 (Binary: 011)
Has read permission? true
Has execute permission? false
Body temp: 36.6°C → Integer part: 36
Number 65 → Character: A, String: "65"
String "Hello,Mundo" length: 11 bytes
Byte-by-byte traversal (all ASCII here, but CJK differs):
[0] 72 H
[1] 101 e
[2] 108 l
[3] 108 l
[4] 111 o
[5] 44 ,
[6] 77 M
[7] 117 u
[8] 110 n
[9] 100 d
[10] 111 o
Rune-by-rune traversal (same for ASCII):
[0] U+0048 'H'
[1] U+0065 'e'
[2] U+006C 'l'
[3] U+006C 'l'
[4] U+006F 'o'
[5] U+002C ','
[6] U+004D 'M'
[7] U+0075 'u'
[8] U+006E 'n'
[9] U+0064 'd'
[10] U+006F 'o'
Key Points:
iotastarts from 0 and auto-increments by 1 per line, making it perfect for defining enum constants.- Bitwise permissions are a classic
iotaapplication: use|to combine permissions and&to check permissions. int(float64)directly truncates the decimal part, it does not round.len(string)returns the byte count, not the character count. CJK characters occupy 3 bytes in UTF-8.- Use
rangeto iterate over strings to correctly handle multi-byte characters (runes).
Example 3: Comprehensive Application (Difficulty ⭐⭐⭐)
Scenario: Simulate a simple user registration information processing system, combining variables, constants, type conversion, and formatted output.
package main
import (
"fmt"
"strings"
)
// ========== Constants ==========
// Gender enum
type Gender int
const (
Unknown Gender = iota // 0: Unknown
Male // 1: Male
Female // 2: Female
)
func (g Gender) String() string {
return [...]string{"Unknown", "Male", "Female"}[g]
}
// Membership levels and corresponding discounts
const (
LevelBronze = 1
LevelSilver = 2
LevelGold = 3
LevelPlatinum = 4
)
// Use iota to generate discount percentages
const (
_ = iota // Skip 0
DiscountBronze = 95 // 5% off
DiscountSilver = 90 // 10% off
DiscountGold = 85 // 15% off
DiscountPlatinum = 80 // 20% off
)
// ========== User Struct (Preview, detailed in later lessons) ==========
type User struct {
Name string
Age int
Gender Gender
Level int
Balance float64
}
// ========== Core Logic ==========
func main() {
// Simulated user data
users := []User{
{Name: "Li Ming", Age: 25, Gender: Male, Level: LevelGold, Balance: 1580.50},
{Name: "Wang Fang", Age: 30, Gender: Female, Level: LevelPlatinum, Balance: 3200.00},
{Name: "Zhang Wei", Age: 17, Gender: Male, Level: LevelBronze, Balance: 200.00},
{Name: "Zhao Min", Age: 22, Gender: Female, Level: LevelSilver, Balance: 800.75},
}
fmt.Println("╔══════════════════════════════════════════════════════════╗")
fmt.Println("║ User Registration System ║")
fmt.Println("╚══════════════════════════════════════════════════════════╝")
fmt.Println()
// Statistics variables
var totalAge int
var totalBalance float64
var maleCount, femaleCount int
var adultCount int
for _, u := range users {
// Get discount
discount := getDiscount(u.Level)
discountAmount := u.Balance * float64(100-discount) / 100.0
// Format output user info
ageTag := ""
if u.Age >= 18 {
ageTag = "✅ Adult"
adultCount++
} else {
ageTag = "❌ Minor"
}
fmt.Printf("👤 %-8s | Gender: %-6s | Age: %d (%s) | Level: Lv.%d | Balance: $%.2f\n",
u.Name, u.Gender, u.Age, ageTag, u.Level, u.Balance)
fmt.Printf(" 💰 %d%% discount, save $%.2f\n", discount, discountAmount)
// Statistics
totalAge += u.Age
totalBalance += u.Balance
switch u.Gender {
case Male:
maleCount++
case Female:
femaleCount++
}
fmt.Println(strings.Repeat("-", 60))
}
// Print statistics
avgAge := float64(totalAge) / float64(len(users))
fmt.Println()
fmt.Println("📊 Statistics:")
fmt.Printf(" Total users: %d\n", len(users))
fmt.Printf(" Male: %d | Female: %d\n", maleCount, femaleCount)
fmt.Printf(" Adults: %d | Minors: %d\n", adultCount, len(users)-adultCount)
fmt.Printf(" Average age: %.1f years\n", avgAge)
fmt.Printf(" Total balance: $%.2f\n", totalBalance)
fmt.Printf(" Average balance: $%.2f\n", totalBalance/float64(len(users)))
// Demonstrate type conversion and formatting
fmt.Println()
fmt.Println("🔧 Type Conversion Demo:")
demoTypeConversion()
}
// getDiscount returns the discount based on membership level
func getDiscount(level int) int {
switch level {
case LevelBronze:
return DiscountBronze
case LevelSilver:
return DiscountSilver
case LevelGold:
return DiscountGold
case LevelPlatinum:
return DiscountPlatinum
default:
return 100 // No discount
}
}
// demoTypeConversion demonstrates various type conversion scenarios
func demoTypeConversion() {
// 1. Numeric type conversion
var pi float64 = 3.14159
var intPi int = int(pi) // Truncates, doesn't round
fmt.Printf(" float64 → int: %.5f → %d\n", pi, intPi)
// 2. Number → string (two methods compared)
var num int = 2024
way1 := string(rune(num)) // Unicode character → not what you want
way2 := fmt.Sprintf("%d", num) // Decimal string → "2024"
way3 := fmt.Sprintf("%x", num) // Hexadecimal string → "7e8"
fmt.Printf(" int → string (rune): %q\n", way1)
fmt.Printf(" int → string (%%d): %q\n", way2)
fmt.Printf(" int → string (%%x): %q\n", way3)
// 3. String → number (using fmt.Sscanf)
var parsed int
_, err := fmt.Sscanf("12345", "%d", &parsed)
if err == nil {
fmt.Printf(" string → int (Sscanf): %d\n", parsed)
}
// 4. bool → string
b := true
boolStr := fmt.Sprintf("%t", b)
fmt.Printf(" bool → string: %t → %q\n", b, boolStr)
// 5. Difference between rune and byte
var r rune = '€'
var b2 byte = 'A'
fmt.Printf(" rune '€': value=%d, hex=%U, %d bytes (UTF-8)\n", r, r, len(string(r)))
fmt.Printf(" byte 'A': value=%d, hex=%U, 1 byte\n", b2, b2)
}
Output:
╔══════════════════════════════════════════════════════════╗
║ User Registration System ║
╚══════════════════════════════════════════════════════════╝
👤 Li Ming | Gender: Male | Age: 25 (✅ Adult) | Level: Lv.3 | Balance: $1580.50
💰 15% discount, save $237.08
------------------------------------------------------------
👤 Wang Fang | Gender: Female | Age: 30 (✅ Adult) | Level: Lv.4 | Balance: $3200.00
💰 20% discount, save $640.00
------------------------------------------------------------
👤 Zhang Wei | Gender: Male | Age: 17 (❌ Minor) | Level: Lv.1 | Balance: $200.00
💰 5% discount, save $10.00
------------------------------------------------------------
👤 Zhao Min | Gender: Female | Age: 22 (✅ Adult) | Level: Lv.2 | Balance: $800.75
💰 10% discount, save $80.08
------------------------------------------------------------
📊 Statistics:
Total users: 4
Male: 2 | Female: 2
Adults: 3 | Minors: 1
Average age: 23.5 years
Total balance: $5781.25
Average balance: $1445.31
🔧 Type Conversion Demo:
float64 → int: 3.14159 → 3
int → string (rune): "\u07e8"
int → string (%d): "2024"
int → string (%x): "7e8"
string → int (Sscanf): 12345
bool → string: true → "true"
rune '€': value=8364, hex=U+20AC, 3 bytes (UTF-8)
byte 'A': value=65, hex=U+0041, 1 byte
Key Points:
- Constant blocks with
iotacan elegantly define enums and incrementing values. - Type conversion must be explicit; Go won't automatically convert for you.
string(int)andfmt.Sprintf("%d", int)have completely different effects — the former produces a Unicode character, the latter a numeric string.runeis for handling Unicode characters;byteis for handling single-byte data.%qformats strings with quotes and escapes special characters, very useful for debugging.
3. Common Use Cases
Case 1: Configuration Management
package main
import "fmt"
// Application configuration constants (determined at compile time, immutable)
const (
AppName = "GoWeb"
Version = "1.0.0"
MaxRetries = 3
DefaultTimeout = 30 // seconds
)
// Environment enum
type Env int
const (
Dev Env = iota // Development
Staging // Staging
Prod // Production
)
func (e Env) String() string {
return [...]string{"Dev", "Staging", "Prod"}[e]
}
func main() {
// Runtime variables
currentEnv := Prod
dbHost := "192.168.1.100"
dbPort := 3306
debug := currentEnv == Dev
fmt.Printf("App: %s v%s\n", AppName, Version)
fmt.Printf("Environment: %s\n", currentEnv)
fmt.Printf("Database: %s:%d\n", dbHost, dbPort)
fmt.Printf("Debug mode: %t\n", debug)
fmt.Printf("Max retries: %d, Timeout: %d seconds\n", MaxRetries, DefaultTimeout)
}
Case 2: Data Formatting & Reports
package main
import "fmt"
func main() {
// Simulated student grade data
type Student struct {
Name string
Score float64
Grade string
}
students := []Student{
{"Alice", 92.5, ""},
{"Bob", 87.0, ""},
{"Charlie", 76.5, ""},
{"Diana", 95.0, ""},
{"Eve", 63.0, ""},
}
// Automatically assign grades based on scores
for i := range students {
switch {
case students[i].Score >= 90:
students[i].Grade = "A (Excellent)"
case students[i].Score >= 80:
students[i].Grade = "B (Good)"
case students[i].Score >= 70:
students[i].Grade = "C (Average)"
case students[i].Score >= 60:
students[i].Grade = "D (Pass)"
default:
students[i].Grade = "F (Fail)"
}
}
// Print grade report
fmt.Println("┌─────────┬────────┬────────────┐")
fmt.Println("│ Name │ Score │ Grade │")
fmt.Println("├─────────┼────────┼────────────┤")
var total float64
for _, s := range students {
fmt.Printf("│ %-7s │ %6.1f │ %-10s │\n", s.Name, s.Score, s.Grade)
total += s.Score
}
fmt.Println("├─────────┼────────┼────────────┤")
fmt.Printf("│ Average │ %6.1f │ │\n", total/float64(len(students)))
fmt.Println("└─────────┴────────┴────────────┘")
}
❓ FAQ
Q1: Which should I use, var or :=? (Conceptual confusion)
A: Follow this simple principle:
| Scenario | Recommended | Reason |
|---|---|---|
| Declare and assign inside function | := |
More concise, Go community convention |
| Declare without assignment inside function | var |
:= requires assignment |
| Package-level variable | var |
:= cannot be used outside functions |
| Need explicit type specification | var |
Avoid unexpected type inference |
func example() {
// ✅ Recommended: use := inside functions
name := "Go"
count := 0
// ✅ Must use var: declare without assignment
var result string
if someCondition {
result = "yes"
} else {
result = "no"
}
// ✅ Must use var: need exact type
var b byte = 255 // := would infer as int
}
Q2: Why isn't string(65) equal to "65"? (Common pitfall)
A: string(int) treats the integer as a Unicode code point, not as a text representation of the number:
// ❌ Wrong assumption
s1 := string(65) // Expected "65", actually got "A" (Unicode U+0041)
// ✅ Correct approach
s2 := fmt.Sprintf("%d", 65) // "65"
s3 := strconv.Itoa(65) // "65" (requires import "strconv")
// Actual use of string(rune): Unicode code point → character
ch := string(rune(8364)) // "€" (U+20AC)
Q3: Why isn't len("Hello,Mundo") equal to 8? (Common pitfall)
A: len() returns the byte count, not the character count. Go strings use UTF-8 encoding by default, and CJK characters occupy 3 bytes each:
s := "Hello,Mundo"
fmt.Println(len(s)) // 11 (5 + 1 + 5 = 11 bytes)
fmt.Println(utf8.RuneCountInString(s)) // 11 (5 + 1 + 5 = 11 characters)
// Correct way to count characters
count := 0
for range s {
count++
}
fmt.Println(count) // 8
Q4: How do I quickly swap two variables? (Practical tip)
A: Go supports multiple assignment, one line of code does it:
a, b := 10, 20
a, b = b, a // Swap: a=20, b=10
// You can also ignore a value this way
_, err := someFunction() // Use _ to ignore unwanted return value
📖 Summary
- Variable declaration has two methods:
var(universal, can be outside functions) and:=(concise, inside functions only). - Go has rich basic types:
int/uint/float64/bool/string/byte/rune. - The zero value mechanism ensures variables always have a definite value after declaration: numbers
0, booleanfalse, string"", pointernil. constdeclares constants;iotaauto-increments from 0 inconstblocks, suitable for defining enums.- Go does not support implicit type conversion; different types must be explicitly converted, and conversion may lose precision.
- The
fmtpackage is a powerful tool for formatted output:Printlnfor printing,Printffor formatting,Sprintffor generating strings. len(string)returns the byte count; userangeto iterate strings to correctly handle multi-byte characters.string(int)treats integers as Unicode code points, not as number-to-text conversion; usefmt.Sprintffor number-to-text.
📝 Exercises
Exercise 1 (⭐): Variables and Zero Values
Declare the following variables and print their zero values:
- An
intvariablecount - A
float64variableprice - A
boolvariableactive - A
stringvariablemessage
Expected output:
count: 0
price: 0
active: false
message: ""
Exercise 2 (⭐⭐): iota Enum and Type Conversion
Define a Color type (underlying int), use iota to define three colors: Red=0, Green=1, Blue=2. Implement a String() method for Color. Then:
- Declare a
Colorvariable set toGreen, print its name and value. - Convert
float64value3.14159toint, print the result (should be3). - Convert number
2025to string"2025", print the result.
Exercise 3 (⭐⭐⭐): Temperature Conversion Tool
Write a program that does the following:
- Define a constant
AbsoluteZeroC = -273.15(absolute zero in Celsius). - Declare a set of Celsius temperatures:
-40, 0, 37, 100. - Iterate through the temperature array, converting each Celsius temperature to Fahrenheit (formula:
F = C × 9/5 + 32). - Format output as a table, with requirements:
- Celsius and Fahrenheit temperatures rounded to 1 decimal place
- Note whether the temperature is below absolute zero (physically impossible)
- Align column widths
Expected output:
┌─────────┬─────────┬────────────┐
│ Celsius │Fahrenheit│ Status │
├─────────┼─────────┼────────────┤
│ -40.0 │ -40.0 │ Valid │
│ 0.0 │ 32.0 │ Valid │
│ 37.0 │ 98.6 │ Valid │
│ 100.0 │ 212.0 │ Valid │
└─────────┴─────────┴────────────┘
Hint: Use const, var (array/slice), for loop, fmt.Printf formatting, type conversion (int ↔ float64).



