Proprietà e sicurezza della memoria

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

seed utilizza proprietà, risorse di solo spostamento, lessicale loans/provenance, capacità strutturali, pulizia deterministica, linear te regioni. Non ha GC, sintassi di durata del controllo del prestito o risoluzione delle eccezioni. Sono presenti le regole semantiche autorevoli seed/compiler/llvm/SEMANTIC_RULES.md; i limiti correnti sono tracciati in Stato corrente.

Copia, sposta, prendi in prestito, clona

Operazione Significato
copia sorgente e destinazione rimangono disponibili; solo per valori copiabili
muoversi trasferimenti di proprietà e il luogo di origine diventa non disponibile
prendere in prestito una visualizzazione a copia zero è legata allo storage di backup comprovato
clone esplicito indipendente value/handle secondo la capacità strutturale

Scalari e aggregati contenenti solo campi di copia copia. string, bytes, shared_bytes, proprietari dei distruttori di utenti, aggregati di risorse e linear t obbedire al loro move/clone/cleanup capacità.

shared_bytes non è copiabile bit a bit: .clone() conserva esplicitamente un altro handle senza copiare il payload. La clonazione di proprietà string/bytes o gli aggregati che li contengono possono essere allocati. Gli effetti e le funzionalità di allocazione dei cloni sopravvivono alla compilazione .sdi elisa alla fonte.

Luoghi e proprietà parziale

Il compilatore tiene traccia delle variabili locali, dei campi e dei campi nidificati, degli indici di array fissi noti, dei payload ADT attivi, dei campi raw-union, tuple/array/nominal aggregati, istanze generiche, option/result, valori di funzione, channels/tasks, alias, blocchi, chiamate, ritorni e join del flusso di controllo.

Uno spostamento parziale rende indisponibile il luogo spostato e ne impedisce l'utilizzo da parte dell'intero proprietario quando è richiesta la completezza. I campi rimanenti indipendenti rimangono utilizzabili quando un distruttore utente non richiede un proprietario completo. Gli spostamenti dell'indice dinamico degli elementi di risorsa rimangono rifiutati perché il set di pulizia dipenderebbe dal runtime.

Pulizia

La pulizia è pianificata in base ai fatti di proprietà HIR digitati, verificati separatamente e ridotti alle normali uscite strutturate. I proprietari live puliscono esattamente una volta nell'ordine inverso di inizializzazione riuscita. La riassegnazione pulisce il precedente proprietario attivo prima di archiviare il sostituto. I cicli utilizzano punti fissi del flusso di dati tra ingresso, bordi posteriori, interruzione, continuazione e ritorno.

Panico, trappola e interruzione terminano senza rilassarsi. Non eseguono la pulizia lessicale o i distruttori degli utenti.

Distruttori

fn TypeName.drop() riceve il consumo implicito self. Deve essere univoco e restituire void. La chiamata a value.drop() consuma manualmente il proprietario, impedendo una seconda chiamata automatica. I proprietari restituiti trasferiscono il loro obbligo di pulizia al chiamante. I valori globali Cleanup/resource vengono rifiutati perché lo smontaggio globale non è un servizio linguistico implicito.

La pulizia ADT viene inviata solo sulla variante attiva. I sindacati grezzi richiedono una disciplina attiva sul campo non sicura; le alternative con tag sicuri dovrebbero utilizzare ADT o un wrapper controllato.

Proprietà di chiusura

Una semplice chiusura |...| possiede l'archiviazione di acquisizione dello stack generata dal compilatore. L'inizializzazione della cattura segue il primo ordine di uso lessicale: i valori che possono essere copiati vengono copiati e gli altri valori posseduti si spostano. Le acquisizioni sono immutabili; un'acquisizione di risorse spostata rende la chiusura stessa di solo spostamento. Il proprietario rilascia le acquisizioni live esattamente una volta nell'ordine di inizializzazione inverso.

Le chiusure semplici sono riutilizzabili e senza fuga. Non possono acquisire valori che non possono rimanere archiviati in modo sicuro dopo una chiamata, incluse visualizzazioni, acquisizioni modificabili, valori legati alla regione, raw/reference valori, format_argso luoghi già spostati. Non possono essere restituiti, conservati in un ambito esterno, inviati a un'attività o passati attraverso il limite di un puntatore a funzione della piattaforma.

box |...| alloca esplicitamente lo spazio di archiviazione di acquisizione e restituisce Result<box fn(...) -> T, alloc_error>. Il proprietario in scatola può essere restituito o archiviato ma rimane di solo spostamento, non clonabile e non inviabile. L'errore di allocazione elimina le acquisizioni trasferite una volta; il rilascio riuscito esegue il thunk di rilascio di acquisizione e libera l'ambiente una volta. Una chiusura scatolata assegnata sotto region(...) non può uscire da quella regione.

Valori lineari

linear t conserve te aggiunge un obbligo esattamente una volta. Rimuove copy/clone/shared funzionalità pur mantenendo informazioni valide su spostamento, prestito, invio, sincronizzazione e pulizia. L'implementazione propaga l'obbligo attraverso alias, blocchi, aggregati, option/result, ADT, generici, canali, valori di funzione e chiamate indirette, acquisizione di attività, parameters/returns e schema-17 .sdi.

Lasciare un valore lineare attivo all'uscita dall'ambito, consumarlo due volte, clonarlo, scartarlo, sovrascriverlo o archiviarlo in un archivio non lineare preesistente viene rifiutato.

Testo, buffer e visualizzazioni

Non esiste una sintassi a vita. @borrow i metodi riceventi creano prestiti lessicali condivisi; le visualizzazioni restituite rimangono legate al destinatario .sdi riassunti. Le funzioni libere possono anche restituire statici o derivati dall'input str/viste in sezioni; una vista derivata dal proprietario da un locale string/bytes rimane un V001 fuggire. @mut i metodi del ricevitore creano un prestito esclusivo nell'ambito della chiamata e modificano direttamente la posizione del chiamante. I ricevitori immutabili e le visualizzazioni live sovrapposte vengono rifiutati; nessuno spostamento o clone viene nascosto dalla chiamata.

Regioni

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

La capacità viene valutata una volta, l'allocazione allineata viene controllata in overflow e l'esaurimento viene restituito come alloc_error dalle API fallibili. Le regioni nidificate sono LIFO. Le risorse del corpo vengono pulite prima che venga rilasciata la memoria di backup. I proprietari, le visualizzazioni e i puntatori sicuri legati a un'area non possono sfuggire tramite il risultato, la restituzione o l'assegnazione a un'associazione esterna.

Il lavoro generato cattura il contesto dell'allocatore. Le attività legate alla regione rimangono strutturate e si uniscono prima della pulizia della regione.

Trasferimento di attività

spawn record copy/move modalità di cattura. Mantenere il valore originale e quello di un'attività richiede un clone esplicito. È necessario attraversare il confine di un'attività send; richiede anche l'accesso condiviso sync. Lo stato globale mutevole non può essere catturato. Ogni gruppo di attività si esaurisce prima che il proprietario lessicale pulisca le risorse.

if let e while let sono forme superficiali su match: entrambi si desugaro in una partita a due bracci il cui secondo braccio è il complemento, quindi vincolo, trasferimento di proprietà, esaustività e pulizia sono esattamente le regole della partita. if let Some(x) = e nomina il suo complemento None; un'altra variante mantiene un braccio con caratteri jolly, che viene rifiutato quando lo scrutatore trasporta un carico utile di solo movimento perché un braccio con caratteri jolly non completa l'obbligo di proprietà di quel carico utile: scrivi lì i bracci match espliciti. Uno schema inconfutabile rende il braccio del complemento irraggiungibile e viene rifiutato. Un valore letterale di array ripetuto [value; count] si espande in elementi count di un valore letterale e prende il tipo di elemento dal tipo di array previsto, quindi let slots: [4]usize = [0; 4] non necessita di cast; il valore deve essere un valore letterale, un cast di un valore letterale o un array di questi, e il conteggio è un valore letterale intero positivo pari al massimo a 65536.

Confine non sicuro

L'allocazione non elaborata, l'accesso al puntatore, FFI, le chiamate di sistema, gli atomi, l'accesso volatile, i cast dello spazio degli indirizzi, l'accesso all'unione non elaborata e l'assemblaggio non elaborato richiedono unsafe. Il codice non sicuro non disabilita la proprietà dei valori sicuri circostanti; presuppone obblighi specifici che dovrebbero essere nascosti dietro ristretti involucri sicuri controllati.

unsafe struct parametri: un unsafe struct con un campo puntatore grezzo e @repr("null_niche") è un tipo di risorsa che non può essere passato per valore come parametro di funzione. Passarlo per valore sposta il proprietario sottostante, rendendolo non disponibile per un utilizzo successivo. Metodi con @mut il ricevitore richiede un posto mutevole e non può essere chiamato su un valore spostato. Per utilizzare un tipo di questo tipo oltre i limiti della funzione, incorporare la logica o restituire il proprietario insieme al risultato.

Quale versione cambia

--release e --release-small ottimizzano la stessa semantica sicura. I controlli relativi a limiti, discriminanti, provenienza, proprietà e pulizia rimangono a meno che non risultino ridondanti. Non viene implementato alcun profilo di rilascio non controllato.

Ricettario di proprietà

Questi sono i venti modelli di costruzione ricorrenti utilizzati da Grove e dai dispositivi del compilatore. La parte importante di ogni modello è l'invariante che preserva; quando un valore non può soddisfare quell'invariante, mantenere l'operazione in un adattatore unsafe ristretto invece di indebolire il modello proprietario.

Modello Costruzione del seme Invariante conservato
1. costruttore di proprietà stringbuilder's string_builder_new(capacity) quindi fallibile to_string() Il builder possiede byte inizializzati e pubblica solo quelli validi UTF-8.
2. vista parser presa in prestito input.view() o str.slice(...) L'analisi prende in prestito spazio di archiviazione stabile e lo alloca solo per i risultati posseduti.
3. arena più viste region(capacity) { ... } I valori e le visualizzazioni legati alla regione non possono uscire dalla regione.
4. buffer di byte delimitato bytes.zeroed(n)? più una vista mutevole La lunghezza impegnata non include mai spazio di archiviazione non inizializzato.
5. riempimento della capacità di riserva controllato spare_capacity() più commit_initialized() Il produttore inizializza esattamente il prefisso che impegna.
6. conversione fallibile view.to_string()? / view.to_bytes()? L'errore di allocazione è un risultato digitato e la pulizia rimane strutturata.
7. payload condiviso di sola lettura bytes.to_shared()? quindi esplicito .clone() La condivisione mantiene un handle; la mutazione del carico utile è impossibile.
8. mutazione disgiunta split_at_mut(index) Il proprietario rimane preso in prestito esclusivamente attraverso due viste non sovrapposte.
9. accesso alla raccolta indicizzata controllato values[index] I limiti vengono controllati; gli spostamenti delle risorse dinamiche rimangono rifiutati.
10. proiezione statica delle risorse un indice o un campo di array noto La pulizia può nominare l'esatto elemento spostato.
11. voce della mappa di solo spostamento HashMap<K,V> con CollectionKey Key/value la proprietà viene trasferita esattamente una volta mediante rimozione.
12. trasferimento del canale spostare un endpoint o un carico utile in spawn I valori delle attività incrociate soddisfano send; l'accesso condiviso richiede anche sync.
13. risultato dell'attività digitata task_result_new, completa, quindi attendi L'handle del risultato e il payload hanno un proprietario lessicale.
14. cancellazione strutturata annullamento dell'attività più un limite di unione L'annullamento non ignora l'unione o la pulizia delle attività.
15. Richiamata C nominato fn(...) -> ... ad un extern "c" confine Il lato C riceve un puntatore di codice sottile, mai un proprietario dell'ambiente Seed.
16. Maniglia C opaca *mut u8 all'interno di un piccolo adattatore non sicuro Gli obblighi esteri di durata e validità rimangono espliciti al confine.
17. repr(C) registrare @repr("c") struct per FFI per valore L'ordine dei campi e il layout ABI fanno parte del contratto dichiarato.
18. richiamata mantenuta `box ...
19. clone prima del fan-out value.clone() davanti ai consumatori indipendenti Ogni consumatore riceve una decisione esplicita sulla proprietà.
20. risultato attraverso lo spawn spawn { fallible_work()? } e wait() I percorsi di errore e successo attraversano il limite dell'attività con la pulizia digitata.

Per un esempio diagnostico funzionante, inizia con l'intervallo di consumo in S001, quindi applica il modello più piccolo sopra che ripristina un singolo proprietario. Il compilatore deve continuare a rifiutare spostamenti dinamici, fughe di regioni, callback non sicuri e operazioni di pulizia mancanti anche quando una costruzione diversa sarebbe più breve.

Esempio S001 lavorato (spostarsi tra una chiamata e un ramo)

Un nuovo utente sposta title in report durante la costruzione della struttura e quindi utilizza l'associazione spostata all'interno di un ramo successivo:

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)
    }
}

Il compilatore risolve questo problema solo dalla diagnostica: error[S001] nomina il percorso di proprietà (title), a note: ownership path declared here punta all'associazione, a note: move occurred here punta alla costruzione della struttura che consuma e la riga help: indica la direzione di riparazione (clone 'title' explicitly before moving). La correzione più piccola clona il titolo nel report e sposta l'originale al proprietario finale prima del ramo, in modo che ogni ramo esca con la stessa disponibilità di risorse:

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 })
    }
}

Ogni modello riportato sopra (ed entrambe le metà di questo esempio pratico) è un programma corpus eseguibile verificato da make -C seed/compiler/llvm gate47-cookbook-test; il corpus vive in seed/compiler/llvm/tests/cookbook/.