Власні типи¶
Go дозволяє визначати власні іменовані типи. Є два різновиди оголошення — вони виглядають майже однаково, але означають абсолютно різні речі.
type Celsius float64 // визначення типу — новий, окремий тип
type Temp = float64 // псевдонім типу — просто ще одна назва для float64
Єдина відмінність — знак =. Наслідки значні.
Навіщо створювати власні типи?¶
Три причини, у порядку важливості:
- Компілятор не дасть змішати те, що не слід змішувати. Значення
Celsiusне можна додати до значенняFahrenheitбез явного перетворення. - До них можна прикріплювати методи. Лише визначені типи (не псевдоніми) можуть мати методи — це розглядається пізніше, але саме заради цього й варто вдаватися до
type Foo Bar. - Назви передають намір.
UserIDдокументує себе краще, ніж черговийint64у сигнатурі функції.
Визначення типу: type Foo Bar¶
Зі специфікації Go:
"Визначення типу створює новий, окремий тип із тим самим базовим типом та операціями, що й заданий тип [...]. Він відрізняється від будь-якого іншого типу, включно з тим, від якого був створений."
type Celsius float64
type Fahrenheit float64
var c Celsius = 20
var f Fahrenheit = 68
sum := c + f // compile error: mismatched types Celsius and Fahrenheit
Celsius і Fahrenheit — різні типи. Обидва розміщені в пам'яті точно як float64 — це їхній спільний базовий тип — але компілятор вважає їх різними, оскільки вони мають різні назви.
Можна перетворювати між ними за допомогою синтаксису T(x) (розглянуто в 03-type-conversions.md):
Типові патерни¶
Семантичні обгортки над примітивами:
type UserID int64
type Email string
type Money int64 // зберігаємо центи, щоб уникнути float
func sendEmail(to Email, user UserID) error { /* ... */ }
var id UserID = 42
var addr Email = "ada@example.com"
sendEmail(addr, id) // ok — порядок аргументів відповідає типам
sendEmail(id, addr) // compile error: неправильний порядок, компілятор ловить помилку
Це безкоштовна мережа безпеки. Без UserID як окремого типу обидва аргументи мали б тип int64/string, і неправильний порядок компілювався б без помилок.
Іменовані типи функцій:
type Handler func(req string) string
func register(h Handler) { /* ... */ }
register(func(req string) string {
return "echo: " + req
})
Іменовані типи зрізів або мап:
Константи у стилі enum — зазвичай у поєднанні з iota:
type Weekday int
const (
Sunday Weekday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
func isWeekend(d Weekday) bool {
return d == Sunday || d == Saturday
}
isWeekend(Monday) // ok — перевіряється тип
isWeekend(5) // ok — 5 є нетипізованою цілою константою, стає Weekday
isWeekend(int(5)) // compile error — int не є Weekday
Псевдонім типу: type Foo = Bar¶
Зі специфікації:
"Оголошення псевдоніма прив'язує ідентифікатор до заданого типу. У межах видимості ідентифікатора він слугує псевдонімом для цього типу."
Псевдонім — не новий тип, а друга назва того самого типу.
type Celsius = float64 // псевдонім
var c Celsius = 20
var f float64 = c // ok — c І Є float64
var sum = c + f // ok — той самий тип, перетворення не потрібне
Порівняйте з формою визначення вище: при type Celsius float64 компілятор відхилив би var f float64 = c. Із псевдонімом він приймає.
Коли використовувати псевдоніми (рідко)¶
Переважна більшість коду потребує визначення типів, а не псевдонімів. Псевдоніми існують переважно для:
- Поступового рефакторингу між пакетами — тимчасово відкривати
oldpkg.Fooякnewpkg.Foo, доки клієнти мігрують. - Скорочення довгих назв типів —
type list = []*linkedListNode[T]для читабельності всередині одного файлу. - Стандартна бібліотека використовує їх обережно —
byte = uint8іrune = int32є псевдонімами. Саме томуbyteіuint8взаємозамінні скрізь.
var b byte = 65
var u uint8 = b // ok — псевдонім означає той самий тип
var c uint32 = uint32(b) // ok — потрібне явне перетворення (різні типи)
Ключове обмеження: до псевдоніма не можна прикріплювати методи. Псевдонім не має окремої ідентичності, до якої їх прикріпити.
Якщо потрібні методи, використовуйте визначення типу.
Методи — головна перевага визначень типів¶
Після визначення типу до нього можна прикріплювати методи — саме заради цього й варто вдаватися до type Foo Bar. Механіка (отримувачі за значенням та за вказівником, набори методів, вбудовування) розглядається в 10-methods.md. Єдине правило, яке варто зазначити тут: методи можна визначати лише для типів із власного пакету — ніколи для int, string, time.Duration чи будь-якого іншого стороннього типу.
«Базовий тип» — точне правило¶
Зі специфікації, переказано як процес:
- Попередньо оголошені типи (
int,string,bool,float64, ...) — їхній базовий тип — вони самі. - Літерали типів (
[]int,map[string]int,struct{...},func(...) ...) — їхній базовий тип — вони самі. - Будь-який інший тип — слідуйте ланцюжку визначень, поки не дійдете до одного з вищезгаданих.
type A = string // базовий тип: string
type B string // базовий тип: string (ланцюжок псевдоніму: B → string)
type C B // базовий тип: string (ланцюжок: C → B → string)
type D struct { // базовий тип: struct{Name string} (літерал типу)
Name string
}
Перевірити під час виконання можна через reflect.TypeOf(x).Kind() — повертається базовий різновид, а не іменований тип.
Ідентичність типів в одному абзаці¶
Два типи є ідентичними з погляду Go, якщо:
- Це той самий визначений тип (та сама назва, той самий пакет), АБО
- Обидва є літералами типів зі структурно сумісними базовими типами.
Визначений тип ніколи не є ідентичним своєму базовому типу чи будь-якому іншому визначеному типу, навіть якщо розміщення в пам'яті збігається. Саме тому Celsius і Fahrenheit не можна змішувати.
Коротка довідка¶
| Форма | Новий тип? | Методи? | Сумісний з оригіналом? |
|---|---|---|---|
type Foo Bar (визначення) |
так | так | ні — потрібне явне перетворення |
type Foo = Bar (псевдонім) |
ні | ні | так — той самий тип |
Джерела¶
- Оголошення типів — go.dev/ref/spec#Type_declarations
- Визначення типів — go.dev/ref/spec#Type_definitions
- Оголошення псевдонімів — go.dev/ref/spec#Alias_declarations
- Базові типи — go.dev/ref/spec#Types
- Ідентичність типів — go.dev/ref/spec#Type_identity
- Оголошення методів — go.dev/ref/spec#Method_declarations