Propriedade e segurança da memória

<!-- verified-against: seed-llvm; examples: docs-examples-test -->

seed usa propriedade, recursos somente de movimentação, léxico loans/provenance, capacidades estruturais, limpeza determinística, linear te regiões. Não possui GC, sintaxe de vida útil do verificador de empréstimo ou desenrolamento de exceção. As regras semânticas autorizadas estão em seed/compiler/llvm/SEMANTIC_RULES.md; os limites atuais são rastreados em Status Atual.

Copiar, mover, emprestar, clonar

Operação Significado
copiar a origem e o destino permanecem disponíveis; apenas para valores com capacidade de cópia
mover transferências de propriedade e o local de origem fica indisponível
pedir emprestado uma visão de cópia zero está vinculada ao armazenamento de apoio comprovado
clonar independente explícito value/handle de acordo com a capacidade estrutural

Escalares e agregados contendo apenas cópia de campos. string, bytes, shared_bytes, proprietários de destruidores de usuários, agregados de recursos e linear t obedecer aos seus move/clone/cleanup capacidades.

shared_bytes não pode ser copiado bit a bit: .clone() retém explicitamente outro identificador sem copiar a carga útil. A clonagem de string/bytes de propriedade ou agregados que os contenham pode ser alocada. Os efeitos e recursos de alocação de clones sobrevivem à compilação .sdi excluída da origem.

Lugares e propriedade parcial

O compilador rastreia locais, campos e campos aninhados, índices de matriz fixa conhecidos, cargas úteis ADT ativas, campos de união bruta, tuple/array/nominal agregados, instâncias genéricas, option/result, valores de função, channels/tasks, aliases, blocos, chamadas, retornos e junções de fluxo de controle.

Uma mudança parcial torna o local movido indisponível e impede o uso por todo o proprietário quando a integridade é necessária. Os campos restantes independentes permanecem utilizáveis ​​quando um destruidor de usuário não requer um proprietário completo. As movimentações de índice dinâmico de elementos de recursos permanecem rejeitadas porque o conjunto de limpeza dependeria do tempo de execução.

Limpeza

A limpeza é planejada a partir de fatos de propriedade do HIR digitados, verificados separadamente e transferidos para saídas estruturadas normais. Os proprietários ativos limpam exatamente uma vez na ordem inversa de inicialização bem-sucedida. A reatribuição limpa o proprietário ativo anterior antes de armazenar a substituição. Os loops usam pontos fixos de fluxo de dados na entrada, nas bordas posteriores, na interrupção, na continuação e no retorno.

Pânico, armadilha e aborto terminam sem desenrolar. Eles não executam limpeza lexical ou destruidores de usuários.

Destruidores

fn TypeName.drop() recebe consumo implícito de self. Deve ser exclusivo e retornar void. Chamar value.drop() consome manualmente o proprietário, evitando uma segunda chamada automática. Os proprietários devolvidos transferem sua obrigação de limpeza para o chamador. Os globais Cleanup/resource são rejeitados porque a desmontagem global não é um serviço de linguagem implícito.

A limpeza do ADT é despachada somente na variante ativa. Os sindicatos brutos exigem disciplina insegura no campo ativo; alternativas marcadas seguras devem usar ADTs ou um wrapper auditado.

Propriedade de fechamento

Um fechamento |...| simples possui armazenamento de captura de pilha gerado pelo compilador. A inicialização da captura segue a primeira ordem de uso léxico: os valores com capacidade de cópia são copiados e outros valores de propriedade são movidos. As capturas são imutáveis; uma captura de recurso movida torna o próprio fechamento apenas para movimentação. O proprietário descarta capturas ao vivo exatamente uma vez na ordem inversa de inicialização.

Os fechos simples são reutilizáveis ​​e não escapam. Eles não podem capturar valores que não podem permanecer armazenados com segurança após uma chamada, incluindo visualizações, capturas mutáveis, valores vinculados à região, raw/reference valores, format_args, ou lugares já movidos. Eles não podem ser retornados, retidos em um escopo externo, enviados para uma tarefa ou passados ​​através de um limite de ponteiro de função da plataforma.

box |...| aloca armazenamento de captura explicitamente e retorna Result<box fn(...) -> T, alloc_error>. O proprietário da caixa pode ser devolvido ou armazenado, mas permanece apenas para movimentação, não clonável e não enviável. A falha na alocação descarta as capturas transferidas uma vez; O drop bem-sucedido executa a conversão de drop de captura e libera o ambiente uma vez. Um fechamento em caixa alocado em region(...) não pode escapar dessa região.

Valores lineares

linear t conservas tda representação e adiciona uma obrigação exatamente uma vez. Ele remove copy/clone/shared capacidade enquanto retém fatos válidos de movimentação, empréstimo, envio, sincronização e limpeza. A implementação propaga a obrigação através de aliases, blocos, agregados, option/result, ADTs, genéricos, canais, valores de função e chamadas indiretas, captura de tarefas, parameters/returns e esquema-17 .sdi.

Deixar um valor linear ativo na saída do escopo, consumi-lo duas vezes, cloná-lo, descartá-lo, substituí-lo ou armazená-lo em um armazenamento não linear pré-existente é rejeitado.

Texto, buffers e visualizações

Não há sintaxe vitalícia. @borrow métodos receptores criam empréstimos lexicais compartilhados; as visualizações retornadas permanecem vinculadas ao receptor por meio .sdi resumos. Funções livres também podem retornar funções estáticas ou derivadas de entrada str/visualizações de fatia; uma visão derivada do proprietário de um local string/bytes permanece um V001 escapar. @mut os métodos do receptor criam um empréstimo exclusivo com escopo de chamada e alteram o local do chamador diretamente. Receptores imutáveis ​​e visualizações ao vivo sobrepostas são rejeitados; nenhum movimento ou clone é ocultado pela chamada.

Regiões

fn work() -> Result<i64, alloc_error> {
    region(4096) {
        // allocating operations use the active checked bump allocator
        Ok(42)
    }
}

A capacidade é avaliada uma vez, a alocação alinhada é verificada pelo estouro e a exaustão é retornada como alloc_error por APIs falíveis. As regiões aninhadas são LIFO. Os recursos do corpo são limpos antes que o armazenamento de apoio seja liberado. Proprietários seguros, visualizações e ponteiros vinculados a uma região não podem escapar por meio de resultado, retorno ou atribuição a uma ligação externa.

O trabalho gerado captura o contexto do alocador. As tarefas vinculadas à região permanecem estruturadas e unidas antes da limpeza da região.

Transferência de tarefas

spawn registros copy/move modo de captura. Manter o valor original e um valor de tarefa requer um clone explícito. Cruzar um limite de tarefa requer send; o acesso compartilhado também requer sync. O estado global mutável não pode ser capturado. Cada grupo de tarefas é drenado antes que seu proprietário léxico limpe os recursos.

if let e while let são formas de superfície sobre match: ambos desugar para uma partida de dois braços cujo segundo braço é o complemento, então vinculação, transferência de propriedade, exaustividade e limpeza são exatamente as regras de correspondência. if let Some(x) = e nomeia seu complemento como None; outro padrão variante mantém um braço curinga, que é rejeitado quando o examinado carrega uma carga útil somente de movimentação porque um braço curinga não completa a obrigação de propriedade dessa carga útil - escreva os braços match explícitos ali. Um padrão irrefutável torna o braço do complemento inacessível e é rejeitado. Um literal de matriz repetido [value; count] se expande para elementos count de um valor literal e obtém seu tipo de elemento do tipo de matriz esperado, portanto, let slots: [4]usize = [0; 4] não precisa de conversão; o valor deve ser um literal, uma conversão de um literal ou uma matriz desses, e a contagem é um literal inteiro positivo de no máximo 65536.

Limite inseguro

Alocação bruta, acesso a ponteiro, FFI, syscalls, atômicos, acesso volátil, conversões de espaço de endereço, acesso de união bruta e montagem bruta requerem unsafe. O código inseguro não desativa a propriedade dos valores seguros circundantes; ele assume obrigações específicas que devem ser escondidas atrás de invólucros seguros auditados e restritos.

unsafe struct parâmetros: um unsafe struct com um campo de ponteiro bruto e @repr("null_niche") é um tipo de recurso que não pode ser passado por valor como parâmetro de função. Passá-lo por valor move o proprietário subjacente, tornando-o indisponível para uso posterior. Métodos com @mut o receptor requer um local mutável e não pode ser chamado em um valor movido. Para usar esse tipo além dos limites da função, incorpore a lógica ou retorne o proprietário junto com o resultado.

Quais mudanças na versão

--release e --release-small otimizam a mesma semântica segura. As verificações de limites, discriminantes, procedência, propriedade e limpeza permanecem, a menos que se prove serem redundantes. Nenhum perfil de lançamento não verificado é implementado.

Livro de receitas de propriedade

Estes são os vinte padrões de construção recorrentes usados pelo Grove e pelos fixtures do compilador. A parte importante de cada padrão é o invariante que ele preserva; quando um valor não puder satisfazer essa invariante, mantenha a operação em um adaptador unsafe estreito em vez de enfraquecer o modelo proprietário.

Padrão Construção de sementes Invariante preservado
1. construtor próprio stringbuilderde string_builder_new(capacity) então falível to_string() O construtor possui bytes inicializados e publica apenas válidos UTF-8.
2. visão do analisador emprestado input.view() ou str.slice(...) A análise toma emprestado armazenamento estável e aloca apenas para resultados próprios.
3. arena mais vistas region(capacity) { ... } Valores e visualizações vinculados à região não podem escapar da região.
4. buffer de bytes limitado bytes.zeroed(n)? mais uma visualização mutável O comprimento confirmado nunca inclui armazenamento não inicializado.
5. preenchimento de capacidade ociosa auditado spare_capacity() mais commit_initialized() O produtor inicializa exatamente o prefixo que confirma.
6. conversão falível view.to_string()? / view.to_bytes()? A falha na alocação é um resultado digitado e a limpeza permanece estruturada.
7. carga útil somente leitura compartilhada bytes.to_shared()? então explícito .clone() O compartilhamento mantém um identificador; a mutação da carga útil é impossível.
8. mutação disjunta split_at_mut(index) O proprietário permanece exclusivamente emprestado através de duas visões não sobrepostas.
9. acesso à coleção indexada verificado values[index] Os limites são verificados; as movimentações dinâmicas de recursos permanecem rejeitadas.
10. projeção de recursos estáticos um índice ou campo de array conhecido A limpeza pode nomear o elemento movido exato.
11. entrada de mapa somente para movimentação HashMap<K,V> com CollectionKey Key/value a propriedade é transferida exatamente uma vez por meio de remoção.
12. transferência de canal mover um endpoint ou carga útil para spawn Os valores de tarefas cruzadas satisfazem send; o acesso compartilhado também requer sync.
13. resultado da tarefa digitada task_result_new, conclua e aguarde O identificador de resultado e a carga útil têm um proprietário léxico.
14. cancelamento estruturado cancelamento de tarefa mais um limite de junção O cancelamento não ignora a associação ou limpeza da tarefa.
15. Retorno de chamada C nomeado fn(...) -> ... em um extern "c" limite O lado C recebe um ponteiro de código fino, nunca um proprietário do ambiente Seed.
16. Cabo opaco *mut u8 dentro de um pequeno adaptador inseguro As obrigações estrangeiras de vida e validade permanecem explícitas na fronteira.
17. repr(C) gravar @repr("c") struct para valor FFI A ordem de campo e o layout ABI fazem parte do contrato declarado.
18. retorno de chamada retido `box ...
19. clonar antes da distribuição value.clone() perante consumidores independentes Cada consumidor recebe uma decisão de propriedade explícita.
20. resultado em spawn spawn { fallible_work()? } e wait() Os caminhos de erro e sucesso ultrapassam os limites da tarefa com a limpeza digitada.

Para um exemplo de diagnóstico resolvido, comece com o intervalo de consumo em S001e aplique o menor padrão acima que restaura um único proprietário. O compilador deve continuar a rejeitar movimentos dinâmicos, escapes de região, retornos de chamada inseguros e limpeza ausente, mesmo quando uma construção diferente seria mais curta.

Exemplo S001 resolvido (mover entre uma chamada e uma ramificação)

Um novo usuário move title para report na construção da estrutura e depois consome a ligação movida dentro de uma ramificação posterior:

struct Report {
    title: string
}

fn main() -> Result<i64, alloc_error> {
    let title = "quarterly".to_string()?
    let report = Report { title: title }
    if len(report.title) == 9 as usize {
        let again = title
        Ok(0)
    } else {
        Ok(1)
    }
}

O compilador resolve isso apenas a partir do diagnóstico: error[S001] nomeia o caminho de propriedade (title), um note: ownership path declared here aponta para a ligação, um note: move occurred here aponta para a construção da estrutura de consumo e a linha help: indica a direção do reparo (clone 'title' explicitly before moving). A menor correção clona o título no relatório e move o original para seu proprietário final antes da ramificação, para que cada ramificação saia com a mesma disponibilidade de recursos:

struct Report {
    title: string
}

fn main() -> Result<i64, alloc_error> {
    let title = "quarterly".to_string()?
    let report = Report { title: title.clone() }
    let again = title
    if len(report.title) == 9 as usize {
        let total = len(again) + len(report.title)
        Ok(if total == 18 as usize { 0 } else { 1 })
    } else {
        let rest = len(again)
        Ok(if rest == 9 as usize { 2 } else { 3 })
    }
}

Cada padrão acima (e ambas as metades deste exemplo resolvido) é um programa corpus executável verificado por make -C seed/compiler/llvm gate47-cookbook-test; o corpus reside em seed/compiler/llvm/tests/cookbook/.