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 xE5
o 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 CompareChars
funçã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.