Lidando com 'runes'

Entendendo um pouco mais sobre o tipo básico 'rune'

· 4 min Atualizado em

#Runes em Go

O tipo rune (ou “rune literal”) é um alias para o tipo int32 que representa um “ponto de código” (“code point”) na tabela Unicode/UTF-81. Ou seja: o valor de uma variável do tipo rune é um número que aponta para um caractere.

Para referência durante o artigo, vou utilizar a tabela Unicode presente neste site.

#Uma string é uma sequência de runes?

Esse é um ponto importante que já me confundiu antes e que eu já vi bastante gente perguntando em fóruns. Uma string é uma sequência de bytes2, não de runes. Cada item dessa sequência é representado por um uint8 (alias para byte).

Abaixo um pequeno programa que ilustra as diferenças:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
	"fmt"
)

func main() {
	s := "⌘"
	fmt.Println("usando string")
	fmt.Printf("string: %s\n", s)
	fmt.Print("hex: ")
	for i := 0; i < len(s); i++ {
		fmt.Printf("%#x ", s[i])
	}
	fmt.Print("\nval: ")
	for i := 0; i < len(s); i++ {
		fmt.Printf("%v ", s[i])
	}

	r := '⌘'
	fmt.Println("\n\n\nusando rune")
	fmt.Printf("string: %s\n", string(r))
	fmt.Printf("hex: %#x", r)
	fmt.Printf("\nval: %v", r)
}
Execute online no Go Playground

O mesmo caractere, primeiro em string, é composto por uma sequência de 3 bytes (podemos notar isso pela quantidade de vezes que o laço for escreveu na tela ou pelo output do len). Quando guardado em uma variável rune é tratado como apenas um valor (de 4 bytes).

Essa diferença é fundamental. Se você indexar2 uma string, existe a chance de que os valores retornados não sejam o que o esperado - caso um caractere seja composto por mais de 1 byte, apenas uma parte do ponto de código dele estará disponível em cada pedaço da sequência.

#Conversão para int

Ao tentar converter uma rune para int diretamente, o resultado será o valor decimal que representa o caractere na tabela Unicode. Por exemplo: ‘0’ viraria 48 que não é o resultado esperado.

A maneira correta de converter uma rune em número é primeiro convertê-la para string e depois utilizar Itoa para recuperar o seu valor numérico. Entretanto, existe uma espécie de “truque” que podemos utilizar para agilizar este processo.

Na tabela Unicode, os números de 0 a 9 começam na posição 48 (decimal). Como já foi dito acima, uma rune nada mais é que um int32. Portanto, podemos simplesmente subtrair o número desejado de 48. Ainda mais fácil, podemos subtrair da rune ‘0’:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
	"fmt"
)

func main() {
	var num rune
	num = '5'

	fmt.Printf("sem subtrair: %d\n", num)
	fmt.Printf("após subtrair: %d\n", num-'0')
}
Execute online no Go Playground

O número 5 está no ponto 53 da tabela. Logo, se subtrairmos 48 (onde o 0 está), o resultado naturalmente é 5.

#Conclusão

A minha motivação para escrever este artigo foi um problema que tive há algumas semanas. Eu tinha uma string composta por números e precisava somar estes valores.

A primeira coisa que fiz foi indexar2 minha string e somar cada parte da sequência. O resultado foi um número enorme que não fazia sentido algum. Então tentei converter a string em uma sequência de runes… o resultado foi o mesmo. Foi então que parei para investigar mais a fundo.

Eu espero que este artigo tenha esclarecido o funcionamento de rune e como lidar com caracteres em Go.

#Referências

Alguns links que me foram úteis durante os meus estudos sobre o assunto (todos em inglês):


  1. UTF-8 (8-bit Unicode Transformation Format) é um tipo de codificação binária (Unicode) de comprimento variável. UTF-8 usa de um a quatro bytes por caractere. É necessário apenas um byte para codificar os 128 caracteres ASCII. ↩︎

  2. No artigo “Strings, bytes, runes and characters in Go” citado acima é explicado que strings funcionam como sequências de bytes o que significa que é possível iterar sobre cada parte dela. Se a string for composta por caracteres ASCII, cada parte da string vai conter um caractere. Existe um exemplo de como iterar sobre uma string no primeiro código deste artigo, nas linhas 12-14 e 16-18. ↩︎

Você pode me encontrar no twitter ou no github!

© Marcelo Gomes Jr 2011-2020