Avatar ·

Как сгенерировать UUID в Go? Нет встроенной функции?

Всем привет! Я только начал изучать Go и столкнулся с задачей: нужно генерировать уникальные идентификаторы (UUID) для записей в базе данных. В Python я привык использовать `uuid.uuid4()`, а в Go, как я ни искал в стандартной библиотеке, не нашел ничего похожего. Только `crypto/rand` для случайных чисел и `fmt.Sprintf` для форматирования. Я попробовал написать свою реализацию — просто сгенерировать 16 случайных байт и вручную расставить дефисы, как в стандартном формате UUID. Но, во-первых, это выглядит коряво, а во-вторых, я не уверен, что байты будут соответствовать версии 4 UUID (случайный). Мой код: ```go package main import ( "crypto/rand" "fmt" ) func main() { b := make([]byte, 16) _, err := rand.Read(b) if err != nil { panic(err) } uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) fmt.Println(uuid) } ``` Этот код работает, но он не устанавливает правильные версию и вариант UUID (биты 4-7 в 7-м байте и биты 6-7 в 9-м байте). Есть ли в Go стандартный способ или хорошая библиотека, которую можно подключить? Я слышал про пакет `github.com/google/uuid`, но не знаю, насколько он надежен. Подскажите, как правильно генерировать UUID в Go, чтобы не изобретать велосипед и соответствовать стандартам RFC 4122? <<>>

Правильный способ: используйте пакет github.com/google/uuid

В Go нет встроенной функции для генерации UUID в стандартной библиотеке (и, скорее всего, никогда не будет — разработчики Go предпочитают держать ядро минимальным). Однако существует официально рекомендованный и самый популярный пакет от Google — github.com/google/uuid. Он полностью соответствует RFC 4122, поддерживает все версии UUID (1, 3, 4, 5) и является де-факто стандартом в Go-сообществе.

Установка

go get github.com/google/uuid

Пример: генерация UUID v4 (случайный)

UUID v4 — самый распространённый тип для большинства задач. Он генерируется из криптостойкого случайного источника:

package main

import (
    "fmt"
    "github.com/google/uuid"
)

func main() {
    // Генерация одного UUID
    id := uuid.New()
    fmt.Println(id.String()) // например: f47ac10b-58cc-4372-a567-0e02b2c3d479

    // Или сразу как строку
    fmt.Println(uuid.New().String())
}

Другие версии UUID

  • UUID v1 (на основе времени и MAC-адреса): uuid.NewUUID()
  • UUID v3 (на основе MD5-хеша от namespace и имени): uuid.NewMD5(uuid.NameSpaceDNS, []byte("example.com"))
  • UUID v5 (на основе SHA1-хеша): uuid.NewSHA1(uuid.NameSpaceURL, []byte("https://example.com"))

Парсинг существующего UUID

id, err := uuid.Parse("f47ac10b-58cc-4372-a567-0e02b2c3d479")
if err != nil {
    panic(err)
}
fmt.Println(id.Version()) // 4

Почему ваша самодельная реализация плоха?

Ваш код имеет две проблемы:

  1. Не устанавливаются биты версии и варианта. В UUID v4 7-й байт должен иметь старшие 4 бита равные 0100 (версия 4), а 9-й байт — старшие 2 бита равные 10 (вариант RFC 4122). Без этого ваш UUID не соответствует стандарту и может быть не принят другими системами.
  2. Форматирование через fmt.Sprintf неэффективно при массовой генерации — создаётся много временных строк.

Пакет google/uuid делает всё правильно и эффективно. Он использует crypto/rand под капотом, но самостоятельно выставляет нужные биты. Кроме того, он предоставляет тип uuid.UUID, который можно сериализовать в JSON, хранить в базе данных как строку или 16-байтовый массив.

Альтернативы (если не хотите внешних зависимостей)

Если вам критично не использовать сторонние пакеты (например, в учебных целях или микросервисе с жёсткими ограничениями), можно написать корректную реализацию самостоятельно:

package main

import (
    "crypto/rand"
    "fmt"
)

func main() {
    uuid := make([]byte, 16)
    _, _ = rand.Read(uuid)

    // Устанавливаем версию 4 (биты 4-7 в 7-м байте)
    uuid[6] = (uuid[6] & 0x0f) | 0x40
    // Устанавливаем вариант RFC 4122 (биты 6-7 в 9-м байте)
    uuid[8] = (uuid[8] & 0x3f) | 0x80

    // Форматируем в стандартный вид
    fmt.Printf("%08x-%04x-%04x-%04x-%012x\n",
        uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16])
}

Но я настоятельно рекомендую использовать готовую библиотеку — она протестирована, оптимизирована и избавит вас от головной боли с edge cases.

<<>>

Пакет github.com/satori/go.uuid (альтернатива)

Если по какой-то причине пакет от Google вам не подходит (например, вы используете старую версию Go или нужна поддержка UUID v6/v7/v8), обратите внимание на github.com/satori/go.uuid. Он тоже очень популярен (более 5k звезд на GitHub) и предоставляет схожий функционал.

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

package main

import (
    "fmt"
    "github.com/satori/go.uuid"
)

func main() {
    // UUID v4
    id := uuid.NewV4()
    fmt.Println(id.String())

    // UUID v1
    id1, _ := uuid.NewV1()
    fmt.Println(id1.String())

    // Парсинг
    parsed, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
    if err != nil {
        panic(err)
    }
    fmt.Println(parsed.Version())
}

Различия между пакетами

  • Google UUID — более современный, активно поддерживается Google, использует crypto/rand по умолчанию, имеет метод uuid.Nil для пустого UUID.
  • Satori UUID — старше, но всё ещё работает. Некоторые разработчики отмечают, что он менее производительный при массовой генерации.

Оба пакета решают вашу задачу. Выбирайте тот, который вам удобнее по API. Лично я рекомендую Google UUID как более современный стандарт.