Статическая типизация и краткое объявление переменных

Краткое описание

В этом уроке изучаем строгую статическую типизацию Go: почему типы нельзя смешивать без явного приведения, как работает автоматический вывод типов компилятором и когда использовать краткую форму объявления := вместо полной var. Разбираем отличия Go от динамически типизированных языков вроде JavaScript.

Ключевые концепции

Строгая статическая типизация

Статическая типизация означает, что тип переменной определяется на этапе компиляции и не может быть изменён во время выполнения программы.

Правило: если переменная объявлена с типом int, она навсегда остаётся int. Нельзя присвоить ей строку, float или другой тип.

var number int = 42
fmt.Printf("number: %d, тип: %T\n", number, number)

// Можно изменить значение
number = 100  // OK

// НЕЛЬЗЯ изменить тип
// number = "строка"  // ОШИБКА компиляции
// number = 3.14      // ОШИБКА компиляции

Отличие от JavaScript

В JavaScript (динамическая типизация) переменная может менять тип:

let val = 42;
console.log(typeof val);  // "number"

val = "123";
console.log(typeof val);  // "string" - тип изменился!

В Go это невозможно — тип фиксируется навсегда при объявлении.

Запрет операций между разными типами

Go не имеет автоматического приведения типов при операциях. Нельзя смешивать int и float64:

var intNum int = 10
var floatNum float64 = 3.14

// ОШИБКА компиляции!
// var result = intNum + floatNum
// invalid operation: intNum + floatNum (mismatched types int and float64)

Компилятор откажется компилировать такой код.

Явное приведение типов

Синтаксис преобразования

Для операций между разными типами нужно явное приведение:

тип(значение)

Примеры приведения

int → float64:

var intNum int = 10
var floatNum float64 = 3.14

var result = float64(intNum) + floatNum
// result = 10.0 + 3.14 = 13.14 (тип float64)

float64 → int (потеря дробной части):

var result = intNum + int(floatNum)
// result = 10 + 3 = 13 (тип int, .14 отброшено!)

Важно: при приведении float к int дробная часть отбрасывается (не округляется):

var f float64 = 3.99
var i int = int(f)  // i = 3 (не 4!)

Рекомендация по направлению конвертации

В большинстве случаев конвертируйте int → float64, чтобы не терять дробную часть:

// Правильно - сохраняем точность
var result = float64(intNum) + floatNum

// Неправильно - теряем дробную часть
var result = intNum + int(floatNum)  // потеря .14

Автоматический вывод типов (Type Inference)

Компилятор умеет определять тип

Go может автоматически вывести тип из значения справа от =:

var inferredInt = 42      // компилятор выводит int
var inferredFloat = 3.14  // компилятор выводит float64
var inferredString = "Go" // компилятор выводит string
var inferredBool = true   // компилятор выводит bool

Тип указывать не обязательно, если он очевиден из контекста.

Правила вывода типов

  • Целое число без точки → int
  • Число с точкой → float64
  • Строка в ""string
  • true/falsebool
fmt.Printf("%T\n", 42)      // int
fmt.Printf("%T\n", 3.14)    // float64
fmt.Printf("%T\n", "text")  // string
fmt.Printf("%T\n", true)    // bool

Оператор короткого объявления :=

Синтаксис

Краткая форма объявления и инициализации:

имя := значение

Эквивалентно:

var имя тип = значение  // где тип выводится компилятором

Примеры использования

Полная форма vs краткая:

// Полная форма
var age int = 25

// Краткая форма (эквивалент)
age := 25

Автоматический вывод типа:

x := 42           // int
pi := 3.14159     // float64
name := "Иван"    // string
isActive := true  // bool

Ограничение: только внутри функций

Оператор := работает только внутри функций. На уровне пакета (вне функций) обязательно используйте var:

package main

// ВНЕ функции - только var
var globalVar int = 100
// globalVar := 100  // ОШИБКА! := работает только в функциях

func main() {
    // ВНУТРИ функции - можно :=
    localVar := 200  // OK
}

Когда использовать var vs :=

Используйте var когда:

1. На уровне пакета (обязательно):

package main

var PackageLevel int = 100  // обязательно var

2. Нужно явно указать тип:

var age int = 25           // явно показываем int
var timeout float64 = 30   // явно показываем float64

3. Объявление без инициализации:

var result int  // объявили, но не присвоили
// ... код ...
result = calculateSomething()  // присвоили позже

С := так нельзя — оно требует инициализации:

// result :=  // ОШИБКА! Нужно значение справа

4. Нулевое значение нужно явно:

var count int        // 0
var message string   // ""
var isReady bool     // false

Используйте := когда:

1. Внутри функций для краткости (частый случай):

func main() {
    name := "Сергей"      // короче и читаемее
    age := 25
    isStudent := true
}

2. Тип очевиден из контекста:

x := 10
y := 20
sum := x + y  // очевидно int

3. Множественное присваивание:

x, y := 12, 23
name, age := "Иван", 30

4. В циклах, условиях (будет в следующих уроках):

for i := 0; i < 10; i++ {
    // i объявлена через :=
}

if err := doSomething(); err != nil {
    // err объявлена через :=
}

Множественное объявление переменных

Множественное присваивание через :=

x, y := 12, 23
name, age := "Иван", 30

Эквивалентно:

var x int = 12
var y int = 23

Множественное объявление через var

Раздельно:

var x int = 12
var y int = 23

Вместе без типа:

var x, y = 12, 23  // компилятор выводит int

Вместе с типом:

var x, y int = 12, 23

Блоком:

var (
    x int = 12
    y int = 23
    z int = 34
)

Ошибки повторного объявления

Нельзя объявлять дважды

Ошибка - два var:

var age int = 25
var age int = 30  // ОШИБКА! age redeclared

Ошибка - два :=:

age := 25
age := 30  // ОШИБКА! no new variables on left side of :=

Ошибка - var затем :=:

var age int
age := 25  // ОШИБКА! no new variables on left side of :=

Правильное переприсваивание

После объявления используйте = (без двоеточия):

var age int = 25
age = 30  // OK - просто присваивание

// или
age := 25
age = 30  // OK

Безопасность типов

Ошибки типов при компиляции

Go проверяет типы на этапе компиляции, а не во время выполнения:

func processNumber(n int) {
    fmt.Printf("Число: %d\n", n)
}

func main() {
    processNumber(42)        // OK
    // processNumber("42")   // ОШИБКА компиляции
    // processNumber(3.14)   // ОШИБКА компиляции
}

Преимущество: баги с типами ловятся до запуска программы, а не в production.

Примеры ошибок компиляции

// cannot use "42" (type string) as type int in argument
processNumber("42")

// cannot use 3.14 (type float64) as type int in argument
processNumber(3.14)

// invalid operation: intNum + floatNum (mismatched types)
var result = intNum + floatNum

Практические примеры

Полный пример из урока

package main

import "fmt"

func main() {
    // Тип фиксируется при объявлении
    var number int = 42
    fmt.Printf("number: %d, тип: %T\n", number, number)

    // Можно изменить значение, но не тип
    number = 100
    fmt.Printf("После изменения: %d, тип: %T\n", number, number)

    // ОШИБКА: нельзя изменить тип
    // number = "строка"  // cannot use "строка" (type string)
    // number = 3.14      // cannot use 3.14 (type float64)

    // Нет автоматического приведения
    var intNum int = 10
    var floatNum float64 = 3.14
    
    // ОШИБКА: разные типы
    // var result = intNum + floatNum

    // Правильно: явное приведение
    var result1 = float64(intNum) + floatNum  // 13.14
    var result2 = intNum + int(floatNum)      // 13 (потеря .14)

    fmt.Printf("float64 результат: %.2f\n", result1)
    fmt.Printf("int результат: %d\n", result2)

    // Краткая форма объявления
    age := 25              // int
    price := 99.99         // float64
    name := "Сергей"       // string
    isActive := true       // bool

    fmt.Printf("age: %d (тип: %T)\n", age, age)
    fmt.Printf("price: %.2f (тип: %T)\n", price, price)
    fmt.Printf("name: %s (тип: %T)\n", name, name)
    fmt.Printf("isActive: %t (тип: %T)\n", isActive, isActive)

    // Множественное объявление
    x, y := 12, 23
    sum := x + y
    fmt.Printf("%d + %d = %d\n", x, y, sum)

    // Функция принимает только int
    processNumber(42)
    // processNumber("42")   // ОШИБКА компиляции
    // processNumber(3.14)   // ОШИБКА компиляции
}

func processNumber(n int) {
    fmt.Printf("Обработка числа: %d\n", n)
}

Важные моменты

1. Тип фиксируется навсегда После объявления переменной с типом int она всегда будет int. Изменить тип невозможно.

2. Нет неявных преобразований Go не преобразует типы автоматически. Всегда требуется явное float64(x) или int(y).

3. := только в функциях На уровне пакета используйте только var. Оператор := работает исключительно внутри функций.

4. Потеря точности при int(float) При приведении float к int дробная часть отбрасывается: int(3.99) = 3.

5. Вывод типа работает с обеими формами

var x = 42    // компилятор выводит int
x := 42       // то же самое

6. Нельзя переобъявлять Переменную можно объявить только один раз. После этого используйте = для присваивания.

7. Безопасность на этапе компиляции Все ошибки типов ловятся компилятором до запуска программы — не нужно ждать runtime ошибок.

8. Стиль зависит от контекста В профессиональных проектах часто предпочитают := внутри функций для краткости, но явный var с типом для важных переменных.

Что запомнить

  • Go строго статически типизирован — тип фиксируется навсегда
  • Нельзя присвоить переменной значение другого типа
  • Нельзя смешивать типы без явного приведения
  • int(x), float64(y) — явное приведение типов
  • При int(float) дробная часть теряется
  • Компилятор автоматически выводит типы из значений
  • := — краткая форма объявления с инициализацией
  • := работает только внутри функций
  • var обязательно на уровне пакета
  • x, y := 12, 23 — множественное объявление
  • Нельзя переобъявлять переменную (повторный := или var)
  • После объявления используйте = для присваивания
  • Ошибки типов ловятся при компиляции
  • Предпочитайте := для краткости, var когда нужна явность
  • Конвертируйте int → float64 для сохранения точности

Полезные ссылки