Em julho, @bradfitz e eu postamos um rascunho de design para arquivos incorporados . O documento tem links para um vídeo, código de protótipo e uma discussão do Reddit.
O feedback sobre esse design foi extremamente positivo.
Proponho adotar o projeto de projeto de arquivos embutidos para Go 1.16, com um acréscimo, sugerido na discussão, para simplificar o caso de acesso direto aos bytes em um único arquivo embutido.
Contanto que um arquivo importe "embed"
( import _ "embed"
se necessário), será permitido usar //go:embed
nomeando um único arquivo (sem padrões glob ou correspondência de diretório permitidos) para inicializar uma variável simples string
ou []byte
:
//go:embed gopher.png
var gopherPNG []byte
A importação é necessária para sinalizar o arquivo como contendo //go:embed
linhas e precisando de processamento. Goimports (e gopls etc) podem aprender esta regra e adicionar automaticamente a importação em qualquer arquivo com //go:embed
conforme necessário.
O design de arquivos incorporados depende do design de rascunho da interface do sistema de arquivos, que também propus adotar em # 41190.
Este problema é _somente_ sobre a adoção do design de arquivos incorporados , pressupondo que o design da interface do sistema de arquivos também seja adotado. Se esta proposta for aceita antes do design da interface do sistema de arquivos, nós simplesmente esperaremos pelo design da interface do sistema de arquivos antes de começar as mudanças.
Seria um erro ter uma diretiva //go:embed
sem importar embed
?
@jimmyfrasche Sim, quinto ao último marcador da lista em https://go.googlesource.com/proposal/+/master/design/draft-embed.md#go_embed -directives.
@rsc Talvez eu tenha perdido no rascunho, mas não vejo a capacidade de incorporar um único arquivo que você mencionou em seu comentário.
Além disso, você seria capaz de incorporar um único arquivo como uma string const também?
Obrigado por esta ótima proposta.
@pierrec Não está no documento de rascunho (a "uma adição" é o texto no comentário acima). As cadeias de const podem acabar desempenhando um papel na decisão se um tipo de programa verifica, o que significaria que todos os verificadores de tipo precisariam entender // go: embed'ed consts. Em contraste, se nos limitarmos aos vars, os verificadores de tipo não são muito sábios e podem ser deixados sozinhos. Parece que provavelmente deveríamos nos limitar a vars.
Há algum motivo específico para você querer um const em vez de um var? Usá-los deve ser quase o mesmo no que diz respeito à eficiência. (Referências a strings const acabam compilando a quantidade de referências a vars ocultos de qualquer maneira.)
Obrigada pelo esclarecimento. Eu tendo a incorporar ativos estáticos como strings const no momento, é por isso que perguntei. Eu também estou bem com vars!
Interessante, então eu poderia fazer algo como:
//go:embed version.txt
var Version string
E potencialmente até mesmo ter um comentário //go:generate
para gerar version.txt. Isso eliminaria um grande caso de uso para makefiles / ldflags.
É um erro se o arquivo não for encontrado? Em caso afirmativo, onde tecnicamente se considera que o erro ocorre? Tempo do link?
Podemos ter certeza de que go: embed executa depois de go: generate para que possamos fazer coisas como gerar versões facilmente, etc?
Podemos ter certeza de que go: embed executa depois de go: generate para que possamos fazer coisas como gerar versões facilmente, etc?
Do meu entendimento, go:generate
ocorrerá com go generate
enquanto go:embed
acontecerá com go build
.
@carlmjohnson Sim, é sempre um erro dizer //go:embed foo
onde foo não existe.
O erro ocorre ao compilar o arquivo de origem que contém essa linha.
(Se você remover foo depois de compilar aquele arquivo fonte, você ainda não conseguiria uma etapa de link - o comando go notaria que o pacote precisa ser reconstruído porque foo foi excluído.)
Acho que esta proposta não está completa sem dizer algo sobre ETag.
https://old.reddit.com/r/golang/comments/hv96ny/qa_goembed_draft_design/fzi0pok/
@ tv42 , sim, faremos a ETag funcionar. Não tenho certeza de qual é a forma disso, mas vamos.
(Também afirmado em https://github.com/golang/go/issues/35950#issuecomment-685845173.)
Duas Três coisas que notei ao trabalhar com mjibson/esc
:
go:embed
não precisa gerar arquivos go para incorporar como sistema de arquivos somente leitura, isso tiraria a dor de alterar os carimbos de data / hora nos arquivos go:generate
ed que desafiavam git porcelain
testes em CI- muito bommjibson/esc
eu posso fazer isso instruindo-o a usar o sistema de arquivos local (embora ele não pegue novos arquivos) e mude o comportamento usando tags de construção. Estou me perguntando o que isso pode caber na proposta.esc
necessário para ser capaz de remover (partes do) caminho de base de forma transparente para, por exemplo, exportar uma pasta de ativos como raiz da web.Consideração posterior : acho que o segundo ponto poderia ser remediado em conjunto com a proposta io/fs
, em que eu usaria o sistema de arquivos incorporado ou ao vivo para inclusão? Implementar a remoção de caminho como io/fs
middleware?
@andig Você já pode remover prefixos ao servir um sistema de arquivos por HTTP . Eu concordo que o recarregamento ao vivo pode ser feito por uma biblioteca de terceiros envolvendo um io/fs
.
Mais uma coisa: se bem entendi, embed irá considerar os arquivos localmente para o pacote e proíbe ..
. Meu projeto atual tem /assets
e /server/
onde o último contém o código do servidor e hoje hospeda os arquivos gerados. Com esta proposta, o embed precisaria ser movido para a pasta raiz, pois os ativos não seriam acessíveis a partir do servidor. Isso impõe restrições de acessibilidade diferentes das importações normais. Eu queria saber se isso é necessário por motivos de segurança ou se as incorporações locais do módulo devem ser geralmente permitidas.
Mais uma coisa: se bem entendi, embed irá considerar os arquivos localmente para o pacote e proíbe
..
. Meu projeto atual tem/assets
e/server/
onde o último contém o código do servidor e hoje hospeda os arquivos gerados. Com esta proposta, o embed precisaria ser movido para a pasta raiz, pois os ativos não seriam acessíveis a partir do servidor. Isso impõe restrições de acessibilidade diferentes das importações normais. Eu queria saber se isso é necessário por motivos de segurança ou se as incorporações locais do módulo devem ser geralmente permitidas.
Você pode criar um arquivo emed.go em seu diretório de ativos e disponibilizar os ativos como seu próprio pacote para o resto do seu programa.
Outro objetivo explícito é evitar uma mudança de idioma. Para nós, a incorporação de ativos estáticos parece ser um problema de ferramentas, não um problema de linguagem.
Concordou. Na minha opinião, adicionar açúcar sintático na linguagem para dar suporte a essa mudança de ferramenta é uma mudança de linguagem. Tenho certeza de que isso é óbvio para os outros, mas é efetivamente um comentário como código.
Eu sinto fortemente que magic / sugar diminui a simplicidade e legibilidade da linguagem; é muito fácil perder um comentário mágico que incorpora um arquivo. Embora uma resposta a isso possa facilmente ser "ok, então não use", essa mudança significa que um revisor ainda precisa estar atento para outros que usam esse recurso e deve se lembrar que os comentários sobre as declarações de variáveis podem quebrar compilações ou falhar em tempo de compilação.
Eu acredito que isso vai aumentar a confusão, diminuir a usabilidade da linguagem e irá resultar em binários opacos e grandes sem benefícios claros (em relação à última preocupação, isso levará até a um antipadrão de reconstrução de binários devido a alterações de arquivo simples ) Se go mod
fosse permitido para --withNonGoCodeAssets
, acredito que isso resolveria as necessidades da maioria dos desenvolvedores que não querem escrever pipelines de compilação mais complexos (presumo que a distribuição do usuário final é um subconjunto menor de o problema para os usuários).
@tristanfisher , entendo sua
Quanto ao inchaço, acho que veremos, mas não prevejo que os programas ficarão muito maiores do que já são. As pessoas já executam ferramentas que transformam arquivos arbitrários em código Go, fazem o check-in em seus repositórios e fazem com que o compilador os construa. O design remove alguma sobrecarga desse processo, mas não permite nada de novo. Talvez as pessoas abusem agora que é mais fácil de fazer, mas, no geral, não espero que isso seja um grande problema. (E se alguma dependência incorpora algo tão grande que incha seus binários, você sempre pode optar por não usar essa dependência.)
Quanto às reconstruções devido a alterações de arquivo simples, os únicos arquivos que podem acionar as reconstruções são aqueles em seu próprio módulo de nível superior, uma vez que as dependências são imutáveis. Se você descobriu que as reconstruções acontecem com mais frequência do que gostaria, a única explicação é (1) você está incorporando arquivos e (2) está modificando esses arquivos. Você teria o controle total de fazer algo sobre qualquer uma das causas. (Seria completamente diferente se a escolha de uma dependência sobre o que usar estivesse de alguma forma forçando reconstruções extras ou outras despesas para você. Mas esse não é o caso aqui.)
@rsc Concordo que podemos discordar e agradeço sua resposta. Minha impressão é que se for incluído por padrão no conjunto de ferramentas padrão e comentários podem levar a uma inicialização implícita de uma variável, então é uma mudança de idioma. Fora desse debate, acho que meu sentimento desagradável é em torno de mais diretivas como comentários "mágicos" que precisam ser memorizados por leitores de código (humanos). Isso pode levar à conclusão absurda de adicionar novos recursos por meio de comentários em bloco que são tratados no momento da construção.
Dito isso, se isso for adicionado ao ecossistema, ficarei grato pela necessidade de importar embed
- isso é melhor do que nada como um "ei, atenção" ao auditar o código. Eu acho que go mod
permitindo não .go resolveria a maioria dos casos de uso (eu imagino que a maioria das pessoas irá globar arquivos para servidores da web) e também viveria inteiramente em ferramentas.
Acho que seu ponto sobre o vinculador é bom. Isso também ajuda a explicar meus sentimentos sobre isso: se o próprio usuário final (por exemplo, não alguém que simplesmente importa um pacote) está tomando a decisão, não há como ser surpreendido por bolhas de não-código chegando. Minhas preocupações nascem da revisão / formação de pares no trabalho de outras pessoas e nas responsabilidades "tecnológicas", e é por isso que senti a necessidade de responder.
Acho que "vamos ter que ver" resume bem (sou mais cínico sobre inchaço / uso indevido).
Vou ler o rascunho do design esta noite, até agora parece bom da perspectiva do TinyGo.
Eu só queria esclarecer uma coisa:
Por outro lado, projetos como TinyGo e sistemas de destino U-root com mais RAM do que disco ou flash. Para esses projetos, compactar ativos e usar a descompressão incremental em tempo de execução pode fornecer economias significativas.
Não sei sobre o U-root, mas para TinyGo os principais alvos são microcontroladores que normalmente têm muito mais flash do que RAM (geralmente um fator de 8 ou 16). Uma rápida olhada no projeto de rascunho parece sugerir que a ideia é manter os arquivos em memória somente leitura, o que funcionaria bem para esses alvos: os arquivos incorporados podem ser lidos diretamente do flash. Provavelmente, não seria desejável que os destinos do TinyGo descompactassem arquivos em tempo de execução.
A proposta io / fs da qual isso depende parece estar bloqueada em problemas Readdir / FileInfo, em discussão em # 41188 e anteriormente # 40352.
Elaborei uma API para substituí-los em https://github.com/golang/go/issues/41188#issuecomment -686283661
@andig
Uma coisa que não encontrei na proposta, mas que precisaria, é a capacidade de recarregar os arquivos incorporados durante os ciclos de desenvolvimento.
embed.Files implementa fs.FS, portanto, tudo que você precisa fazer é usar dev vs! dev build tag para alternar uma variável entre embed.Files e o FS real.
Eu preenchi o número 41265. Ele oferece uma nova API ReadDir () para io / fs.
Tenho preocupações semelhantes às de @tristanfisher . Go tem usado comentários mágicos como diretivas de compilador há muito tempo (desde o início?), Mas eles são feitos para casos extremos e é raro que apareçam no código. Dada a popularidade da incorporação de conteúdo estático em binários Go, //go:embed
provavelmente será mais comum. Talvez seja hora de considerar uma sintaxe diferente para as diretivas do compilador?
Apenas um lembrete de que alterar a sintaxe Go tem um custo muito alto. Praticamente todas as ferramentas Go que existem precisariam ser atualizadas e / ou consertadas para suportar a nova sintaxe, por exemplo.
Não os considero comentários mágicos. As linhas que começam com //go:
são diretivas e podem ser definidas como tal nas especificações. Não há muita diferença semântica entre //go:embed
, @embed
, [[embed]]
ou qualquer outro número de variações de sintaxe, exceto que o prefixo //go:
já é tratado como não -code por ferramentas Go. (meu editor destaca essas linhas de maneira diferente, por exemplo)
@mvdan Se esta proposta acontecer, a sintaxe Go mudou. Apenas mudou de uma forma que não quebra o ferramental existente. Talvez isso pareça pedante.
@iand não sou muito exigente quanto à sintaxe específica das diretivas do compilador. Só acho que precisa ser formalizado em algum momento e as regras especificadas.
Acho que essa proposta é uma boa ideia. Isso resolve um problema comum. Minha preocupação é que o custo de sua adoção seja um pouco mais explícito.
@jonbodner Compartilho suas preocupações sobre comentários mágicos. Mas, até certo ponto, as regras são especificadas por # 37974.
@networkimprov , esta não é a proposta io / fs. Por favor, pare de comentar sobre ReadDir aqui.
@jonbodner
Não sou muito exigente quanto à sintaxe específica das diretivas do compilador. Só acho que precisa ser formalizado em algum momento e as regras especificadas.
Gostaria apenas de salientar que tomamos a decisão de usar //go:
para marcar as diretivas do conjunto de ferramentas Go quando
adicionamos (uso limitado) a anotação
Adicionamos //go:noescape
para autores de montagens em 2013.
Adicionamos //go:generate
em 2014.
Provavelmente estamos
Há outros; esses são apenas os destaques.
Você pode pensar em //go:
significando #pragma
de C se isso ajudar.
Neste ponto, a convenção está muito bem estabelecida.
Escolhemos essa sintaxe em 2012 porque
(1) obviamente não é um comentário para uma pessoa;
(2) ferramentas que não sabem sobre os comentários irão ignorá-los porque são comentários; e
(3) generaliza para outras ferramentas (s / go / yourtool /).
E, como Ian disse, # 37974 formalizou a sintaxe de comentário generalizada exata, para valer a pena.
Com base na discussão acima, parece uma aceitação provável .
(Novamente, supondo, mas separado da proposta FS.)
Nenhuma mudança no consenso, então aceita.
Estou ansioso para colocar minhas mãos no embed - isso já pode ser testado no master ou há planos de enviá-lo como experimento durante o ciclo de 1,15?
@andig , Go 1.15 já foi lançado. Ainda espero que esteja no Go 1.16 e no ramo de desenvolvimento este mês.
@rsc 1.16 disponível?
@septs , não, ainda estamos trabalhando no Go 1.16. O congelamento do código ocorre em 31 de outubro, com lançamento previsto para 1º de fevereiro.
versão 2021Q1 ou 2021Q2 mais rápida?
@septs , pare de fazer perguntas sobre os lançamentos Go neste tópico. Mais de vinte pessoas seguem e são notificadas. Consulte https://golang.org/wiki/Questions e https://github.com/golang/go/wiki/Go-Release-Cycle.
Alterar https://golang.org/cl/243941 menciona este problema: go/build: recognize and report //go:embed lines
Alterar https://golang.org/cl/243940 menciona este problema: go/build: refactor per-file info & reader
Alterar https://golang.org/cl/243942 menciona este problema: embed: implement Files
Alterar https://golang.org/cl/243944 menciona este problema: cmd/compile: add //go:embed support
Mudança https://golang.org/cl/243945 menciona este problema: cmd/go: add //go:embed support
Um detalhe que surgiu na revisão da implementação é que "Arquivos" como um substantivo singular é muito estranho ("Um arquivo contém ...").
A escolha de embed.Files para o nome é anterior à proposta io / fs e também ao suporte para string e [] byte.
Dados esses dois desenvolvimentos, uma maneira aparentemente sensata de resolver o problema de "A guarda de arquivos" é chamá-lo de FS em vez de Arquivos.
Então, as três maneiras de incorporar e imprimir dados são:
import "embed"
//go:embed hello.txt
var s string
print(s)
//go:embed hello.txt
var b []byte
print(string(b))
//go:embed hello.txt
var f embed.FS
data, _ := f.ReadFile("hello.txt")
print(string(data))
Isso parece mais claro sobre o que você obtém: uma string, um [] byte ou um FS.
Ou seja, a maior parte da funcionalidade do embed.F * vem de ser um fs.FS, e chamá-lo de FS torna isso mais claro do que chamá-lo de Arquivos.
Fiz essa mudança no meu último rascunho do pacote de implementação de CL incorporado, mas gostaria de voltar aqui e ver se há alguma objeção à mudança de nome.
(Uma mudança mais radical seria var f fs.FS
vez de var f embed.FS
, mas isso impediria qualquer método em f
diferente de Open. Por exemplo, acima, tendo ReadFile
é conveniente e não seria possível. E, em geral, aprendemos que usar um tipo concreto para algo que pode querer adicionar métodos posteriormente é uma boa proteção para o futuro em comparação com o uso direto de um tipo de interface.)
Acho que renomear é uma boa mudança.
Em relação à mudança mais radical:
fs.FS
, ainda precisaríamos do pacote embed
? Acho que o valor dinâmico ainda deve ter algum tipo, que mora em algum pacote? Acho a ideia de não ter que adicionar um pacote a mais.f.ReadFile(…)
não é significativamente fs.ReadFile(f, …)
.embed.FS
embed.FS
usa receptores de ponteiro ou receptores de valor? IMO ter que passar em torno de &f
é estranho, usar receptores de valor é um pouco inesperado. No entanto, também podemos permitir var f *embed.FS
. Se a variável tiver um tipo de interface, essa questão vai embora.No geral, eu ainda concordo que usar o concreto embed.FS
é melhor - se nada mais, então para fins de documentação.
Agora que você mencionou, não acho que entendi bem: podemos incorporar diretórios, certo?
Sim como um embed.FS que implementa fs.FS.
@Merovius , embed.FS usa receptores de valor. embed.FS é uma estrutura de uma palavra contendo um único ponteiro, então não há sobrecarga real para fazer isso, mas significa que você pode atribuí-los e usá-los sem se preocupar com * se \ & s em todos os lugares.
@ chabad360 , sim, você pode incorporar diretórios.
E quanto aos links simbólicos?
@ burik666 , consulte https://golang.org/s/draft-embed-design para obter detalhes, mas não, você não pode incorporar um link simbólico.
será possível incorporar e usar bibliotecas C dinâmicas? em caso afirmativo, como usaríamos o caminho de incorporação em #cgo
cabeçalhos, como: #cgo LDFLAGS: -L./lib -lmylib -Wl,-rpath=./lib
?
@benitogf Presumo que a única maneira real de fazer isso seria gravá-los no disco e usar dlopen
. Não consigo imaginar como você poderia dizer ao carregador dinâmico como localizar arquivos incorporados. Além disso, se você deseja agrupar em código C, a ligação estática pareceria mais apropriada de qualquer maneira, não?
@benitogf Embedding permite que você coloque um arquivo do disco em um byte [] em seu programa convenientemente, nada mais.
Se você tiver uma maneira de usar uma biblioteca C dinâmica que já está em seu programa na forma de um byte [], a incorporação o ajudará a obter um arquivo de disco lá. Caso contrário, não.
a ligação estática pareceria mais apropriada de qualquer maneira, não?
@Merovius concordou, mas tenho vários casos de uso trabalhando com fornecedores que fornecem apenas bibliotecas dinâmicas
Se você tem uma maneira de usar uma biblioteca C dinâmica que já está em seu programa na forma de um byte []
a única maneira real de fazer isso seria gravá-los no disco e usar dlopen
escrever a biblioteca embutida do [] byte para o sistema de arquivos e usar dlopen parece ok, embora ter os arquivos embutidos opcionalmente "despejados" no sistema de arquivos na construção / execução para que o cabeçalho #cgo
possa acessá-los seria útil, não apenas para cgo imho
Tentando fazer isso agora; uma verruga com a diretiva go:embed
é que se eu incorporar build/*
, os nomes de arquivo ainda terão o prefixo build/
. Se eu quiser servir esse diretório por meio de http.FS
, não há uma maneira fácil de _adicionar_ o prefixo que é necessário para acessá-los, se necessário (sem escrever um invólucro, que então atinge o problema de precisar listar todos os métodos potenciais que o FS pode ter ...).
por exemplo:
//go:embed build/*
var buildDir embed.FS
// Serve some SPA build dir as the app; oops, needs to be build/index.html
http.Handle("/", http.FileServer(http.FS(buildDir)))
// or
//go:embed static/*
var staticDir embed.FS
// Oops; needs to have a static prefix.
http.Handle("/static/*, http.StripPrefix("/static", http.FileServer(http.FS(staticDir))))
// Could be this, but only because the prefix happens to match:
http.Handle("/static/*, http.FileServer(http.FS(staticDir)))
Eu sei que a intenção é escrever go:embed foo/* bar/* baz.ext
e obter todos esses arquivos, mas acho que será muito comum simplesmente incorporar um diretório e servi-lo como ativos estáticos por meio do pacote http. Espero que isso seja um problema quando as pessoas mudam de coisas como http.Dir("static")
ou pkger.Dir("/internal/web/static")
onde o prefixo já foi manipulado, para o novo embed.FS
.
Não tenho certeza de como arquivar isso, pois é uma espécie de interação com embed
, io/fs
e net/http
.
@zikaeroh Escrever um http.Handler
também funcionaria, certo? Esse é apenas um método e ainda existe http.HandlerFunc
. Talvez a biblioteca padrão possa até mesmo fornecer uma para espelhar http.StripPrefix
(algo como http.AddPrefix
ou http.ReplacePrefix
).
Potencialmente, embora pareça um pouco estranho modificar a solicitação HTTP para contornar a implementação do FS (ao contrário de um generalizado "dê-me um FS que é um subdiretório de outro FS", que não é simples com métodos opcionais). Não seria a coisa mais eficiente, retirar e depois adicionar outro prefixo novamente (dados http.Request
cópias), mas vou tentar mais tarde. É pelo menos _diferente_ do que o esquema atual de coisas em que você tem que trabalhar com o pedido, eu suponho.
Eu tenho alguns outros lugares em que uso dados estáticos, não por meio do pacote http, para os quais terei que encontrar uma correção semelhante.
Se eu tiver que dar uma olhada em como isso está sendo implementado, onde posso ver. Uma filial onde está sendo implementado?
Foi sugerido antes incorporar os arquivos no local, ou seja, fazer isso no diretório de construção e depois importá-lo. Isso eliminaria o prefixo de construção. Em seguida, use o manipulador para adicionar o prefixo necessário. Não tenho certeza de como excluir o arquivo go que está fazendo a incorporação da própria incorporação. Consulte https://github.com/golang/go/issues/41191#issuecomment -686621090
Foi sugerido antes incorporar os arquivos no local, ou seja, fazer isso no diretório de construção e depois importá-lo. Isso eliminaria o prefixo de construção. Em seguida, use o manipulador para adicionar o prefixo necessário. Não tenho certeza de como excluir o arquivo go que está fazendo a incorporação da própria incorporação. Veja # 41191 (comentário)
Infelizmente, isso não é bom para diretórios produzidos por outras ferramentas, por exemplo, a saída de, digamos, uma construção de webpack ou CRA (onde geralmente são limpos de antemão, e não registrados). Prefiro hackear os nomes dos arquivos.
Foi sugerido antes incorporar os arquivos no local, ou seja, fazer isso no diretório de construção e depois importá-lo. Isso eliminaria o prefixo de construção. Em seguida, use o manipulador para adicionar o prefixo necessário. Não tenho certeza de como excluir o arquivo go que está fazendo a incorporação da própria incorporação. Veja # 41191 (comentário)
Infelizmente, isso não é bom para diretórios produzidos por outras ferramentas, por exemplo, a saída de, digamos, uma construção de webpack ou CRA (onde geralmente são limpos de antemão, e não registrados). Prefiro hackear os nomes dos arquivos.
Se você usa um sistema tão grande de plug-ins como o webpack, é fácil apenas instalar outro plug-in do webpack para gerar o embed.go junto com os próprios ativos. Se você estiver usando algo mais simples com um makefile ou script de shell, também é fácil gerar o arquivo .go a partir daí.
@zikaeroh
em oposição a um generalizado "dê-me um FS que é um subdiretório de outro FS", que não é simples com métodos opcionais
Deve ser simples. Lidar com o problema da embalagem fazia parte do processo de design. Em particular, o wrapper deve implementar todos os métodos opcionais, apenas chamando a função auxiliar apropriada em fs
. Se isso não funcionar, é preocupante e seria ótimo obter alguns detalhes.
Além disso, IMO, uma implementação de tal invólucro (que retira um prefixo) deve ser fornecida por io/fs
(análogo a io.LimitWriter
, etc). Eu diria que o único motivo pelo qual ainda não aconteceu é o tempo.
@andig O problema em fazer isso é que o arquivo Go contendo a diretiva embed e a variável também fica visível no FS (e seria servido por HTTP ou poderia ser exposto de outra maneira).
O problema em fazer isso é que o arquivo Go contendo a diretiva embed e a variável também fica visível no FS (e seria servido por HTTP ou poderia ser exposto de outra maneira).
Uma maneira de remediar isso seria adicionar a capacidade de excluir arquivos / pastas específicos da incorporação ( @rsc ?)
Uma maneira de remediar isso seria adicionar a capacidade de excluir arquivos / pastas específicos da incorporação ( @rsc ?)
A proposta foi aceita há mais de um mês e já está implementada; Não acho que grandes mudanças de design, como ser capaz de excluir caminhos, sejam razoáveis neste momento. Se você tiver um problema com o design implementado que não consiga contornar, sugiro preencher um relatório de bug separado com detalhes, que pode então ser rastreado antes do lançamento final 1.16.
@Merovius
Deve ser simples. Lidar com o problema da embalagem fazia parte do processo de design. Em particular, o wrapper deve implementar todos os métodos opcionais, apenas chamando a função auxiliar apropriada em fs. Se isso não funcionar, é preocupante e seria ótimo obter alguns detalhes.
Como funcionaria a remoção do prefixo para Glob
?
@icholy presumo algo como
func (f *stripFS) Glob(pattern string) (matches []string, err error) {
matches, err = fs.Glob(f.wrapped, path.Join(f.prefix, pattern))
for i, m := range matches {
matches[i] = strings.TrimPrefix(m, f.prefix+"/")
}
return matches, err
}
Talvez com alguns cuidados adicionais.
Brincando com isso no gotip, noto que incluirá arquivos .DS_Store. Isso é praticamente inevitável, eu acho, mas me preocupo que a inclusão de arquivos de ponto levará à inclusão acidental de arquivos. Talvez os documentos devam ter um forte aviso sobre isso?
Meu shell não inclui arquivos de ponto em *
, então se eu quiser incluí-los, tenho que usar * .*
. Esta pode ser uma forma (talvez igualmente surpreendente) de fornecer um nível de controle.
Não tenho certeza do que pensar - IMO, arquivos de ponto realmente não deveriam ser tratados de forma diferente pelo padrão, mas OTOH, o exemplo .DS_Store
parece algo que realmente deveria ser tratado.
Por que não fazer apenas git clean -dffx && go build
? Se os arquivos DS_Store estiverem em git, eles serão incluídos na construção. Se não forem, não serão. Isso também funcionará bem com o gitignore.
Você deveria estar construindo com um checkout VCS limpo, de qualquer maneira. Se você adicionar arquivos temporários aleatórios, eles podem chegar à compilação final e você não pode saber com quais arquivos as pessoas ficarão. Podemos querer documentar isso.
@mvdan Eu concordo em princípio, mas na prática,
Re: incorporando http.FileServers
Se você usa um sistema tão grande de plug-ins como o webpack, é fácil apenas instalar outro plug-in do webpack para gerar o embed.go junto com os próprios ativos.
Isso é verdade, mas é muito complicado. O objetivo do embed.FS é reduzir a necessidade de soluções alternativas malucas de Makefile. Acho que a solução simples é ter fs.WithPrefix(string) fs.FS
que bloqueia um FS em um subdiretório. Achei que houvesse alguma discussão sobre isso na proposta, mas não consigo encontrar agora. Talvez tenha sido apenas debatido no Reddit ou algo assim.
Uma maneira de remediar isso seria adicionar a capacidade de excluir arquivos / pastas específicos da incorporação ( @rsc ?)
Pode ser algo como
//go:embed static
//go:embed-exclude .*
var staticFiles embed.FS
A diretiva embed-exclude poderia apenas fazer um filtro global nos arquivos aceitos e remover quaisquer correspondências ...
Se quisermos evitar adicionar mais a esta proposta, também pode ser uma regra lint que verifica sistemas de arquivos embutidos para dotfiles potencialmente inesperados e avisa para que você possa corrigir sua construção para removê-los.
Ou exclua arquivos de ponto por padrão, a menos que sejam especificamente mencionados adicionando. * Ou semelhante.
Isso ainda não resolveria expor um arquivo assets.go. Quanto à proposta já implementada, vale ressaltar que a questão sobre a geração do ativo foi levantada durante a fase de discussão. Provavelmente não é perigoso ter um (caso contrário vazio, exceto para a diretiva embed) assets.go exposto, mas seria mais limpo não tê-lo. Como de costume, existem todos os tipos de soluções alternativas que podem ser aplicadas.
Provavelmente não é perigoso ter um (caso contrário vazio, exceto para a diretiva embed) assets.go exposto, mas seria mais limpo não tê-lo.
Eu concordo que é muito improvável que isso seja um problema, mas eu odiaria ver qualquer código-fonte fechado vazado acidentalmente devido a uma configuração incorreta, se pudermos facilitar a configuração correta das coisas.
Não quero ver um vazamento secreto de alguém que não percebeu que seu arquivo .env foi incorporado por engano.
Se alguém usar //go:embed static/*
e houver static/.env
ou static/.super-secret
, você não diria que o usuário realmente quis incluir esses arquivos? Caso contrário, por que eles estariam no diretório estático?
Eu entendo que isso depende do que o usuário espera e o que *
significa na maioria dos contextos, mas eu pessoalmente acho que https://golang.org/pkg/path/filepath/#Glob semântica é nosso único bom opção. É o mais simples e com o qual a maioria dos usuários de Go estará acostumada no contexto de desenvolvimento de Go.
Acho que alertar contra os perigos de incorporar *
é uma boa ideia em qualquer caso, porque em uma quantidade significativa de casos, pode-se reduzir a chance de erro usando globs mais específicos como *.png
.
Além disso, eu ainda encorajo você a registrar um problema separado que pode ser rastreado em relação à versão 1.16, escrito do ponto de vista de um relatório de bug. Essa proposta foi aceita e implementada, então imagino que será encerrada muito em breve. Por exemplo, você poderia formular o relatório de bug da seguinte forma: o suporte a arquivos incorporados facilmente leva à inclusão de arquivos indesejados (e dá alguns exemplos).
Se alguém usar // go: embed static / * e houver um static / .env ou static / .super-secret, você não diria que o usuário realmente pretendia incluir esses arquivos? Caso contrário, por que eles estariam no diretório estático?
Há uma grande quantidade de casos esquivos, por exemplo, você abriu uma sessão de edição com o vim, mas não a fechou, e ele criou o arquivo .*.swp
com alguns conteúdos que você gostaria que ninguém visse.
Movendo a discussão para # 42321.
Isso seria muito apreciado pela equipe Prisma para nosso cliente Database Go , já que usamos um mecanismo de consulta em tempo de execução que é escrito em ferrugem e precisa estar no binário construído go de alguma forma.
A maneira como estamos fazendo isso atualmente é empacotar o binário em um arquivo .go em tempo de go: generate, mas o tamanho do arquivo é muito maior do que quando é um arquivo .gz binário.
Incorporações nativas tornariam isso muito melhor, de modo que pudéssemos incorporar diretamente um arquivo .gz no binário go final.
Se alguém usar
//go:embed static/*
e houverstatic/.env
oustatic/.super-secret
, você não diria que o usuário realmente quis incluir esses arquivos?
Não, eu não faria.
$ mkdir z
$ touch z/.secret z/intended
$ ls z/*
z/intended
$ ls z
intended
Veja meu comentário posterior em https://github.com/golang/go/issues/42328#issuecomment -720169922.
Eu adoro a ideia de fazer arquivos / modelos estáticos incorporáveis, o que definitivamente pode aumentar muito a produtividade do desenvolvedor.
Mas devemos inovar outra tag (por exemplo, @
ou qualquer outra) além de reutilizar este //
, que deveriam ser comentários?
A partir de agora, o //
foi usado em excesso, eu acho, pense no seguinte:
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:webhook:verbs=create;update,path=/validate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=false,failurePolicy=fail,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,versions=v1,name=vcronjob.kb.io
// +optional
...
Mas devemos inovar outra tag (por exemplo,
@
ou qualquer outra) além de reutilizar este//
, que deveriam ser comentários?
Essa é uma discussão separada e, embora seja bom que as diretivas não sejam comentários, muito do código go1 compat já depende disso, então é provável que não vá mudar.
Mas devemos inovar outra tag (por exemplo,
@
ou qualquer outra) além de reutilizar este//
, que deveriam ser comentários?
Tentei encontrá-lo e não consegui, mas me lembro de haver uma decisão de padronizar em //go:<word>
para esses tipos de diretivas.
Não parece uma boa ideia mudar a sintaxe ao redor, dada a decisão de convergir expressamente para eles.
(Além disso, é claro, eles são comentários especificamente para que o compilador os ignore - essas diretivas são específicas para a ferramenta go, portanto, não devem vazar para a linguagem adequada)
Eu vi uma menção sobre o problema de io/fs
que embed.FS
suporta ETag: https://github.com/golang/go/issues/41190#issuecomment -737702433
Tentei executar um teste para ele, mas não estou vendo um conjunto de cabeçalhos de resposta ETag
. Talvez eu esteja entendendo mal o uso. Devo esperar ver um aqui? https://play.golang.org/p/Wq5xU5blLUe
Eu acho que não. http.ServeContent
(usado por http.FileServer
) inspeciona o cabeçalho ETag, mas não o define, AIUI.
Em um comentário acima, Russ disse que a ETag será feita para funcionar . A dificuldade é como fazer embed.FS
comunicar a http.FileServer
as informações necessárias para definir ETag
ou outros cabeçalhos de cache. Provavelmente, deve haver um problema de rastreamento separado para isso.
Pessoalmente, eu diria que embed.FS
deve usar o tempo do último commit do módulo relevante como ModTime
. Isso corresponderia aproximadamente a debug.BuildInfo , portanto, não afetaria a reprodutibilidade. Embora eu não tenha certeza de como isso é definido para commits que não correspondem a um lançamento marcado e ainda deixaria a questão de como defini-lo para compilações de árvores de trabalho sujas.
Mas acredito que @rsc tenha uma boa solução em mente :)
Não tenho certeza se entendi o comentário sobre "tempo de confirmação"; se a fonte do módulo for um arquivo zip, não há "confirmação". Não vejo nenhum momento mencionado em debug.BuildInfo ou debug.Module .
Mais importante, eu diria que qualquer mecanismo baseado em carimbo de data / hora é estritamente inferior a uma etag adequada (baseada em hash de conteúdo).
@ tv42 Cada versão do módulo é a) uma versão semântica derivada de uma tag (que aponta para um commit) ou b) uma pseudo-versão contendo um hash de commit. Eu penso? Pelo menos no git. Posso estar entendendo mal alguma coisa.
Mais importante, eu diria que qualquer mecanismo baseado em carimbo de data / hora é estritamente inferior a uma etag adequada (baseada em hash de conteúdo).
Eu não tenho tanta certeza. Ele precisa de algum canal lateral para comunicar o hash ou o servidor precisa calcular o hash do arquivo quando solicitado (o que parece muito caro). Afinal, net/http
não sabe, a priori, se o conteúdo de fs.FS
pode mudar. O resultado final de uma ETag baseada em hash pode justificar o custo de adicionar tal canal lateral (como uma interface opcional), mas não parece obviamente estritamente melhor.
Além disso, eu diria que suportar pelo menos também uma abordagem baseada no tempo significa que você pode trabalhar com mais clientes. Não tenho nenhum dado para apoiar isso, porém (ou seja, não sei se e quantos clientes existem que podem suportar If-Modified-Since
mas não ETag
e vice-versa).
Mas, realmente, não me importo muito com a abordagem escolhida. Queria apenas mencionar a opção de usar o momento em que uma versão do módulo foi marcada.
Os módulos
$ unzip -v ~/go/pkg/mod/cache/download/golang.org/x/crypto/@v/v0.0.0-20200510223506-06a226fb4e37.zip|head
Archive: /home/tv/go/pkg/mod/cache/download/golang.org/x/crypto/@v/v0.0.0-20200510223506-06a226fb4e37.zip
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
345 Defl:N 233 33% 1980-00-00 00:00 237856c8 golang.org/x/[email protected]/.gitattributes
Parece haver um carimbo de data / hora no arquivo *.info
o acompanha, mas não tenho ideia se ele é confiável ou está disponível para as ferramentas.
$ cat ~/go/pkg/mod/cache/download/golang.org/x/crypto/@v/v0.0.0-20200510223506-06a226fb4e37.info; echo
{"Version":"v0.0.0-20200510223506-06a226fb4e37","Time":"2020-05-10T22:35:06Z"}
Mesmo assim, qual carimbo de data / hora deve ser: usar incorporar no módulo principal (aquele em que você executa go build
)?
Pessoalmente, no contexto de arquivos estáticos que são imutavelmente incorporados no binário, o hash parece simplesmente superior aos carimbos de data / hora em todos os aspectos (exceto o suporte legado de antes de ETag existir, pré-HTTP / 1.1, anos 90), e não tem nenhuma fonte possível de confusão em sistemas distribuídos e compilações simultâneas. Duvido seriamente que existam clientes que não entendem mais a ETag, e eles certamente não farão a transição HTTP / 2.
Um canal lateral para comunicar o hash parece a coisa certa para mim; uma interface FS opcional para retornar um hash do arquivo. (E talvez outro para solicitar um algoritmo de hash específico, se acontecer de ele ter ?. Alguns casos de uso de serviço realmente precisam especificamente de sha256 / sha1 / md5, não apenas de "algum hash"). Um FS que não tem uma resposta barata o suficiente pode optar por não implementá-la.
(Embora os hashes modernos sejam gigabytes / segundo / núcleo mesmo quando criptograficamente seguros, dezenas de gigabytes / segundo para os menos seguros e fáceis de armazenar em cache com base na chamada de estatísticas. Basta suportar ETag em todos os lugares, pessoal.)
Estive pensando sobre esta proposta hoje. Estou feliz em ver essa funcionalidade incluída no conjunto de ferramentas Go e agradeço todo o pensamento e esforço investidos na proposta até o momento. Obrigado por propor isso e levar isso adiante.
Eu tenho duas perguntas e, em seguida, uma sugestão (reconhecendo que o período de discussão da proposta já passou e já está no congelamento da versão):
Percebo que o código atual espera que as variáveis sejam declaradas com a precisão ~ embed.FS
~, string
ou []byte
. Em particular, eles não são permitidos: []uint8
, ~ FS
após a importação de pontos de "embed" ~, ou quaisquer outros tipos idênticos construídos usando apelidos de tipo. (Editar: esqueci como as importações de pontos funcionam no gc e li incorretamente o código para detectar embed.FS
.)
Isso é intencional ou um bug?
[]byte
.Não vejo qualquer menção de qual é a semântica de identidade esperada de []byte
-variáveis tipadas. Em particular, para variáveis com escopo de função. Isso não é importante para variáveis do tipo string
- e embed.FS
, porque elas fazem referência a dados imutáveis que podem ser desduplicados com segurança. Mas é importante saber a semântica pretendida para variáveis do tipo []byte
.
Com a implementação atual, o programa de teste abaixo imprime false true
(quando foo.txt
não está vazio). Isso é pretendido / garantido?
package main
//go:embed foo.txt
var a []byte
//go:embed foo.txt
var b []byte
func f() *byte {
//go:embed foo.txt
var x []byte
return &x[0]
}
func main() {
println(&a[0] == &b[0], f() == f())
}
Acho que a semântica mais parecida com Go para variáveis //go:embed
seria a de literais compostos: que cada execução produz uma nova cópia.
Se não houver consenso sobre a semântica adequada para isso, podemos sempre trabalhar nisso e fazer com que o escopo da função seja []byte
-typed incorpora um erro para Go 1.16: os usuários ainda podem declarar uma variável de nível de pacote se eles querem a semântica atual (uma fatia de byte por declaração de origem), ou use uma variável do tipo string
e converta para []byte
se eles quiserem uma semântica literal composta. Poderíamos então revisitar mais tarde qual comportamento os usuários se beneficiariam mais.
//go:
diretivasNão recomendo adicionar diretivas //go:
para usuários finais que afetam a semântica do programa, e não acho convincentes os argumentos dados para favorecer //go:embed
relação à sintaxe Go normal. Eu respeitosamente encorajo a reconsiderar essa decisão antes do lançamento do Go 1.16. (Mais uma vez, agradeço o quão atrasado é este pedido.)
Vou começar apontando que tenho um CL de prova de conceito funcionando em CL 276835 , que altera a API de incorporação de:
//go:embed foo.txt bar.txt
var x embed.FS
para
var x = embed.Files("foo.txt", "bar.txt")
Da mesma forma, as funções embed.Bytes
e embed.String
estão disponíveis para incorporar um único arquivo e obtê-lo como um []byte
ou string
.
Da mesma forma, uma variável embed.Files pode ser global ou local, dependendo do que for mais conveniente no contexto.
Ter embed.Files
, etc. permite também usá-los em contextos de expressão, o que pode ser ainda mais conveniente.
É um erro usar // go: embed em um arquivo de origem que não importa "embed" (a única maneira de violar essa regra envolve truques de apelido de tipo).
Com embed.Files
, etc., isso é um erro em virtude das funções que estão sendo exportadas pelo pacote embed.
Goimports (e gopls etc) podem aprender esta regra e adicionar automaticamente a importação em qualquer arquivo com // go: embed conforme necessário.
Nenhuma lógica especial de goimports ou gopls é necessária para estar ciente de que importar "embed" é a maneira adequada de corrigir o uso de embed.Files
, etc.
Essa abordagem corrige o problema de verificação de tipo - não é uma mudança completa de linguagem - mas ainda tem uma complexidade de implementação significativa.
Notavelmente, CL 276835 é uma remoção líquida de código. Em particular, o código do compilador (que terá que ser reimplementado no gccgo e em outros compiladores) é muito mais simples.
Também espero que seja mais fácil ensinar go / types sobre a semântica diferenciada de embed.Files
, etc. (ou seja, que eles só aceitam argumentos literais de string) do que ensiná-la sobre //go:embed
.
O comando go precisaria analisar todo o arquivo de origem Go para entender quais arquivos precisam ser disponibilizados para incorporação. Hoje, ele analisa apenas até o bloco de importação, nunca expressões Go completas
Com CL 276835, o comportamento é o mesmo da dica: o comando go analisa todo o arquivo de origem Go em busca de arquivos que importam pacote incorporado, e apenas as importações de arquivos que não o fazem.
Reconhecidamente, para arquivos que importam embed, CL 276835 faz uma análise completa e analisa, enquanto tip faz uma varredura de string mais eficiente para //go:embed
comentários. Eu acho que um algoritmo de uma passagem mais otimizado para encontrar embed.Files
chamadas é possível, se desejado.
Também não estaria claro para os usuários quais restrições são colocadas nos argumentos para essas chamadas especiais: elas se parecem com chamadas Go comuns, mas só podem aceitar strings literais, não strings computadas pelo código Go e provavelmente nem mesmo constantes nomeadas (ou então o o comando go precisaria de um avaliador de expressão Go completo).
Isso não parece substancialmente diferente da diretiva //go:embed
para mim: os usuários não têm nenhuma expectativa de quais argumentos podem usar lá até que os usem pela primeira vez. Além disso, de qualquer forma, os usuários receberão mensagens de erro do compilador se o usarem incorretamente, mas IDEs e outras ferramentas fornecerão automaticamente godocs e referências cruzadas melhores para embed.Files
, etc.
@mdempsky FS
após uma importação de ponto de embed
é do mesmo tipo. Então, esse caso particular parece um "sim, tudo bem" direto (tentei e já funciona). Da mesma forma, byte
é um apelido para uint8
, então []uint8
também é do mesmo tipo que []byte
, embora um tanto surpreendentemente, isso não funcione agora . No entanto, eu diria que a semântica conforme implementada está bem, por enquanto - podemos sempre permitir mais tipos e / ou apelidos e / ou mesmo "o mesmo tipo subjacente" posteriormente, se houver necessidade.
Acho que a semântica mais parecida com Go para variáveis // go: embed seria a de literais compostos: que cada execução produz uma nova cópia.
Tendo a concordar, essa era minha expectativa também. A princípio, pensei que isso poderia ser um artefato de análise de escape e o compilador perceber que você não altera os dados, mas não:
func f() {
//go:embed foo.txt
var x []byte
fmt.Printf("%q\n", x)
x[0] = 'x'
}
func main() {
f()
f()
}
No entanto, "cada declaração var cria uma nova variável" também tem seus problemas, porque significa um código como este
func ServeIndex(w http.ResponseWriter, r *http.Request) {
//go:embed "index.html"
var x []byte
http.ServeContent(w, r, "index.html", time.Now(), bytes.NewReader(x))
}
iria alocar e copiar desnecessariamente. Talvez isso seja bom e seja superado pelos benefícios de uma semântica mais clara. Mas talvez seja também um sinal para proibir as incorporações locais de []byte
conforme você mencionou.
Também espero que seja mais fácil ensinar go / types sobre as semânticas diferenciadas de embed.Files, etc. (ou seja, que eles só aceitam argumentos literais de string), do que ensiná-lo sobre // go: embed.
Apenas constantes de string, pelo menos, podem ser feitas no sistema de tipo Go.
Mas, go/types
precisa saber sobre arquivos embutidos, com //go:embed
? Afinal, o tipo dessas variáveis é bastante claro (com exceção das incorporações locais de []byte
, conforme discutido).
//go:embed "foo"
var x []byte
Isso realmente é para ser mutável? Não consigo pensar em um único caso de uso em que gostaria de incorporar um ativo estático, mas alterá-lo em tempo de execução, então sinto que isso apenas habilitará bugs. Eu teria ficado mais feliz enfiando-o em .rodata
e entrando em pânico se algo tentasse alterá-lo.
(Solicitado por x[0] = 'x'
acima.)
@Merovius escreveu:
@mdempsky
FS
após uma importação de ponto deembed
é do mesmo tipo. Então, esse caso particular parece um "sim, tudo bem" direto (tentei e já funciona).
Obrigado. Esqueci como as importações de ponto funcionam dentro do compilador, por isso li mal o código. Eu deveria ter tentado antes de incluir aquele exemplo.
No entanto, eu diria que a semântica conforme implementada está bem, por enquanto - podemos sempre permitir mais tipos e / ou apelidos e / ou mesmo "o mesmo tipo subjacente" posteriormente, se houver necessidade.
[]byte
e []uint8
são tipos idênticos sob a especificação Go, assim como inúmeros outros tipos construídos usando apelidos de tipo. Se não for aceitável que um compilador trate embed.Files("foo" + ".txt")
diferente de embed.Files("foo.txt")
, então o mesmo argumento deve se estender para não permitir que ele trate tipos com alias de maneira diferente.
Mas,
go/types
precisa saber sobre arquivos incorporados, com//go:embed
?
Não, não precisa, assim como não precisa saber sobre a semântica especial de embed.Files
também. Mas para qualquer sintaxe, ainda pode haver valor em go / types, sendo capaz de alertar sobre o uso indevido.
-
@ tv42 escreveu:
Isso realmente é para ser mutável?
Evidentemente. O compilador Go organiza especificamente para string
e embed.FS
incorporações a serem desduplicadas e colocadas em rodata, enquanto []byte
incorporações não são:
No entanto, é verdade que a proposta não parece ter abordado esse ponto.
Obrigado pelos comentários atenciosos. Eu apresentei dois problemas para acompanhamento:
# 43216 - remove o suporte para diretivas de incorporação em variáveis locais
# 43217 - define os aliases de tipo String e Bytes que devem ser usados com // go: embed
Eu não registrei um problema para a própria sintaxe // go: embed. Eu queria dizer o porquê aqui, para que não pareça a todos que estou apenas descartando isso.
Na época em que escrevi sobre o processo de proposta, passei muito tempo procurando maneiras de (e não) lidar com decisões em grandes grupos. Uma fonte que descobri que fazia muito sentido para mim foi a postagem “ Tomada de decisão aberta ” de John Ousterhout. Vale a pena ler a postagem inteira, mas vou apenas citar aqui a seção sobre Reconsideração:
A última regra para a tomada de decisão aberta é certificar-se de não reconsiderar uma decisão a menos que haja novas informações significativas. Esta regra tem duas partes. Primeiro, você deve estar preparado para corrigir decisões que se revelem erradas de maneira significativa. Isso é particularmente verdadeiro em startups: muitas decisões devem ser tomadas sem informações completas e, inevitavelmente, algumas delas acabarão por estar erradas. Uma decisão errada que é corrigida rapidamente causa pouco ou nenhum dano, mas uma decisão errada que não é corrigida pode ser catastrófica.
Por outro lado, você não deve reconsiderar uma decisão, a menos que novas informações significativas tenham surgido desde que a decisão original foi tomada. Se nenhuma informação nova estiver disponível, reconsiderar uma decisão provavelmente produzirá o mesmo resultado que a decisão original, desperdiçando o tempo de todos. Não é incomum que os funcionários me procurem semanas depois que uma decisão foi tomada: "John, votei contra a decisão de XYZ e, quanto mais penso nisso, mais convencido fico de que estava errado; realmente acho que precisamos reconsidere isso. " Minha resposta é "Quais são as novas informações que você tem?" Se a resposta for "nenhum", não reconsideramos. Em situações como essa ajuda ter um registro dos argumentos apresentados durante a discussão, para que você possa verificar se as novas informações são realmente novas. Se você torna muito fácil reabrir decisões, você acaba vacilando para frente e para trás onde nenhuma decisão é definitiva e os funcionários hesitam em implementar decisões porque não acreditam que sejam permanentes.
Se você quiser ter certeza de não reconsiderar muitas decisões, certifique-se de reunir amplamente as informações durante o processo de tomada de decisão. Se você não receber informações suficientes, aumentará a probabilidade de que novas informações significativas surjam após a decisão, o que significa que você terá que reconsiderar. Se você fizer um bom trabalho de coleta de informações, é muito menos provável que tenha que rever suas decisões.
Isso realmente ressoou em mim: o processo de proposta Go (como grandes projetos de código aberto geralmente) é um sistema com uma carga oferecida muito mais alta do que a capacidade de trabalho, então é importante que nós (discordemos se necessário e) nos comprometamos e passemos para a próxima decisão .
string e [] byte foram adicionados bem tarde no processo de consideração desse problema, em resposta ao feedback inicial, e claramente não pensamos em todas as implicações disso. Então, apresentei essas duas novas edições, # 43216 e # 43217.
Por outro lado, a sintaxe // go: embed foi uma parte central das discussões originais e foi amplamente discutida, tanto prós quanto contras. Não acredito que haja “novas informações significativas” que devam nos levar a reconsiderar essa sintaxe inteiramente, então, no interesse de seguir em frente e manter o conselho de Ousterhout sobre reconsideração em mente, estou deixando isso de lado.
Obrigado novamente por apontar os problemas de string e [] bytes!
Evidentemente. O compilador Go organiza especificamente para
string
eembed.FS
incorporações a serem desduplicadas e colocadas em rodata, enquanto[]byte
incorporações não são:
@rsc por acaso você pensou neste último ponto? Com a implementação atual, pode se tornar uma "prática recomendada" usar string em vez de [] byte para produzir binários melhores. Estamos bem com isso?
Não entendo por que isso seria uma "prática recomendada". Para mim, isso é o mesmo que dizer que é "prática recomendada" fazer constantes de erros sentinela, portanto, não devemos permitir variáveis com escopo de pacote de erro de tipo - no sentido de que eu discordo de ser uma boa prática e da restrição adicional como um solução.
Eu pude ver um argumento para usar apenas string
em vars locais. Mas em variáveis com escopo de pacote, a semântica é clara e bem definida e eu não recomendaria não usar um embed []byte
mais do que eu recomendaria não usar qualquer outra variável []byte
.
@mvdan , se as pessoas string
vez de []byte
como uma prática recomendada, eu consideraria isso uma coisa boa. string
é o tipo Go apropriado para “uma sequência imutável de bytes”, enquanto []byte
é o tipo apropriado para “uma sequência mutável de bytes” ou “uma sequência de bytes de mutabilidade indeterminada”.
Para os casos em que você tem um valor do tipo string
(imutável) e deseja usá-lo como tipo []byte
(indeterminado), você já pode usar unsafe
para fazer isso corretamente . (Veja, por exemplo, meu unsafeslice.OfString
). Talvez devêssemos adicionar uma biblioteca padrão com suporte para essa operação, mas isso parece uma proposta separada.
Portanto, parece correto usar sempre o tipo string
se você realmente deseja que o valor seja somente leitura.
@Merovius @bcmills, você levanta bons pontos e, para ficar claro, não tenho objeções. Eu só quero ter certeza de que os designers da proposta pensem sobre essa distinção antes do lançamento final.
Eu realmente não acho que a desduplicação surgirá muito na prática. (Em que configuração vários pacotes irão incorporar exatamente os mesmos arquivos?) As pessoas devem usar a forma que precisam e não se preocupar com "string significa que meus binários são menores", porque em geral não acho que isso seja verdade.
Algumas pessoas perguntaram sobre ETags. Ficamos sem tempo para isso, mas apresentei uma proposta em https://github.com/golang/go/issues/43223 e espero que isso leve a uma boa ideia que pode entrar no Go 1.17. Desculpe por não ter conseguido nesta rodada.
Obrigado por apresentar os números 43216 e 43217. Se forem aceitos, eles resolverão substancialmente minhas preocupações pendentes com a proposta //go:embed
.
Por outro lado, a sintaxe // go: embed foi uma parte central das discussões originais e foi amplamente discutida, tanto prós quanto contras. Não acredito que haja “novas informações significativas” que devam nos levar a reconsiderar essa sintaxe inteiramente, então, no interesse de seguir em frente e manter o conselho de Ousterhout sobre reconsideração em mente, estou deixando isso de lado.
Posso respeitar a vontade de não querer revisitar assuntos que já foram decididos por meio de extensa discussão. Mas depois de revisar a discussão que ocorreu no # 35950, o tópico do //go:embed
.
Aqui estão os comentários que descobri que tocaram na sintaxe para indicar quais arquivos incorporar:
18 Problema do GitHub e comentários do Reddit
//go:embed
introduz outro nível de complexidade também. Você teria que analisar os comentários mágicos para verificar o código. A abordagem do "pacote embutido" parece mais amigável para a análise estática. " (Observação: a proposta //go:embed
revisada torna mais fácil digitar o código de verificação, mas ainda não é trivial se um analisador quiser realmente ver as strings usadas, porque elas não estão em go / ast.)go build -embed example=./path/example.txt
e algum pacote que expõe acesso a ele (por exemplo, embed.File("example")
, em vez de usar go:embed
? "(Sugere sintaxe de função sobre a diretiva //go:
. Downvoted, mas suspeito porque sugeriu go build
bandeiras.)//go:
nem de novas funções integradas, mas ainda prefere código em vez de comentários.)//go:generate
diretivas não são executadas automaticamente na construção go, o comportamento da construção go pode parecer um pouco inconsistente: //go:embed
funcionará automaticamente, mas para //go:generate
você tem que executar go generate manualmente. ( //go:generate
já pode interromper o fluxo go get se ele gerar arquivos .go necessários para a compilação). " (Incluído para completar, mas já temos //go:
diretivas que afetam e não afetam go build
comportamento mesmo sem //go:embed
, portanto, não acho este argumento convincente.)//go:embed
especificamente; o mesmo argumento se estende a embed.Files
funções, etc.)package embed
para usar a sintaxe Go"import "C"
já estabeleceu um precedente para caminhos de importação" mágicos ". IMO funcionou muito bem."runtime/embed
como mencionado anteriormente, satisfaria isso e permitiria uma fácil extensibilidade no futuro. "go/ast
/ go/format
e go mod edit
). [...] No caso de um pacote especial, não vejo nada a mudar em go.mod
parsing ( go mod
tools) ou go/ast
parser. "binclude.Include(filename)
para incluir um arquivo / diretório o que cerca de fs.Embed(filename)
? "//go:embed
provavelmente será mais comum. Talvez seja hora de considerar uma sintaxe diferente para o compilador diretivas? "Conforme leio os comentários, eles parecem sempre favorecer a sintaxe Go, com a ressalva de querer evitar alterações que quebrem as ferramentas. Eu não vi ninguém discutindo para //go:embed
como a ortografia, tanto quanto apenas aceitá-lo como é. Adicionar novos intrínsecos do compilador com stubs de compatibilidade com versões anteriores para verificadores de tipo legado parece mais em linha com a preferência expressa dos participantes da discussão do que mais //go:
diretivas.
No estilo das pesquisas de polegar para cima / polegar para baixo em # 43216 e # 43217:
Gostei (👍) se você preferir novos intrínsecos do compilador (por exemplo, CL 276835 ):
import "embed"
var website = embed.Files("logo.jpg", "static/*")
Veja embed_test.go para mais exemplos de uso.
(Observação: os argumentos devem ser literais de string , não apenas valores de string. Ou seja, constantes de string declaradas como const logo = "logo.jpg"
ou expressões de string constantes como "logo" + ".jpg"
também não são permitidas. No entanto, embed.Files
, etc. pode ser usado em qualquer contexto, não apenas para inicializar variáveis.)
Não gostei (👎) se você preferir novas diretivas de compilador (ou seja, a proposta atual):
import "embed"
//go:embed logo.jpg static/*
var website embed.FS
@mdempsky Acho que Russ foi bastante claro, o que seria necessário para justificar uma reversão da decisão para ele - novas informações. Acho que reunir comentários anteriores claramente não é isso. Sem intenção de ofender.
Não há precedentes para esses novos tipos de funções, certo? Ou seja, algo que se parece com uma função de pacote normal, mas na verdade é um tipo especial de embutido que só pode ser chamado de uma determinada maneira?
Você diz "intrínseco", mas os intrínsecos atuais se comportam exatamente como as funções Go normais.
x := 10000
_ = bits.RotateLeft64(10, x)
Vestir novas diretivas para se parecerem com funções Go, mas com uma sintaxe diferente (mais restritiva) do que as funções Go, parece uma opção ruim de onde estou sentado. Acho que embed
precisaria ser descrito na especificação, por um lado, ao contrário das diretivas //go:
.
(Você pode aproximar os "únicos argumentos literais permitidos" no código Go normal criando uma função que usa argumentos type internalString string
não exportados, mas deduzo que não é isso que você está propondo, já que seu CL faz alterações no analisador e verificador de tipo.)
@Merovius De acordo com o processo de proposta Go:
Consenso e discordância
O objetivo do processo de proposta é chegar a um consenso geral sobre o resultado em tempo hábil.
Se não for possível chegar a um consenso geral, o grupo de revisão de propostas decide a próxima etapa revisando e discutindo a questão e chegando a um consenso entre eles. Se mesmo o consenso entre o grupo de revisão da proposta não puder ser alcançado (o que seria extremamente incomum), o árbitro (rsc @) analisa a discussão e decide a próxima etapa.
Na minha leitura dos comentários, o consenso pareceu favorecer a sintaxe do código Go. Se o consenso agora é de fato ficar com //go:embed
, eu respeito isso. Mas não acho que o processo documentado justifique a decisão inicial de avançar com //go:embed
.
(No momento, os resultados da pesquisa favorecem fracamente novas funções em vez de novas diretivas, mas não muito. Se os polegares para cima não superarem os polegares para baixo em pelo menos 2: 1, não há problema em simplesmente ignorar isso.)
@cespare
Não há precedentes para esses novos tipos de funções, certo? Ou seja, algo que se parece com uma função de pacote normal, mas na verdade é um tipo especial de embutido que só pode ser chamado de uma determinada maneira?
Existem funções pré-declaradas do universo e funções não seguras do pacote.
Você pode argumentar que o pacote inseguro está documentado na especificação Go, mas eu argumento que não precisa ser. Go costumava oferecer suporte a modos em que o pacote não seguro não estava disponível para os usuários e, ainda hoje, o pacote não seguro tem funcionalidade e restrições que são documentadas apenas nos documentos do pacote, não nas especificações do Go.
Existem também funções internas dentro do tempo de execução Go que são implementadas apenas pelo compilador Go. Por exemplo, getg
, getcallerpc
, getcallersp
e getclosureptr
.
(Você pode aproximar os "únicos argumentos literais permitidos" no código Go normal criando uma função que usa argumentos de string internalString do tipo não exportado,
Acho que seria uma adição razoável para restringir ainda mais o comportamento dos verificadores de tipo legado.
mas percebi que não é isso que você está propondo, já que seu CL faz alterações no analisador e no verificador de tipo.)
CL 276835 não altera o analisador, exceto para remover o novo código do analisador adicionado para //go:embed
. Isso muda o verificador de tipo, mas comparativamente a //go:embed
antes.
Seria fácil estender go / types para estar ciente do pacote embed, mas optei por não fazer para CL 276835 especificamente para mostrar que ele ainda funciona (por exemplo, cmd / vet não está falhando nos testes de unidade do pacote embed).
@mdempsky Você pode discordar sobre se um consenso foi ou não alcançado naquele momento. Assim como você pode discordar da própria decisão. Eu não acho que isso realmente mude as coisas, no entanto. No final das contas, "há consenso" também é uma decisão tomada. E exatamente os mesmos pontos sobre a exigência de novas informações para uma reversão se aplicam a essa decisão.
A necessidade de satisfazer cada pessoa é um vetor de DDoS - tanto no que diz respeito à decisão quanto ao processo com que foi feita.
FTR, a questão sobre ferramentas vs. mudança de linguagem foi discutida , assim como a compensação entre "string-literal vs. string-constant" ("string-literal" requer mágica no verificador de tipo, "string-constant" requer o ir ferramenta para fazer a verificação de tipo - comentários não precisam de nenhuma das duas). Então, novamente, não há realmente nada de novo aqui. Você pode discordar com o resultado de que a decisão ou o processo foi feito com - mas os argumentos que você mencionou têm sido considerados ao tomar a decisão.
A necessidade de satisfazer cada pessoa é um vetor de DDoS - tanto no que diz respeito à decisão quanto ao processo com que foi feita.
Não estou exigindo que esteja pessoalmente satisfeito e acho um insulto que você caracterize minhas postagens como tal. Anteriormente, você também conversou comigo como se eu não estivesse familiarizado com a linguagem Go ou com o compilador Go. Por favor, pare com a condescendência.
Listei acima vários comentários onde as pessoas quase universalmente expressaram uma preferência de não adicionar novas diretivas //go:
, enquanto ninguém havia feito comentários afirmativos em seu apoio. Como tal, a esmagadora preferência expressa pela comunidade pareceu-me favorecer a sintaxe Go, e meu comentário estava argumentando em defesa de suas preferências declaradas.
No entanto, como está, https://github.com/golang/go/issues/41191#issuecomment -747095807 tem mais polegares para baixo do que polegares para cima. Isso é surpreendente para mim, porque parece inconsistente com todos os comentários durante a discussão anterior. Mas estou feliz em aceitar que a questão foi abordada diretamente e (especialmente como alguém que estará envolvido no suporte de longo prazo para esse recurso dentro do compilador Go) estou mais feliz agora em apoiar //go:embed
visto que é de fato a preferência da comunidade e não apenas a preferência dos autores da proposta. Este resultado não teria sido alcançado se a discussão tivesse sido encerrada, como você parece determinado.
FTR, a questão sobre ferramentas vs. mudança de linguagem foi discutida , assim como a compensação entre "string-literal vs. string-constant" ("string-literal" requer mágica no verificador de tipo, "string-constant" requer o ir ferramenta para fazer a verificação de tipo - comentários não precisam de nenhuma das duas).
Esse comentário é irrelevante para os argumentos que eu estava apresentando. A grafia alternativa que criei no CL 276835 tem exatamente as mesmas propriedades técnicas da grafia //go:embed
: as ferramentas que precisam saber quais arquivos devem ser incorporados precisarão ser atualizadas para processar os arquivos de origem Go de maneira diferente (por exemplo, para erro sobre o uso indevido de //go:embed
comentários ou da função embutida embed.Bytes
), enquanto as ferramentas existentes podem continuar a processar o código razoavelmente sem se preocupar com eles (por exemplo, go / types irá ignorar //go:embed
comentários mas não detectará se é aplicado a tipos de variáveis incorretos e pode verificar o tipo embed.Bytes
usando as declarações de stub, mas não saberá rejeitar todas as chamadas que usam argumentos diferentes de literais de string).
A questão de saber se alguma delas é uma "mudança de linguagem" é mais filosófica do que técnica.
(O argumento de Russ de que "se um programa é válido não muda" na proposta //go:embed
também está incorreto. Https://github.com/golang/go/issues/41191#issuecomment-747799509 dá um exemplo de um pacote que era e é válido de acordo com as especificações do Go e também foi aceito pelos lançamentos do conjunto de ferramentas Go até o Go 1.15, mas não será mais válido no Go 1.16.)
Como alguém que deu um 👍 à proposta de Matt de usar var website = embed.Files("logo.jpg", "static/*")
, minha preocupação em usar o formulário de comentário ( //go:embed logo.jpg static/*
) é "facilidade de uso".
Por exemplo, esses 2 exemplos de programa produziriam 2 coisas diferentes, apenas porque uma "importação" foi perdida:
import (
"fmt"
)
//go:embed sample.txt
var sample string
func main() {
fmt.Println(sample) // will print a blank line
}
import (
"embed"
"fmt"
)
//go:embed sample.txt
var sample string
func main() {
fmt.Println(sample) // will print contents of sample.txt
}
Ao forçar o desenvolvedor a usar a importação por meio da semântica da linguagem, você minimiza os problemas em que os arquivos incorporados não estão funcionando conforme o esperado porque não foram inicializados conforme o esperado.
@ kushieda-minori Embora eu ache que minha proposta também seja mais fácil de usar, o primeiro exemplo já não compila na dica:
./x.go:7:3: //go:embed only allowed in Go files that import "embed"
Acho que esse problema também é atenuado por # 43217, já que você precisa importar "embed" para declarar variáveis do tipo embed.Bytes
e embed.String
qualquer maneira. E o compilador (mas não go / types ou cmd / vet) também já relata um erro se você aplicar a diretiva //go:embed
a uma variável de um tipo incorreto.
@mdempsky , entretanto, compilar acidentalmente em uma versão mais antiga do Go não falhará e pode dar uma falsa sensação de que a incorporação funcionou.
Versões mais antigas do Go não têm pacote incorporado, então import "embed"
falhará. É verdade que existe o risco de os usuários escreverem:
package p
//go:embed foo.txt
var foo []byte
e será silenciosamente aceito pelo Go 1.15 e anteriores. Mas não será aceito pelo Go 1.16 e mais recentes. Não há pelo menos nenhum programa que compile com Go 1.15 e Go 1.16 e tenha semânticas diferentes (devido à incorporação de pacote, pelo menos).
Acho que uma correção (parcial) apropriada aqui seria o cmd / compile parar de aceitar diretivas //go:
desconhecidas. Por exemplo, outra limitação da proposta atual relativa à sintaxe embutida Go é que se você digitar incorretamente a diretiva //go:embed
(digamos //go:enbed
, //go;embed
ou // go:embed
com um espaço), ele também será ignorado silenciosamente. (Considerando que erros de digitação como enbed.Bytes("foo.txt")
causariam um erro de verificação de tipo, mesmo com go / types não modificados.)
Grande observação sobre diretivas go:
não validadas. Se isso fosse aplicado, ajudaria a aliviar erros de digitação difíceis de detectar.
Outra ideia que acabei de pensar é que minhas ferramentas estão configuradas para adicionar / remover importações automaticamente, conforme necessário. Se minhas ferramentas estiverem desatualizadas, tenho que evitar a remoção da importação "não utilizada" embed
? Percebi que está resolvido se eu usar embed.String
etc, mas usar []byte]
normais e string
é considerado totalmente válido. Isso pode ser frustrante para um novo gopher que está escolhendo trechos da web para fazer algo funcionar, caso não veja os aliases embed.*
.
mas usar
[]byte]
estring
normais é suposto ser completamente válido.
Eles não serão se # 43217 for aceito. Eu recomendo a leitura sobre # 43216 e # 43217. Ambos receberam um apoio extremamente positivo até agora e parecem muito prováveis de serem aceitos por mim. (No entanto, não estou no comitê de revisão de propostas.)
Obrigado, quando li # 43217 pela primeira vez, perdi a palavra-chave
"have" para usar os tipos embed.*
.
Acho que minha única preocupação é a última que você apontou.
Em Qui, 17 de dezembro de 2020, 20:24 Matthew Dempsky [email protected]
escreveu:
mas usar regular [] byte] e string deve ser completamente válido.
Eles não serão se # 43217 https://github.com/golang/go/issues/43217 for
aceitaram. Eu recomendo a leitura sobre # 43216
https://github.com/golang/go/issues/43216 e # 43217
https://github.com/golang/go/issues/43217 . Ambos receberam
apoio esmagadoramente positivo até agora, e parece muito provável que seja aceito
para mim. (No entanto, não estou no comitê de revisão de propostas.)-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/golang/go/issues/41191#issuecomment-747808153 ou
Cancelar subscrição
https://github.com/notifications/unsubscribe-auth/ADLZABJWBJX475BVYDVD6ODSVKVOTANCNFSM4QTHVTUA
.
Comentários muito úteis
Nenhuma mudança no consenso, então aceita.