Propiedad y seguridad de la memoria
<!-- verified-against: seed-llvm; examples: docs-examples-test -->
seed utiliza propiedad, recursos de solo movimiento, léxico loans/provenance, capacidades estructurales, limpieza determinista, linear ty regiones. No tiene GC, sintaxis de por vida del verificador de préstamos ni desenredado de excepciones. Las reglas semánticas autorizadas están en seed/compiler/llvm/SEMANTIC_RULES.md; Los límites actuales se rastrean en Estado actual.
Copiar, mover, pedir prestado, clonar
| Operación | Significado |
|---|---|
| copiar | el origen y el destino permanecen disponibles; sólo para valores con capacidad de copia |
| moverse | transferencias de propiedad y el lugar de origen deja de estar disponible |
| pedir prestado | una vista sin copia está vinculada a un almacenamiento de respaldo probado |
| clonar | independiente explícita value/handle según capacidad estructural |
Escalares y agregados que contienen solo copiar campos. string, bytes, shared_bytes, propietarios de usuarios-destructores, agregados de recursos y linear t obedecer a sus move/clone/cleanup capacidades.
shared_bytes no se puede copiar bit a bit: .clone() conserva explícitamente otro identificador sin copiar la carga útil. Se pueden asignar clonaciones de string/bytes de propiedad o agregados que los contengan. Los efectos y capacidades de asignación de clones sobreviven a la compilación .sdi sin fuente.
Lugares y propiedad parcial
El compilador rastrea locales, campos y campos anidados, índices de matriz fija conocidos, cargas útiles ADT activas, campos de unión sin formato, tuple/array/nominal agregados, instancias genéricas, option/result, valores de función, channels/tasks, alias, bloques, llamadas, devoluciones y uniones de flujo de control.
Un traslado parcial hace que el lugar trasladado no esté disponible e impide el uso total del propietario cuando se requiere integridad. Los campos restantes independientes siguen siendo utilizables cuando un destructor de usuario no requiere un propietario completo. Los movimientos de índice dinámico de elementos de recursos siguen siendo rechazados porque el conjunto de limpieza dependería del tiempo de ejecución.
limpieza
La limpieza se planifica a partir de los datos de propiedad de HIR escritos, se verifican por separado y se bajan a las salidas estructuradas normales. Los propietarios en vivo limpian exactamente una vez en el orden inverso de inicialización exitosa. La reasignación limpia al propietario vivo anterior antes de almacenar el reemplazo. Los bucles utilizan puntos fijos de flujo de datos en la entrada, los bordes posteriores, la interrupción, la continuación y el retorno.
El pánico, la trampa y el aborto terminan sin relajarse. No ejecutan limpieza léxica ni destructores de usuarios.
Destructores
fn TypeName.drop() recibe el consumo implícito self. Debe ser único y devolver void. Llamar a value.drop() manualmente consume al propietario, lo que evita una segunda llamada automática. Los propietarios que regresan transfieren su obligación de limpieza a la persona que llama. Los valores globales Cleanup/resource se rechazan porque el desmontaje global no es un servicio de lenguaje implícito.
La limpieza de ADT se envía solo sobre la variante activa. Los sindicatos crudos requieren una disciplina insegura en el campo activo; Las alternativas etiquetadas seguras deben utilizar ADT o un contenedor auditado.
Propiedad de cierre
Un cierre simple |...| posee almacenamiento de captura de pila generado por el compilador. La inicialización de la captura sigue el primer orden de uso léxico: los valores con capacidad de copia se copian y otros valores propios se mueven. Las capturas son inmutables; una captura de recursos movidos hace que el cierre en sí sea de solo movimiento. El propietario coloca capturas en vivo exactamente una vez en orden de inicialización inverso.
Los cierres simples son reutilizables y no se escapan. No pueden capturar valores que no pueden permanecer almacenados de forma segura después de una llamada, incluidas vistas, capturas mutables, valores vinculados a regiones, raw/reference valores, format_args, o lugares ya movidos. No se pueden devolver, retener en un ámbito externo, enviar a una tarea ni pasar a través de un límite de puntero de función de plataforma.
box |...| asigna almacenamiento de captura explícitamente y devuelve Result<box fn(...) -> T, alloc_error>. El propietario en caja se puede devolver o almacenar, pero sigue siendo de solo movimiento, no se puede clonar ni enviar. El error de asignación elimina las capturas transferidas una vez; La caída exitosa ejecuta el procesador de captura y libera el entorno una vez. Un cierre de caja asignado en region(...) no puede escapar de esa región.
Valores lineales
linear t conservas t's representación y agrega una obligación exactamente una vez. Se quita copy/clone/shared capacidad mientras conserva datos válidos de movimiento, préstamo, envío, sincronización y limpieza. La implementación propaga la obligación a través de alias, bloques, agregados, option/result, ADT, genéricos, canales, valores de funciones y llamadas indirectas, captura de tareas, parameters/returns y esquema-17 .sdi.
Se rechaza dejar un valor lineal activo al salir del alcance, consumirlo dos veces, clonarlo, descartarlo, sobrescribirlo o almacenarlo en un almacenamiento no lineal preexistente.
Texto, buffers y vistas
- el texto literal es estático prestado
str; stringes propiedad de UTF-8 válido y se mueve;byteses un propietario binario mutable único y se mueve;shared_byteses un identificador compartido de solo lectura con clonación explícita;- El
stringde propiedad se toma prestado automáticamente comostrcuando se espera; - derivado del propietario
str,[]t, ymut []tlas vistas llevan procedencia; - una vista en vivo evita movimientos conflictivos, sobrescrituras, liberaciones, reasignaciones o mutación de su propietario de respaldo; una escritura se verifica únicamente con préstamos extranjeros, por lo que escribir a través de la vista exclusiva en vivo propietaria del lugar (o a través de una partición hermana del mismo grupo de partición) es la propia autoridad de esa vista, mientras que cualquier otro préstamo activo superpuesto hace que la escritura sea ilegal incluso cuando el elemento escrito es Copia;
mut []t.split_at_mut(index)compruebaindex <= len, no asigna nada y devuelve dos vistas léxicas exclusivas; Las fijacionesmatchpueden usarmut name;- el propietario del respaldo no se puede leer, mover, cambiar de tamaño ni liberar mientras el la tupla de partición está activa; las particiones anidadas permanecen confinadas a su padre;
- devolver o almacenar una vista solo está permitido cuando es static/input/receiver la procedencia demuestra una vida útil suficiente;
- la selección condicional conlleva la unión finita de todos los orígenes posibles;
la implementación limita este conjunto en ocho orígenes y convierte el desbordamiento o la procedencia indirecta no respaldada en
unknown, nunca en una prueba de por vida; - Los resúmenes de llamadas directas
.sdipreservan estos orígenes simbólicos en todos los módulos; El esquema de caché HIR 9 almacena los mismos registros independientes del objetivo, mientras que cada familia de seguridad se vuelve a ejecutar después de la reutilización de la caché; - local/temporary-derived Las opiniones no pueden escapar a través de retornos, globales, contenedores, tareas o biblioteca dinámica ABI.
No existe una sintaxis de por vida. @borrow los métodos receptores crean préstamos léxicos compartidos; Las vistas devueltas permanecen vinculadas al receptor a través de .sdi resúmenes. Las funciones gratuitas también pueden devolver datos estáticos o derivados de entradas. str/vistas de corte; una visión derivada del propietario desde un local string/bytes sigue siendo un V001 escapar. @mut Los métodos del receptor crean un préstamo exclusivo con alcance de llamada y mutan el lugar de la persona que llama directamente. Se rechazan los receptores inmutables y las vistas en vivo superpuestas; La llamada no oculta ningún movimiento o clonación.
Regiones
fn work() -> Result<i64, alloc_error> {
region(4096) {
// allocating operations use the active checked bump allocator
Ok(42)
}
}
La capacidad se evalúa una vez, se verifica el desbordamiento de la asignación alineada y el agotamiento se devuelve como alloc_error por API falibles. Las regiones anidadas son LIFO. Los recursos del cuerpo se limpian antes de que se libere el almacenamiento de respaldo. Los propietarios, vistas y punteros seguros vinculados a una región no pueden escapar mediante resultados, retornos o asignaciones a un enlace externo.
El trabajo generado captura el contexto del asignador. Las tareas vinculadas a la región permanecen estructuradas y se unen antes de la limpieza de la región.
transferencia de tareas
spawn registros copy/move modo de captura. Mantener el valor original y el de una tarea requiere una clonación explícita. Cruzar el límite de una tarea requiere send; El acceso compartido también requiere sync. El estado global mutable no se puede capturar. Cada grupo de tareas drena los recursos antes de que su propietario léxico los limpie.
if let y while let son formas superficiales sobre match: ambos se desazucarán en un partido de dos brazos cuyo segundo brazo es el complemento, por lo que la vinculación, la transferencia de propiedad, la exhaustividad y la limpieza son exactamente las reglas del partido. if let Some(x) = e nombra su complemento None; otro patrón variante mantiene un brazo comodín, que se rechaza cuando el examinado lleva una carga útil de solo movimiento porque un brazo comodín no completa la obligación de propiedad de esa carga útil: escriba el explícito match brazos allí. Un patrón irrefutable hace que el brazo del complemento sea inalcanzable y es rechazado. Un literal de matriz repetida [value; count] se expande a count elementos de un valor literal y toma su tipo de elemento del tipo de matriz esperado, por lo que let slots: [4]usize = [0; 4] no necesita yeso; el valor debe ser un literal, una conversión de un literal o una matriz de ellos, y el recuento es un literal entero positivo de como máximo 65536.
Límite inseguro
La asignación sin formato, el acceso al puntero, FFI, las llamadas al sistema, los atómicos, el acceso volátil, las conversiones de espacio de direcciones, el acceso a la unión sin formato y el ensamblaje sin formato requieren unsafe. El código inseguro no desactiva la propiedad de los valores seguros circundantes; asume obligaciones específicas que deberían ocultarse detrás de estrechos envoltorios seguros y auditados.
unsafe struct parámetros: un unsafe struct con un campo de puntero sin formato y @repr("null_niche") es un tipo de recurso que no se puede pasar por valor como parámetro de función. Pasarlo por valor mueve al propietario subyacente, lo que hace que no esté disponible para su uso posterior. Métodos con @mut El receptor requiere un lugar mutable y no se puede invocar con un valor movido. Para utilizar un tipo de este tipo a través de los límites de la función, inserte la lógica o devuelva el propietario junto con el resultado.
¿Qué versión cambia?
--release y --release-small optimizan la misma semántica segura. Los controles de límites, discriminación, procedencia, propiedad y limpieza permanecen a menos que se demuestre que son redundantes. No se implementa ningún perfil de versión no verificado.
Libro de cocina de propiedad
Estos son los veinte patrones de construcción recurrentes utilizados por el Grove y los dispositivos del compilador. La parte importante de cada patrón es la invariante que conserva; cuando un valor no puede satisfacer esa invariante, mantenga la operación en un adaptador estrecho unsafe en lugar de debilitar el modelo propietario.
| Patrón | Construcción de semillas | Invariante preservada |
|---|---|---|
| 1. constructor propio | stringbuilder's string_builder_new(capacity) entonces falible to_string() |
El constructor posee bytes inicializados y publica sólo los válidos. UTF-8. |
| 2. vista del analizador prestado | input.view() o str.slice(...) |
El análisis toma prestado almacenamiento estable y lo asigna solo para los resultados propios. |
| 3. arena más vistas | region(capacity) { ... } |
Los valores y vistas vinculados a la región no pueden escapar de la región. |
| 4. búfer de bytes acotado | bytes.zeroed(n)? más una vista mutable |
La longitud comprometida nunca incluye almacenamiento no inicializado. |
| 5. llenado de capacidad sobrante | auditado spare_capacity() más commit_initialized() |
El productor inicializa exactamente el prefijo que confirma. |
| 6. conversión falible | view.to_string()? / view.to_bytes()? |
El error de asignación es un resultado escrito y la limpieza permanece estructurada. |
| 7. carga útil compartida de solo lectura | bytes.to_shared()? luego explícito .clone() |
Compartir conserva un control; La mutación de la carga útil es imposible. |
| 8. mutación disjunta | split_at_mut(index) |
El propietario queda prestado exclusivamente a través de dos vistas no superpuestas. |
| 9. acceso a la colección indexada | comprobado values[index] |
Se verifican los límites; Los movimientos dinámicos de recursos siguen siendo rechazados. |
| 10. proyección estática de recursos | un índice o campo de matriz conocido | La limpieza puede nombrar el elemento movido exacto. |
| 11. entrada de mapa de solo movimiento | HashMap<K,V> con CollectionKey |
Key/value la propiedad se transfiere exactamente una vez mediante eliminación. |
| 12. transferencia de canal | mover un punto final o carga útil a spawn |
Los valores de tareas cruzadas satisfacen send; el acceso compartido también requiere sync. |
| 13. resultado de la tarea escrita | task_result_new, complete, luego espere |
El identificador de resultados y la carga útil tienen un propietario léxico. |
| 14. cancelación estructurada | cancelación de tarea más un límite de unión | La cancelación no omite la unión o limpieza de tareas. |
| 15. C devolución de llamada | nombrado fn(...) -> ... en un extern "c" límite |
El lado C recibe un puntero de código delgado, nunca un propietario del entorno Seed. |
| 16. Mango opaco | *mut u8 dentro de un pequeño adaptador inseguro |
Las obligaciones extranjeras de vida y validez permanecen explícitas en la frontera. |
17. repr(C) grabar |
@repr("c") struct para FFI por valor |
El orden de campo y el diseño ABI son parte del contrato declarado. |
| 18. devolución de llamada retenida | `box | ... |
| 19. clonar antes de desplegarlo | value.clone() ante consumidores independientes |
Cada consumidor recibe una decisión de propiedad explícita. |
| 20. resultado en generación | spawn { fallible_work()? } y wait() |
Las rutas de error y éxito cruzan los límites de la tarea con la limpieza escrita. |
Para ver un ejemplo de diagnóstico resuelto, comience con el intervalo de consumo en S001, luego aplique el patrón más pequeño anterior que restaura a un solo propietario. El compilador debe continuar rechazando movimientos dinámicos, escapes de regiones, devoluciones de llamadas inseguras y limpiezas faltantes incluso cuando una construcción diferente sería más corta.
Ejemplo trabajado de S001 (moverse a través de una llamada y una rama)
Un nuevo usuario mueve title a report en la construcción de la estructura y luego consume el enlace movido dentro de una rama 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)
}
}
El compilador resuelve esto solo a partir del diagnóstico: error[S001] nombra la ruta de propiedad (title), un note: ownership path declared here apunta al enlace, un note: move occurred here apunta a la construcción de la estructura consumidora y la línea help: indica la dirección de reparación (clone 'title' explicitly before moving). La solución más pequeña clona el título en el informe y mueve el original a su propietario final antes de la sucursal, por lo que cada sucursal sale con la misma disponibilidad 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 patrón anterior (y ambas mitades de este ejemplo trabajado) es un programa de corpus ejecutable verificado por make -C seed/compiler/llvm gate47-cookbook-test; el corpus vive en seed/compiler/llvm/tests/cookbook/.