From 83172eff7975db9740525667a6f6e33631f69e7b Mon Sep 17 00:00:00 2001 From: Joel Alvarez Date: Wed, 29 Jul 2026 02:06:07 -0700 Subject: [PATCH] feat: add safe generational arena --- .../2026-07-29-rust-performance-course.md | 2 +- src/arena.rs | 65 +++++++++++++++++++ src/lib.rs | 1 + tests/safe_arena.rs | 20 ++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/arena.rs create mode 100644 tests/safe_arena.rs diff --git a/docs/superpowers/plans/2026-07-29-rust-performance-course.md b/docs/superpowers/plans/2026-07-29-rust-performance-course.md index 82f1069..3484842 100644 --- a/docs/superpowers/plans/2026-07-29-rust-performance-course.md +++ b/docs/superpowers/plans/2026-07-29-rust-performance-course.md @@ -128,7 +128,7 @@ su dependencia. ### Capítulo 07: arenas seguras y ciclos de vida - [x] #21 Especificar arena segura, capacidad, reset y alternativas. -- [ ] #22 Implementar y probar una arena educativa sin `unsafe`. +- [x] #22 Implementar y probar una arena educativa sin `unsafe`. - [ ] #23 Escribir capítulo, diagrama, ejemplos, ejercicios y benchmarks. ## Milestone 3: Movimiento de datos y SIMD diff --git a/src/arena.rs b/src/arena.rs new file mode 100644 index 0000000..ff859b1 --- /dev/null +++ b/src/arena.rs @@ -0,0 +1,65 @@ +//! Arena educativa segura basada en índices y generaciones. + +/// Identificador válido únicamente dentro de una generación de arena. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ArenaId { + index: usize, + generation: u64, +} + +/// Colección de valores con invalidación explícita mediante `reset`. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SafeArena { + values: Vec, + generation: u64, +} + +impl SafeArena { + /// Construye una arena sin capacidad reservada. + #[must_use] + pub const fn new() -> Self { + Self { + values: Vec::new(), + generation: 0, + } + } + + /// Construye una arena con capacidad inicial explícita. + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self { + values: Vec::with_capacity(capacity), + generation: 0, + } + } + + /// Inserta un valor y devuelve un identificador de la generación actual. + pub fn insert(&mut self, value: T) -> ArenaId { + let id = ArenaId { + index: self.values.len(), + generation: self.generation, + }; + self.values.push(value); + id + } + + /// Obtiene un valor si su identificador pertenece a la generación actual. + #[must_use] + pub fn get(&self, id: ArenaId) -> Option<&T> { + (id.generation == self.generation) + .then(|| self.values.get(id.index)) + .flatten() + } + + /// Elimina todos los valores e invalida los identificadores anteriores. + pub fn reset(&mut self) { + self.values.clear(); + self.generation = self.generation.wrapping_add(1); + } +} + +impl Default for SafeArena { + fn default() -> Self { + Self::new() + } +} diff --git a/src/lib.rs b/src/lib.rs index 2b4a5e5..4927bbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ #![forbid(unsafe_code)] pub mod allocation; +pub mod arena; pub mod benchmark; pub mod layout; pub mod locality; diff --git a/tests/safe_arena.rs b/tests/safe_arena.rs new file mode 100644 index 0000000..0748f0b --- /dev/null +++ b/tests/safe_arena.rs @@ -0,0 +1,20 @@ +use rust_performance::arena::SafeArena; + +#[test] +fn retrieves_values_inserted_in_the_current_generation() { + let mut arena = SafeArena::with_capacity(2); + let id = arena.insert("valor"); + + assert_eq!(arena.get(id), Some(&"valor")); +} + +#[test] +fn reset_invalidates_previous_identifiers() { + let mut arena = SafeArena::new(); + let old = arena.insert(7); + arena.reset(); + let current = arena.insert(9); + + assert_eq!(arena.get(old), None); + assert_eq!(arena.get(current), Some(&9)); +}