Pular para o conteúdo

Mover, emprestar e copiar

As três formas de passar um recurso adiante — e o que cada uma promete.

Passar um recurso para outra parte do programa tem três significados diferentes, e confundi-los é de onde vem quase todo bug de recurso.

O que aconteceQuem fecha
movero dono antigo perde o valoro novo dono
emprestaro outro usa, e devolveo dono, como antes
copiarhá dois valores independentescada um o seu
dataforge
adopt Arcane.Posse as Posse

original := Posse.dono({"id": 1}, void, "sessao")
novo := original.mover()

// O antigo perdeu: usá-lo é erro, e o erro diz que foi movido.
assert original.movido() is yes
assert novo.usar(lambda v => v["id"]) is 1

monitor:
    original.usar(lambda v => v["id"])
    assert no
handle Error as e:
    out e.message

Emprestar, e o `com` que SOLTA#

Duas peças parecidas com propósitos diferentes, e trocá-las é o erro mais comum desta área:

d.usar(acao)Posse.com(d, acao)
o que fazempresta para ler, e devolveusa e solta no fim
o dono depoiscontinua vivosolto
equivale aum empréstimoo defer aplicado a um dono
quandono meio do trabalhona última vez que se usa aquele recurso
dataforge
adopt Arcane.Posse as Posse

// 'usar' empresta e devolve: o dono continua vivo.
d := Posse.dono([1, 2, 3])
assert d.usar(lambda v => len(v)) is 3
assert d.vivo() is yes
assert Posse.estado(d)["emprestimos"] is 0
d.soltar()

// 'com' é RAII: ele usa e SOLTA — inclusive quando o corpo falha,
// que é justamente o caminho por onde metade dos recursos vaza.
soltos := []
d2 := Posse.dono([1, 2], lambda v => soltos.append("fechou"))
assert Posse.com(d2, lambda v => len(v)) is 2
assert d2.solto() is yes
assert soltos is ["fechou"]

// E com erro no meio, ele solta do mesmo jeito.
d3 := Posse.dono([1], lambda v => soltos.append("fechou 3"))
monitor:
    Posse.com(d3, lambda v => 1 / 0)
handle Error:
    out "a ação falhou…"
assert d3.solto() is yes
assert soltos is ["fechou", "fechou 3"]

Não se escreve no meio de uma leitura#

É a regra que dá nome à disciplina: enquanto há um empréstimo de leitura vivo, um empréstimo exclusivo é recusado. Sem isso, a coleção muda debaixo de quem a percorre — e o sintoma é um item pulado, não um erro.

dataforge
adopt Arcane.Posse as Posse

cel := Posse.celula([1, 2, 3])

// Ler e escrever recebem uma AÇÃO, e é isso que dá o escopo: o valor
// não escapa, e a exclusividade vale só enquanto a ação roda.
assert cel.ler(lambda v => len(v)) is 3

// O que a ação devolve passa a ser o valor — inclusive num número ou
// num texto, onde não há como mexer no lugar.
cel.escrever(lambda v => [...v, 4])
assert cel.ler(lambda v => len(v)) is 4

contador := Posse.celula(10)
contador.escrever(lambda v => v + 1)
assert contador.ler(lambda v => v) is 11

// 'trocar' devolve o anterior e põe o novo, sem janela entre os dois.
antigo := cel.trocar([9])
assert len(antigo) is 4
assert cel.ler(lambda v => v[0]) is 9

Copiar é RASA, e isso importa#

copiar() devolve outro dono do mesmo valor — é a cópia rasa. Os dois podem soltar sem erro (soltar é idempotente), mas o valor lá dentro continua sendo um só:

dataforge
adopt Arcane.Posse as Posse

d := Posse.dono({"itens": [1, 2]})
c := d.copiar()

// Dois DONOS, um valor: mexer por um aparece no outro.
c.mudar(lambda v => {"itens": [...v["itens"], 3]})
assert c.usar(lambda v => len(v["itens"])) is 3
assert d.usar(lambda v => len(v["itens"])) is 2   // o 'd' guarda o antigo

// Para dois valores de verdade, copie o VALOR, e não o dono:
adopt Arcane.Objetos as Obj
outro := Posse.dono(Obj.clonar_fundo(d.usar(lambda v => v)))
outro.mudar(lambda v => {"itens": [...v["itens"], 9]})
assert d.usar(lambda v => len(v["itens"])) is 2