Перейти до змісту

Змінні та константи

var — повна форма

var оголошує одну або кілька змінних. Підтримуються чотири форми.

// 1. Тип з ініціалізатором.
var name string = "Ada"
var u, v, w float64 = -1.0, -2.0, -3.0

// 2. Тип без ініціалізатора — змінна отримує нульове значення.
var counter int          // counter == 0
var greeting string      // greeting == ""
var active bool          // active  == false

// 3. Ініціалізатор без явного типу — тип виводиться автоматично.
var k = 0                // k is int
var pi = 3.14            // pi is float64

// 4. Згруповані оголошення (поширено на рівні пакету).
var (
    host     = "localhost"
    port     = 8080
    verbose  bool
)

Нульові значення

Зі специфікації Go:

"В іншому випадку кожна змінна ініціалізується своїм нульовим значенням."

У Go немає «неініціалізованого» стану — кожна змінна завжди має значення.

Тип Нульове значення
числові (int, float64, …) 0
bool false
string ""
вказівник, зріз, мапа, канал, функція, інтерфейс nil
структура структура, поля якої мають власні нульові значення
var s []int           // s == nil, len(s) == 0
var m map[string]int  // m == nil — читання коректне, запис спричинить паніку
var p *int            // p == nil

:= — коротке оголошення

Усередині функції := оголошує та присвоює за один крок. Тип завжди виводиться автоматично.

func main() {
    name := "Ada"                  // string
    count := 0                     // int
    ratio := 0.5                   // float64
    items := []string{"a", "b"}    // []string — зріз; розглянемо пізніше

    fmt.Println(name, count, ratio, items)
}

Два правила, які варто запам'ятати:

  1. Тільки в межах функції. := заборонено на рівні пакету — там потрібно використовувати var.
  2. Повторне оголошення дозволяється, якщо хоча б одне ім'я ліворуч є новим, а ті, що повторюються, зберігають той самий тип:
    field1, offset := nextField(s, 0)
    field2, offset := nextField(s, offset)  // ok: offset повторно оголошено, field2 нове
    
    x, y := 1, 2
    x, y := 3, 4                            // помилка компіляції: no new variables
    

З досвіду Python: Python дозволяє присвоювати де завгодно без зайвих оголошень. У Go є два способи (var та :=): перший — єдиний дозволений на рівні пакету, другий дає зручний висновок типу та підтримку множинного повернення всередині функцій.

Множинне присвоєння та порожній ідентифікатор _

Схоже на розпакування кортежу у Python, але для повернення значень із функцій:

n, err := strconv.Atoi("42")
if err != nil {
    return err
}
fmt.Println(n)

_, err = io.Copy(dst, src)   // ігноруємо кількість байтів, зберігаємо помилку

_ — це порожній ідентифікатор: слот «лише для запису», що дозволяє відкинути непотрібне значення.

Пастка затінення

Новий := у внутрішній області видимості створює нову змінну, яка затінює зовнішню. Читайте уважно:

err := doFirst()
if err != nil { return err }

if cond {
    err := doSecond()        // !!! НОВА err, затінює зовнішню
    log.Println(err)
}
// зовнішня err тут досі та, що з doFirst, а НЕ з doSecond.

Це одна з найпоширеніших помилок у Go. Аналізатор тіней go vet і golangci-lint попередять вас.

const — константи часу компіляції

Константи обчислюються під час компіляції. Вони можуть бути типізованими або нетипізованими.

const Pi float64 = 3.14159     // типізована
const Greeting = "hello"       // нетипізований рядок

const (
    KB = 1024
    MB = 1024 * KB
    GB = 1024 * MB
)

Константі не можна присвоїти нове значення. Також не можна отримати її адресу (&Pi є помилкою компіляції).

Нетипізовані константи є гнучкими — вони набувають того типу, який потрібен у контексті:

const limit = 10
var i int     = limit          // limit використовується як int
var f float64 = limit          // limit використовується як float64
var s string  = "small"
const small   = "small"
fmt.Println(s == small)        // працює — small підходить для string

Якби limit було оголошено як const limit int = 10, другий рядок не скомпілювався б — int не можна присвоїти float64 без явного перетворення.

iota — константи з автоматичним інкрементом

iota — це зумовлений ідентифікатор, який скидається до 0 на початку кожного блоку const і збільшується на 1 з кожним ConstSpec.

const (
    Sunday = iota   // 0
    Monday          // 1
    Tuesday         // 2
    Wednesday       // 3
    Thursday        // 4
    Friday          // 5
    Saturday        // 6
)

Вираз у кожному рядку неявно повторюється з попереднього рядка. Можна використовувати зі зсувами бітів для прапорців-перерахувань:

const (
    ReadPerm    = 1 << iota  // 1   (iota == 0)
    WritePerm                // 2   (iota == 1)
    ExecutePerm              // 4   (iota == 2)
)

perms := ReadPerm | WritePerm  // == 3

iota існує лише всередині блоків const — загального синтаксису лічильника не існує.

Джерела