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
- o texto literal é estático emprestado
str; stringpertence a UTF-8 válido e se move;bytesé um proprietário binário mutável exclusivo e movimentos;shared_bytesé um identificador somente leitura compartilhado com clone explícito;- propriedade
stringtoma emprestado automaticamente comostrquando esperado; - derivado do proprietário
str,[]temut []tas vistas carregam a proveniência; - uma visualização ao vivo evita movimentação, substituição, liberação, realocação ou mutação do seu proprietário de apoio; uma gravação é verificada apenas em relação a empréstimos estrangeiros, portanto, a gravação através da visualização exclusiva ao vivo que possui o local (ou através de uma partição irmã do mesmo grupo de partição) é a própria autoridade dessa visualização, enquanto qualquer outro empréstimo ativo sobreposto torna a gravação ilegal, mesmo quando o elemento escrito é Cópia;
mut []t.split_at_mut(index)verificaindex <= len, não aloca nada e retorna duas visualizações lexicais exclusivas; As ligaçõesmatchpodem usarmut name;- o proprietário de apoio não pode ser lido, movido, redimensionado ou liberado enquanto o a tupla de partição está ativa; as partições aninhadas permanecem confinadas ao seu pai;
- retornar ou armazenar uma visualização é permitido somente quando seu static/input/receiver a procedência comprova vida útil suficiente;
- a seleção condicional carrega a união finita de todas as origens possíveis;
a implementação limita esse conjunto em oito origens e transforma o overflow ou a proveniência indireta não suportada em
unknown, nunca em uma prova vitalícia; - os resumos de chamadas
.sdidiretos preservam essas origens simbólicas entre os módulos; O esquema de cache HIR 9 armazena os mesmos registros independentes de destino, enquanto cada família de segurança é executada novamente após a reutilização do cache; - local/temporary-derived visões não podem escapar através de retornos, globais, contêineres, tarefas ou biblioteca dinâmica ABI.
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/.