Pular para o conteúdo

O livro · 08

Memória

book.md · 88 linhas · 2 min de leitura

Objetivo: entender por que você quase nunca pensa em memória (colorless + GC default), e como descer ao metal (@mm, @transfer/@promote) quando precisa.

Alocação é colorless: cada objeto carrega um ponteiro implícito pro seu MemoryManager, e o runtime chama a interface sem saber qual estratégia está por baixo. Com o GC default, você escreve código normal e nunca toca @mm:

fn build() -> Report {
r := Report.new() // aloca pelo @mm do contexto (GC, por default)
r.add(linha)
return r // sem free manual; o GC cuida
}

Mutabilidade segue o capítulo 01: var muta, imutável aliasa de graça (compartilhado sem cópia). Para mutar o valor de quem chama, use mut (write-back, estilo inout):

fn accumulate(total: mut int, values: []int) {
loop v in values { total += v }
}
var soma := 0
accumulate(mut soma, dados) // 'mut' no call site; o alvo tem que ser 'var'

Ponteiro *T (um só tipo, sem *const/*mut) serve para segurar uma referência persistente (grafos, FFI); nasce de &lugar com identidade no heap, e deref é explícito (*p). Coleções usam [*]T (ponteiro-de-muitos) como backing, algo que você raramente vê direto.

@mm define o allocator do contexto (processo, função ou objeto). As estratégias: gc (default), arena (libera em bloco), none (manual, estilo C), c (memória vinda do C).

fn handle(req: Request) -> Response {
arena := Arena.new()
defer arena.free_all() // tudo daqui morre de uma vez
@mm(arena) {
... // alocações deste bloco usam a arena
}
}

Sob arena/none você é dono do lifetime (território unsafe); o GC é a rede segura por construção.

Como processos não compartilham memória, passar um dado de um pro outro é explícito. @transfer move (invalida a origem; o objeto viaja com seu @mm intacto); @promote(mm) copia pra outro MemoryManager:

@mm(arena) spawn producer(data @transfer) // move; 'data' fica inválido aqui
@mm(gc) spawn consumer(chan)
result @promote(gc, deep) // copia o subgrafo pro GC (corta o cordão com a arena)

(Cópia simples é o default não-marcado; não há @copy.)

@promote copia o objeto no ponteiro. Um bloco gc carrega seu tamanho no header, então promover um [*]T gc copia o bloco inteiro, com quantos elementos tiver. Um [*]T cru (many-pointer non-gc) não carrega length – como um ponteiro C – então promove como um único objeto. Para promover uma coleção multi-elemento de fora de uma região non-gc, promova um valor que carrega length (uma List, ou um bloco gc), não um many-pointer cru.

As conveniências de alto nível (List.push, string.append, encode) não threadam OOM, porque isso seria coloring. A regra é a do BEAM: a alocação que falha mata o processo, e o supervisor trata (cap. 07). O caso recuperável com orçamento (uma arena com teto, um @limit) desce ao mem.alloc, que devolve Result[Ptr, error{OOM}]:

@limit(16mb)
spawn worker(data) // estourou o teto → erro de OOM vira o |e| do supervisor

Dois mundos sem conflito: alto nível colorless que crasha (99% do código), e baixo nível com Result explícito (autor de allocator, OOM com orçamento).

Próximo: 09 · Channels de rede