Lidando com Unicode em Go

Se Go normalmente é um passeio no parque, trabalhar com Unicode em Go pode ser descrito como um passeio inesperado por um campo minado. Tomemos, por exemplo, esta cadeia discreto da primeira página: "Hello, 世界". O que acontece se obtivermos o comprimento desta string?

fmt.Println(len("Hello, 世界"))
>>> 13

Espere, o que aconteceu? O comprimento não deveria ser 9? De onde vieram os 4 personagens extras?

Por baixo do capô, Go está, na verdade, codificando a string como uma matriz de bytes. Embora não faça você distinguir entre strings ASCII normais e strings Unicode como Python 2.x, ainda não abstrai a codificação de bytes subjacente dos caracteres. Como os caracteres chineses ocupam três bytes enquanto os caracteres ASCII ocupam apenas um, Go informa o tamanho 1*7+3*2=13. Isso pode ser muito confuso e uma armadilha enorme e interessante para aqueles que apenas testam seu código com valores ASCII. Considere por exemplo:

hello := "Hello, 世界"
for i := range hello {
fmt
.Print(string(hello[i]))
}
>>> Hello, äç

Err, ok, como 世界 se tornou äç? Já posso ouvir você gritando, “mas você pode apenas usar o valor de retorno do segundo intervalo!” Certamente você pode!

hello := "Hello, 世界"
for _, c := range hello {
fmt
.Print(string(c))
}
>>> Hello, 世界

Muito melhor! Ah, mas nem sempre podemos fazer assim, podemos? Como um exemplo simples, suponha que desejamos apenas comparar um caractere com o próximo caractere na string. Uma abordagem ingênua pode fazer o seguinte:

func CompareChars(word string) {
for i, c := range word {
if i < len(word)-1 {
fmt
.Print(string(word[i+1]) == string(c), ",")
}
}
}
...
CompareChars("hello")
>>> false,false,true,false,

E com testes apenas para ASCII, funcionará perfeitamente. Agora, e se estivéssemos dizendo olá em chinês?

CompareChars("你好好好")
>>> false,false,false,false,

Opa. Claro, os caracteres nunca serão considerados iguais, porque estamos comparando com xE5o primeiro byte de .

Então, <s> 怎么 办 呢 </s> o que fazer? Felizmente, se você cavar fundo o suficiente, verá que o Go vem com o pacote. Não oferece muito, mas vamos usar isso para voltar ao nosso primeiro problema: encontrar o comprimento da string “Hello”:unicode/utf8

import (
"fmt"
"unicode/utf8"
)
...
fmt
.Println(utf8.RuneCountInString("Hello, 世界"))
>>> 9

Ótimo, essa é a contagem que esperávamos no início! Agora, que tal atualizar nossa CompareCharsfunção para que funcione com Unicode?

func CompareChars(word string) {
s
:= []byte(word)
for utf8.RuneCount(s) > 1 {
r
, size := utf8.DecodeRune(s)
s
= s[size:]
nextR
, size := utf8.DecodeRune(s)
fmt
.Print(r == nextR, ",")
}
}
...
CompareChars("hello")
>>> false,false,true,false,
CompareChars("你好好好")
>>> false,true,true,

Funcionou! や っ た!

Moral da história :
tenha muito cuidado ao trabalhar com Unicode no Go, especialmente ao fazer loop em strings. E o mais importante, sempre escreva testes que contenham strings Unicode e ASCII e use o pacote UTF-8 integrado quando apropriado.