Tudo começou a cerca de 1 mês, quando eu decidi experimentar a chamada linguagem Go de que tanto falavam. Diziam que ela tinha excelente performance, era moderna, era segura e era tudo isso e mais um pouco. Fiquei tentado então a experimentar essa linguagem ao menos uma vez. Mas, como sempre, na vida de um desenvolvedor as coisas quase nunca funcionam bonitinho e de primeira kkkk.
Eu, como alguns por aqui já sabem, gosto muito de C++ e a maior parte do meu desenvolvimento em projetos pessoais ocorrem nele. Uma coisa que eu acabei me acostumando ao longos desses anos desenvolvendo em C++ foi o fato de que nele os identificadores de tipos e identificadores de variáveis existem em espaços de nomes distintos um dos outros, mas esse não era o caso em Go. Nela, os identificadores existem todos no mesmo espaço de nome.
Mas espera ai — você talvez se pergunte — o que diabos é um identificador? E um espaço de nome?? 🤔🤔
De forma simplificada, um identificador é basicamente o nome que se dá a qualquer tipo, variável ou função. Por exemplo, se você declara uma variável do tipo 'string' com o nome 'senhaDoUsuario', tanto o nome do tipo, nesse caso 'string', quanto o nome da própria variável, 'senhaDoUsuario', são considerados identificadores. O espaço de nome, intuitivamente, pode ser entendido como regiões em que os nomes dos tipos e das variáveis ficam armazenadas (como se fosse um listão, sabe?).
// Exemplo em Typescript
let nomeDoUsuario: string = '...';
// ┬──────────── ┬─────
// │ ╰────▶ Identificador
// ╰───────────────────▶ Identificador
Em C++, mas também em outras linguagens, os identificadores que referem a tipos e os identificadores que a nomes de variáveis existem em espaços de nomes separados (duas listas distintas), de forma a não conflitarem entre si caso tenham o mesmo valor. Ou seja, você pode criar um tipo com o nome 'pessoa' e uma variável também chamada 'pessoa' ao mesmo tempo sem nenhum problema que o compilador entenderá, contextualmente, quem é o tipo e quem é a variável.
Entretanto em Go é um pouco diferente.
Como mencionado anteriormente, na linguagem Go todos os identificadores existem no mesmo espaço de nomes (em teoria de linguagens de programação, isso se chama flat namespaces), isto é, elas podem conflitar entre si. Mas não é exatamente conflitar no sentido de impedir que o programa seja compilado, mas no sentido em que se você declarar um tipo com o nome 'pessoa' e logo abaixo declarar uma variável com o nome 'pessoa' o tipo 'pessoa' deixará de ser referenciável, fazendo com que apenas a variável esteja visível para seu programa nas linhas seguintes à sua declaração. Isso, na programação, se chama shadowing.
O meu sofrimento resultou justamente desse fato.
Quando eu fui experimentar a linguagem pela primeira vez, eu queria fazer algo simples porém não trivial. Manipulação de arquivos de texto, para ser mais específico. O interessante do Go é que o tratamento de erros é feito pelos tipos de retorno, ou seja, todos os erros que possam acontecer são explicitamente retornados pela função como se fosse um valor qualquer (ao contrário de Java, onde exceções são lançadas). Isso resulta, então, num padrão onde você terá o valor de retorno real da função acompanhado do possível erro emitido durante sua execução.
// arquivo (simplificado): example.go
value, error := foo()
if error != nil {
fmt.Println(err)
}
fmt.Println(value)
Isso por si só é tranquilo, sendo até preferível se você vem de uma linguagem como C e não curte muito a ideia de exceções. O problema surge mesmo quando consideramos o fato mencionado anteriormente:
"todos os identificadores existem no mesmo espaço de nomes"
Imagine agora que você queira explicitar os tipos dos valores retornados pela função foo.
// arquivo (simplificado): example.go
var (
value int
error *error
)
value, error = foo()
if error != nil {
fmt.Println(error.message)
return
}
fmt.Println(value)
Tudo parece normal, certo? O código compila bonitinho, tudo certo, tudo uma maravilha. Mas ai é que se esconde o motivo que incentivou esse post.
Perceba que eu utilizei o mesmo nome tanto para a variável error quanto para o tipo error. Leve em consideração agora a seguinte situação:
// arquivo (simplificado): example.go
var (
value int
error *error
)
value, error = foo()
if error != nil {
fmt.Println(error.message)
return
}
fmt.Println(value)
var (
anotherValue int
anotherError error
)
anotherValue, anotherError = foo()
if anotherError != nil {
fmt.Println(error.message)
return
}
fmt.Println(anotherValue)
O que você acha que irá ocorrer aqui?
Bom, se você tentar compilar esse código, você irá receber a seguinte mensagem de erro de compilação:
./example.go:28:16: error is not a type
Que. Olha, eu esperava de tudo, menos isso.
Qual é o significado dessa mensagem? error é claramente um tipo, oras, eu até mesmo utilizei ele para declara uma variável anteriormente!
...
E foi esse detalhe minucioso que me fez perder algumas horas pesquisando se o problema era com a minha instalação do compilador de Go, meu sistema operacional ou seja lá quem ou o que tivesse causado isso.
A explicação para essa mensagem estranha está no fato de que em Go identificadores de variáveis e identificadores de tipos existem no mesmo espaço de nome, como já explicado anteriormente, logo, o que ocorreu no exemplo acima foi que o compilador substituiu a referência para o tipo error pela referência para a variável error, fazendo com que qualquer uso seguinte desse identificador no lugar de um tipo fosse considerado semânticamente inválido.
Dai que entra a minha parte nessa história.
Hoje, depois de muito tempo sem encostar nessa linguagem devido a frustração resultante desse ocorrido, me deparei com um post no Linkedin comentando sobre ela novamente. No momento em que eu me vi lendo a postagem, eu me recordei dessa experiência desagradável que tive em primeira mão com Go. Dai eu decidi fazer o seguinte: por que não abrir um issue no repositório da linguagem sugerindo uma possível melhoria nisso?
A ideia era clara, ao invés de retornar isso:
./example.go:28:16: error is not a type
como mensagem de erro, porque não retornar algo como isso:
./example.go:28:16: error refers to a variable, but is being used as type here.
./example.go:16:37: error was re-declared here as a variable (originally declared as a type at example.go:5:37).
no lugar? Seria muito melhor! evitaria que outras pessoas no futuro perdessem tempo tentando descobrir o significado por trás daquela mensagem misteriosa. Pelo menos era o que eu pensava.
Entretanto, quando eu entrei no repositório da linguagem, eu vi a quantidade imensa de issues abertos no projeto (mais de cinco mil) e confesso que deu uma desanimada. Com isso tudo de issue aberto, seria muito improvável que o meu, de um maluco aleatório que nunca contribuiu antes para essa linguagem, fosse sequer visto por alguém.
Mas apesar de tudo, eu ainda assim fui lá e abri.
E depois de alguns minutos ... não é que responderam!? Melhor ainda, eles aceitaram a minha sugestão! Fiquei bem feliz em ver que realmente levaram em consideração a crítica que eu fiz e aceitaram colocar no backlog do projeto como meta de melhoria para a próxima release oficial. Entãose você for, em algum momento distante, utilizar essa linguagem, e se deparar novamente com a mesma situação ... saiba que ela já foi bem pior!
E é isso! Talvez agora eu possa dizer (e colocar no currículo 👀) que de certa forma eu contribui para uma das linguagens mais famosas no mundo atualmente! Kkkkkk.
*Talvez seja esticar um pouco demais a noção de contribuição, entretanto, convenhamos, o que vale é a intenção! 😁
SOCIAL SHARE CARD GENERATOR