Introdução: Por que você pode precisar transformar bytes em fluxo?
\n\nImagine a situação: você escreveu um ótimo serviço em Go que recebe dados de uma API externa. A resposta chega como um array de bytes ([]byte). Tudo bem, mas o problema é que a próxima função no seu pipeline exige como entrada não um slice de bytes, mas uma interface io.Reader. O que fazer? Entrar em pânico? Criar uma solução do zero? Não! Basta lembrar do bytes.NewReader.
Esta questão é uma das mais populares no Stack Overflow (milhares de visualizações!). E não é à toa. Em Go, o conceito de io.Reader é a base para trabalhar com fluxos de dados: arquivos, conexões de rede, respostas HTTP e, claro, simplesmente bytes na memória. Se você é um desenvolvedor Go iniciante, mais cedo ou mais tarde vai se deparar com essa barreira. Vamos entender como contorná-la de forma elegante e eficiente.
Neste artigo, não vamos apenas dar o comando seco "use bytes.NewReader". Vamos analisar por que isso funciona, quais são as alternativas e mostrar exemplos reais do dia a dia. Vamos lá!
Teoria: O que é io.Reader e com o que se come?
\n\nAntes de converter, vamos lembrar o que é io.Reader. É uma interface definida na biblioteca padrão do Go:
type Reader interface {\n Read(p []byte) (n int, err error)\n}\n\nTudo é genialmente simples. Qualquer estrutura que tenha um método Read(p []byte) (int, error) automaticamente satisfaz a interface io.Reader. Isso permite escrever código universal para ler dados de qualquer fonte: arquivo, socket, string, slice de bytes.
Por que isso é legal? Porque você pode escrever uma única função que analisa JSON, XML ou simplesmente copia dados, sem se preocupar de onde eles vieram. Você só precisa de um io.Reader. Isso é uma abstração de fluxo.
Mas o que fazer se você já tem os dados na memória (por exemplo, []byte)? Você precisa de um objeto que "finja" ser um fluxo, mas que leia do seu slice. É aqui que entra o bytes.NewReader.
Método principal: bytes.NewReader
\n\nEste é o caminho mais simples e correto. A função bytes.NewReader recebe um []byte e retorna um ponteiro para a estrutura bytes.Reader, que implementa io.Reader (assim como io.ReadSeeker, io.WriterTo e outras interfaces úteis).
Exemplo 1: Uso básico
\n\npackage main\n\nimport (\n "bytes"\n "fmt"\n "io"\n "log"\n)\n\nfunc main() {\n // Nosso slice de bytes original\n data := []byte("Olá, mundo! Estes são bytes.")\n\n // Criando um Reader a partir do slice de bytes\n reader := bytes.NewReader(data)\n\n // Lendo dados do reader para um buffer\n buf := make([]byte, 10) // lendo de 10 em 10 bytes\n for {\n n, err := reader.Read(buf)\n if err != nil {\n if err == io.EOF {\n break // leitura finalizada\n }\n log.Fatal(err)\n }\n fmt.Printf("Lidos %d bytes: %s\\n", n, buf[:n])\n }\n // Output:\n // Lidos 10 bytes: Olá, mund\n // Lidos 10 bytes: o! Estes s\n // Lidos 6 bytes: o bytes.\n}\n\nObserve: bytes.Reader se comporta como um fluxo real. Quando os dados terminam, ele retorna io.EOF. Tudo honesto.
Exemplo 2: Uso com io.Copy
\n\nO cenário mais comum é copiar bytes para algum lugar (arquivo, resposta HTTP). Para isso, io.Copy é ideal:
package main\n\nimport (\n "bytes"\n "fmt"\n "io"\n "os"\n)\n\nfunc main() {\n data := []byte("Salvando estes dados em um arquivo.")\n reader := bytes.NewReader(data)\n\n // Criando um arquivo\n file, err := os.Create("output.txt")\n if err != nil {\n panic(err)\n }\n defer file.Close()\n\n // Copiando do reader para o arquivo\n