O arquivo, o soquete e a conexão
O que a posse protege num mundo com coletor — e o que ela não protege.
A primeira coisa a dizer, porque ela evita a leitura errada de tudo o mais: num mundo com coletor, a memória nunca esteve em risco. O que Arcane.Posse protege é o protocolo de um recurso — soltar uma vez, não usar depois de soltar, não escrever no meio de uma leitura.
| O problema | Sem posse | Com posse |
|---|---|---|
| usar depois de fechar | erro do sistema operacional, três camadas longe | PosseMovidaError, na linha que usou |
| fechar duas vezes | erro, ou pior: fecha o descritor de outro | soltar é idempotente |
| esquecer de fechar | o arquivo fica aberto até o processo morrer | o escopo solta, e o check avisa |
| dois donos do mesmo arquivo | quem fecha primeiro estraga o outro | há um dono, e mover é explícito |
adopt Arcane.Posse as Posse
// O dono carrega o valor E o que fazer ao soltar.
fechados := []
arquivo := Posse.dono({"nome": "dados.csv"},
lambda v => fechados.append(v["nome"]),
"arquivo")
// Usar é sempre por dentro de uma ação: não há como guardar a
// referência crua e usá-la depois de soltar.
assert arquivo.usar(lambda v => v["nome"]) is "dados.csv"
assert arquivo.vivo() is yes
arquivo.soltar()
assert fechados is ["dados.csv"]
assert arquivo.solto() is yes
// E soltar de novo NÃO é erro.
assert arquivo.soltar() is noPor que `soltar` é idempotente#
Um close() escrito no defer e no caminho de erro é a forma mais comum de fechar recurso — e se o segundo soltar levantasse, a disciplina atrapalharia em vez de ajudar. A regra: soltar é pedir um estado final, e nesse ponto já não importa se ele já estava lá.
Usar depois de soltar é erro, e o erro diz onde#
adopt Arcane.Posse as Posse
conexao := Posse.dono({"banco": "loja"})
conexao.soltar()
monitor:
conexao.usar(lambda v => v["banco"])
assert no
handle Error as e:
out e.messageO de fora solta o que possuía#
Um dono que guarda outro dono precisa soltá-lo — é a drop glue. Sem isso, soltar o de fora deixaria o de dentro aberto, que é exatamente o vazamento que a peça existe para evitar:
adopt Arcane.Posse as Posse
soltos := []
action abrir(nome):
yield Posse.dono({"nome": nome}, lambda v => soltos.append(v["nome"]), nome)
// Um 'Escopo' guarda vários, e solta todos na ordem inversa da
// abertura — como a pilha de um bloco.
escopo := Posse.escopo()
escopo.guardar(abrir("conexao"))
escopo.guardar(abrir("transacao"))
escopo.guardar(abrir("arquivo"))
assert escopo.quantos() is 3
escopo.soltar()
assert soltos is ["arquivo", "transacao", "conexao"]
out soltosA ordem inversa não é estética: a transação foi aberta sobre a conexão, e fechar a conexão primeiro deixaria a transação sem onde confirmar.
E o `defer`, que já existia#
defer | Posse | |
|---|---|---|
| quando roda | na saída da ação | quando o dono é solto, ou o escopo fecha |
| quem garante | o interpretador | quem escreveu |
| protege de usar depois | não | sim — e é a diferença que importa |
| atravessa fronteira | não | sim: o dono pode ser movido |
| custo para quem não usa | zero | zero |
Os dois convivem, e o mais comum é usar defer para o caso simples e Posse quando o recurso atravessa — vai para dentro de uma estrutura, é devolvido por uma ação, ou tem mais de um candidato a dono.