Перетворення типів¶
Суворе правило¶
«Явні перетворення обов'язкові, коли в одному виразі або присвоєнні змішуються різні числові типи.» — специфікація Go
У Go немає неявних числових перетворень. Навіть між типами однакового розміру потрібно явно перетворювати.
var i int = 42
var f float64 = i // compile error: cannot use i (int) as float64
var f float64 = float64(i) // ok
var a int32 = 1
var b int64 = a // compile error: cannot use a (int32) as int64
var b int64 = int64(a) // ok
З досвіду Python: Python вільно змішує
intіfloatв арифметиці. Go відмовляє — потрібно явно вказувати перетворення щоразу. Як тільки ви приймаєте це, ви виявите, що в програмах на Go менше помилок типу «стривай, звідки взявся цей float?».
Синтаксис перетворення: T(x)¶
var i int = 42
var f float64 = float64(i) // int → float64
var u uint32 = uint32(f) // float64 → uint32 (відкидає дробову частину до нуля)
var b byte = byte(i) // int → uint8 (відкидає старші біти)
Перетворення числа з рухомою комою в ціле відкидає дробову частину. Число має бути змінною, а не голим літералом — int(3.9), записаний як літерал, є помилкою компіляції, оскільки Go відмовляється мовчки відкидати .9 з нетипізованої константи. Як тільки значення живе у float64, перетворення є коректним:
Перетворення більшого цілого в менше відкидає старші біти — результат загортається:
Рядки, байти та руни¶
Ці три перетворення достатньо поширені, щоб їх запам'ятати.
string ↔ []byte — сирі UTF-8 байти¶
string ↔ []rune — кодові точки Unicode¶
s := "日本語"
r := []rune(s) // [26085 26412 35486]
s2 := string(r) // "日本語"
fmt.Println(len(s)) // 9 — байти
fmt.Println(len(r)) // 3 — руни
Окрема руна до рядка¶
Є тонкість: string(65) також дає "A", але go vet це позначить. Завжди спочатку приводьте до rune, щоб намір був зрозумілий.
Рядки ↔ числа — використовуйте strconv, а не перетворення типів¶
Синтаксис T(x) не розбирає рядок у число. Для цього стандартна бібліотека має strconv.
import "strconv"
// рядок → число
n, err := strconv.Atoi("42")
if err != nil { /* ... */ } // n == 42
f, err := strconv.ParseFloat("3.14", 64) // f == 3.14
b, err := strconv.ParseBool("true") // b == true
// число → рядок
s := strconv.Itoa(42) // "42"
s := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
s := strconv.FormatInt(255, 16) // "ff"
fmt.Sprintf — це універсальна альтернатива: повільніша, але гнучка:
З досвіду Python:
int("42")іstr(42)у Python є глобальними вбудованими функціями. Go поміщає їх уstrconv, щоб зробити вартість (і можливість помилки) явною.strconv.Atoiповертає(int, error)— жодних винятків для перехоплення немає.
Нетипізовані константи — єдиний виняток¶
Це, мабуть, найбільша пастка для Python-розробника, що вивчає Go, і вона зринає в кількох місцях далі в курсі, тому варто приділити їй хвилину.
Нетипізовані константи не мають типу до моменту використання¶
Літерал на кшталт 42, 3.14 або "hi", записаний у вихідному коді, є нетипізованою константою. Вона не має фіксованого типу Go — вона несе ідеалізоване числове значення (довільна точність для чисел). Тип визначається лише тоді, коли цього вимагає контекст.
var x float64 = 42 // ok — 42 є нетипізованою цілочисельною константою, підходить для float64
var y int = 0.0 // ok — 0.0 можна представити як int (немає дробової частини)
var z int = 3.14 // compile error: 3.14 (untyped float) truncated to int
Третій рядок — це те саме правило, що спрацювало вище з int(3.9): Go відмовляється мовчки відкидати дробову частину константи.
Арифметика констант також є довільно точною¶
Наслідок, який дивує Python-розробників: коли ви виконуєте арифметику над константами, Go робить це під час компіляції з довільною точністю, а не з числами IEEE 754. Класична пастка порівняння чисел з рухомою комою не спрацьовує:
fmt.Println(0.1 + 0.2 == 0.3) // true (constants)
var a, b, c float64 = 0.1, 0.2, 0.3
fmt.Println(a + b == c) // false (float64 variables)
Ті самі числа, зовсім різні відповіді — залежно від того, чи виразу до часу виконання, чи він згортається під час компіляції.
Щойно константа отримала тип, правило відновлюється¶
Саме тому більшість констант на рівні пакету залишаються нетипізованими — вони адаптуються до будь-якого типу, потрібного в місці виклику. Явний тип вказуйте лише тоді, коли хочете їх обмежити.
З досвіду Python: у Python немає справжнього аналога.
42— це завждиint,3.14— завждиfloat, а операції підпорядковуються семантиці часу виконання. Модель Go «константа — це значення, а не типізована річ» справді інша — і вона є джерелом кількох тонких відмінностей у поведінці, з якими ви зустрінетеся (результати порівняння літерал-проти-змінна, що компілюється, а що ні, типи за замовчуванням у:=).
T(x) також працює між користувацькими типами¶
Той самий синтаксис T(x) підходить для будь-яких двох типів, що мають спільний базовий тип, — не лише для зумовлених числових. Повна розповідь є в 09-custom-types.md; коротко:
type Celsius float64
type Fahrenheit float64
c := Celsius(20.0)
f := Fahrenheit(c) // ok — однаковий базовий тип
var raw float64 = float64(c) // ok — знімаємо іменований тип
Перетворення інтерфейсів (ствердження типів) використовують інший синтаксис — розглянемо в наступній темі.
Короткий довідник¶
| Що | Синтаксис | Примітки |
|---|---|---|
| Числові між типами | T(x) |
Завжди явно. Відкидає дроби; загортає при звуженні. |
string → байти |
[]byte(s) |
UTF-8 байти, незмінна копія. |
байти → string |
string(b) |
Копіює. |
string → руни |
[]rune(s) |
Декодує UTF-8 у кодові точки. |
руни → string |
string(r) |
Перекодовує у UTF-8. |
string → число |
strconv.Atoi, strconv.ParseInt, strconv.ParseFloat, ... |
Повертає (value, error). |
число → string |
strconv.Itoa, strconv.FormatInt, strconv.FormatFloat або fmt.Sprintf |
Вибирайте за швидкістю/гнучкістю. |
| Окрема руна → рядок | string(rune(0x4e2d)) |
Завжди спочатку приводьте до rune. |