Oxc’s arena allocator is a cornerstone of its performance. By using bump-based arena allocation instead of traditional heap allocation, Oxc achieves 10-50% performance improvements in parsing and analysis operations.
Arena allocation (also called region-based memory management) allocates objects in a contiguous memory region called an “arena.” Instead of freeing objects individually, all memory is released at once when the arena is dropped.
Think of it like a notepad: you write notes from top to bottom (bump allocation), and when you’re done with all the notes, you tear out the whole page at once instead of erasing individual notes.
use oxc_allocator::{Allocator, Box};let allocator = Allocator::default();// Allocate a single valuelet boxed: Box<i32> = Box::new_in(42, &allocator);assert_eq!(*boxed, 42);// Box implements Deref, so you can use it like a referencelet value: &i32 = &boxed;
Box does NOT implement Drop. Objects are never individually dropped - memory is released when the allocator is dropped. You cannot allocate Drop types into the arena.
All arena-allocated objects have a lifetime 'a tied to the allocator:
let allocator = Allocator::default();// AST nodes have lifetime 'a tied to the allocatorlet program: Program<'a> = parser.parse().program;// This won't compile - can't use AST after allocator is dropped:// drop(allocator);// println!("{:?}", program); // ❌ Compile error!
Rust’s lifetime system ensures memory safety. You cannot accidentally use arena-allocated data after the arena is freed.
let source_text = "const x = 42;";let allocator = Allocator::default();let ret = Parser::new(&allocator, source_text, source_type).parse();// Identifier names are string slices, not copies!// They point directly into the original source_textfor stmt in ret.program.body { if let Statement::VariableDeclaration(decl) = stmt { for declarator in &decl.declarations { // This is a &str slice into source_text - zero allocation! let name: &str = declarator.id.name.as_str(); } }}
// Multiple AST nodes can reference the same data without copyinglet allocator = Allocator::default();let common_type = /* some TypeScript type */;// These variables can all share references to common_typelet var1_type: &Type = common_type;let var2_type: &Type = common_type;let var3_type: &Type = common_type;// No cloning or copying needed!
let mut allocator = Allocator::new();for source_file in source_files { let source_text = read_file(source_file)?; // Parse using the allocator let ret = Parser::new(&allocator, &source_text, source_type).parse(); // Process the AST process_ast(&ret.program); // Reset for next iteration - keeps largest chunk, rewinds cursor allocator.reset();}
// DON'T DO THIS!for source_file in source_files { let allocator = Allocator::new(); // ❌ Expensive allocation! let source_text = read_file(source_file)?; let ret = Parser::new(&allocator, &source_text, source_type).parse(); process_ast(&ret.program); // ❌ Expensive deallocation when allocator drops}
use oxc_allocator::AllocatorPool;use rayon::prelude::*;// Create a pool of allocators for parallel processinglet pool = AllocatorPool::new();source_files.par_iter().for_each(|source_file| { // Get an allocator from the pool let allocator = pool.allocator(); let source_text = read_file(source_file).unwrap(); let ret = Parser::new(&allocator, &source_text, source_type).parse(); process_ast(&ret.program); // Allocator is automatically returned to pool when dropped});
// Build a vector of results in the arenalet mut statements = Vec::new_in(&allocator);for item in items { let stmt = create_statement(item, &allocator); statements.push(stmt);}// Return the collected statements (zero-copy)return statements;
Cannot allocate Drop types: Types that implement Drop cannot be allocated in the arena because they won’t be dropped when the arena is freed. This is enforced at compile time.