O que é Rune em Go?

Uma rune em Go é um alias para o tipo int32 que representa um único code point Unicode. Em termos práticos, uma rune é um “caractere” — seja uma letra, dígito, símbolo, emoji ou qualquer outro glifo definido pelo padrão Unicode.

A definição na standard library é simples:

type rune = int32

Go usa UTF-8 como encoding padrão para strings, onde cada caractere pode ocupar de 1 a 4 bytes. Uma rune representa o valor numérico do code point Unicode de um caractere, independente de quantos bytes ele ocupa em UTF-8. Por exemplo, a letra ‘A’ é a rune 65 (U+0041), o ‘é’ é a rune 233 (U+00E9) e o emoji ‘🚀’ é a rune 128640 (U+1F680).

Essa distinção entre bytes e runes é fundamental para trabalhar corretamente com texto internacionalizado. Enquanto strings operam no nível de bytes, runes operam no nível de caracteres, e confundir os dois é uma das fontes mais comuns de bugs em programas que lidam com texto Unicode.

Rune vs byte

A distinção entre rune e byte é essencial:

// byte = uint8 — um único byte (0-255)
// rune = int32 — um code point Unicode (0-1114111)

texto := "café"

// Acessando como bytes
fmt.Println(len(texto))     // 5 bytes (não 4 caracteres!)
fmt.Println(texto[3])       // 195 (0xC3) — primeiro byte de 'é'
fmt.Println(texto[4])       // 169 (0xA9) — segundo byte de 'é'

// Acessando como runes
runes := []rune(texto)
fmt.Println(len(runes))     // 4 caracteres
fmt.Println(runes[3])       // 233 (U+00E9) — rune 'é' completa
fmt.Printf("%c\n", runes[3]) // é
Aspectobyte (uint8)rune (int32)
Tamanho1 byte4 bytes
RepresentaUm byteUm code point Unicode
Range0-2550-1.114.111
Suficiente paraASCIIQualquer caractere Unicode
Alias deuint8int32

Rune literals

Rune literals são escritas com aspas simples:

// Caracteres simples
var a rune = 'A'       // 65
var e rune = 'é'       // 233
var sigma rune = 'Σ'   // 931

// Emojis
var foguete rune = '🚀' // 128640
var coracao rune = '❤'  // 10084

// Escape sequences
var newline rune = '\n' // 10
var tab rune = '\t'     // 9
var nulo rune = '\000'  // 0

// Unicode escapes
var omega rune = '\u03A9'     // Ω (U+03A9)
var smiley rune = '\U0001F600' // 😀 (U+1F600)

fmt.Printf("Rune: %c, Valor: %d, Unicode: U+%04X\n", a, a, a)
// Rune: A, Valor: 65, Unicode: U+0041

Range sobre strings entrega runes

O range sobre strings é a forma idiomática de iterar por caracteres em Go, porque ele automaticamente decodifica bytes UTF-8 em runes:

texto := "Olá, 🌍!"

// range decodifica UTF-8 → runes automaticamente
for i, r := range texto {
    fmt.Printf("posição_byte=%d  rune=%c  valor=%d\n", i, r, r)
}
// posição_byte=0  rune=O  valor=79
// posição_byte=1  rune=l  valor=108
// posição_byte=2  rune=á  valor=225
// posição_byte=5  rune=,  valor=44
// posição_byte=6  rune=   valor=32
// posição_byte=7  rune=🌍  valor=127757
// posição_byte=11 rune=!  valor=33

// CUIDADO: note que os índices NÃO são sequenciais!
// 'á' começa na posição 2 e ocupa 2 bytes
// '🌍' começa na posição 7 e ocupa 4 bytes

Note que i no range é a posição em bytes, não em caracteres. Se precisar de índice por caractere, use []rune:

runes := []rune(texto)
for i, r := range runes {
    fmt.Printf("índice_char=%d  rune=%c\n", i, r)
}
// Agora os índices são sequenciais: 0, 1, 2, 3, 4, 5, 6

Conversão entre string, []byte e []rune

original := "São Paulo 🇧🇷"

// String → []rune (decodifica UTF-8)
runes := []rune(original)
fmt.Println(len(runes)) // número de caracteres

// []rune → String (codifica UTF-8)
volta := string(runes)
fmt.Println(volta) // "São Paulo 🇧🇷"

// Rune individual → String
s := string('A')  // "A"
s = string(9731)  // "☃" (boneco de neve)

// int → String (CUIDADO no Go moderno!)
// string(65) cria "A" (rune 65), NÃO "65"
// Para converter número para texto, use strconv:
import "strconv"
s = strconv.Itoa(65) // "65"

O pacote unicode/utf8

O pacote unicode/utf8 é essencial para trabalhar com runes de forma segura:

import "unicode/utf8"

texto := "Programação em Go 🚀"

// Contar caracteres (não bytes)
fmt.Println(utf8.RuneCountInString(texto)) // 19 caracteres
fmt.Println(len(texto))                     // 23 bytes

// Decodificar primeira rune
r, tamanho := utf8.DecodeRuneInString(texto)
fmt.Printf("Primeira rune: %c (%d bytes)\n", r, tamanho)

// Decodificar última rune
r, tamanho = utf8.DecodeLastRuneInString(texto)
fmt.Printf("Última rune: %c (%d bytes)\n", r, tamanho)

// Verificar se string é UTF-8 válido
fmt.Println(utf8.ValidString(texto))   // true
fmt.Println(utf8.ValidString("\xff"))   // false

// Tamanho de uma rune em bytes
fmt.Println(utf8.RuneLen('A'))  // 1
fmt.Println(utf8.RuneLen('é'))  // 2
fmt.Println(utf8.RuneLen('🚀')) // 4

O pacote unicode

Para classificação e transformação de runes:

import "unicode"

// Classificação
fmt.Println(unicode.IsLetter('A'))   // true
fmt.Println(unicode.IsDigit('9'))    // true
fmt.Println(unicode.IsSpace(' '))    // true
fmt.Println(unicode.IsPunct('.'))    // true
fmt.Println(unicode.IsUpper('A'))    // true
fmt.Println(unicode.IsLower('a'))    // true

// Transformação
fmt.Println(unicode.ToUpper('a'))    // 'A'
fmt.Println(unicode.ToLower('A'))    // 'a'
fmt.Println(unicode.ToTitle('a'))    // 'A'

// Verificação de scripts/categorias
fmt.Println(unicode.Is(unicode.Latin, 'A'))     // true
fmt.Println(unicode.Is(unicode.Cyrillic, 'Д'))  // true
fmt.Println(unicode.Is(unicode.Han, '漢'))       // true

Manipulação de texto por caractere

Inverter uma string corretamente

// INCORRETO: inverte bytes, quebra caracteres multi-byte
func inverterErrado(s string) string {
    bytes := []byte(s)
    for i, j := 0, len(bytes)-1; i < j; i, j = i+1, j-1 {
        bytes[i], bytes[j] = bytes[j], bytes[i]
    }
    return string(bytes) // QUEBRADO para texto com acentos!
}

// CORRETO: inverte runes
func inverter(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

fmt.Println(inverter("Olá"))  // "álO" (correto)
fmt.Println(inverter("Go 🚀")) // "🚀 oG" (correto)

Truncar texto por caractere

func truncar(s string, maxChars int) string {
    runes := []rune(s)
    if len(runes) <= maxChars {
        return s
    }
    return string(runes[:maxChars]) + "..."
}

fmt.Println(truncar("Programação em Go", 12)) // "Programação ..."

Contar caracteres específicos

func contarVogais(s string) int {
    count := 0
    for _, r := range s {
        switch unicode.ToLower(r) {
        case 'a', 'e', 'i', 'o', 'u', 'á', 'é', 'í', 'ó', 'ú', 'ã', 'õ':
            count++
        }
    }
    return count
}

Emojis e caracteres compostos

Emojis modernos podem ser compostos por múltiplos code points Unicode:

// Emoji simples: 1 rune
foguete := "🚀"
fmt.Println(len([]rune(foguete))) // 1

// Emoji composto: múltiplas runes
familia := "👨‍👩‍👧‍👦"
fmt.Println(len([]rune(familia))) // 7 (4 pessoas + 3 ZWJ)

// Bandeira: 2 runes (Regional Indicator Symbols)
brasil := "🇧🇷"
fmt.Println(len([]rune(brasil))) // 2

// Pessoa com tom de pele: 2 runes
pessoa := "👋🏽"
fmt.Println(len([]rune(pessoa))) // 2

Para trabalhar corretamente com emojis compostos e clusters de grafemas, considere usar bibliotecas externas como github.com/rivo/uniseg que implementam a segmentação correta de grafemas Unicode.

RuneError e validação

Quando Go encontra bytes inválidos em UTF-8 durante a decodificação, retorna a rune especial utf8.RuneError (U+FFFD):

import "unicode/utf8"

// Bytes inválidos
dados := []byte{0xff, 0xfe, 0x41} // 2 bytes inválidos + 'A'

s := string(dados)
for i, r := range s {
    if r == utf8.RuneError {
        fmt.Printf("Byte inválido na posição %d\n", i)
    } else {
        fmt.Printf("Rune válida: %c na posição %d\n", r, i)
    }
}

Sempre valide input de fontes externas com utf8.ValidString() antes de processar, especialmente ao construir APIs REST que recebem texto de usuários.

Runes em contextos práticos

Validação de CPF/CNPJ

func apenasDigitos(s string) string {
    var b strings.Builder
    for _, r := range s {
        if unicode.IsDigit(r) {
            b.WriteRune(r)
        }
    }
    return b.String()
}

cpf := apenasDigitos("123.456.789-00") // "12345678900"

Normalização de texto para busca

func normalizar(s string) string {
    var b strings.Builder
    for _, r := range strings.ToLower(s) {
        if unicode.IsLetter(r) || unicode.IsDigit(r) || r == ' ' {
            b.WriteRune(r)
        }
    }
    return b.String()
}

Esses padrões são comuns em aplicações backend com PostgreSQL e sistemas de busca.

Boas práticas com runes

  1. Use range para iterar strings — decodifica runes automaticamente
  2. Use []rune quando precisar indexar ou fatiar por caractere
  3. Use utf8.RuneCountInString para contar caracteres, nunca len()
  4. Valide UTF-8 em input externo com utf8.ValidString()
  5. Cuidado com emojis compostos — podem ser múltiplas runes
  6. Use unicode.IsLetter em vez de comparação com ranges ASCII
  7. Prefira strings.Builder.WriteRune para construir strings com runes

Para entender a relação entre runes e strings, e como Go representa texto internamente, explore também o glossário de tipos e os tutoriais para iniciantes.

Perguntas frequentes (FAQ)

Qual a diferença entre rune e byte em Go?

byte é um alias para uint8 (1 byte, range 0-255) e representa um único byte. rune é um alias para int32 (4 bytes, range 0-1.114.111) e representa um code point Unicode completo. Caracteres ASCII (inglês básico) cabem em um byte, mas acentos, CJK e emojis precisam de runes. Sempre use runes quando trabalhar com texto Unicode.

Por que Go usa runes em vez de char como outras linguagens?

Go foi projetado por Ken Thompson e Rob Pike (criadores do UTF-8) com suporte Unicode nativo. O nome “rune” vem da tradição Unix e representa claramente um code point Unicode, sem a ambiguidade de “char” que em C/C++ significa apenas 1 byte. Uma rune de 4 bytes pode representar qualquer caractere do Unicode, incluindo emojis e caracteres CJK.

Como contar o número real de caracteres em uma string Go?

Use utf8.RuneCountInString(s) ou len([]rune(s)). Nunca use len(s) que retorna o número de bytes, não de caracteres. Para “café”: len() retorna 5 (bytes), utf8.RuneCountInString() retorna 4 (caracteres). Para emojis compostos como bandeiras, note que um “caractere visual” pode ser múltiplas runes.

É caro converter string para []rune em Go?

Sim, a conversão []rune(s) aloca memória e decodifica toda a string UTF-8. Para strings grandes, evite conversões desnecessárias. Se só precisa iterar, use range (zero alocação extra). Se precisa de posições específicas, considere utf8.DecodeRuneInString para acessar runes individuais sem converter a string inteira.