bkolobara labeled Issue #2583:
Hi, I'm using
wasmtime = 0.21and run into a bug where theMemoryis never dropped if moved inside a closure passed toLinker::func.Here is a minimal example:
use wasmtime::*; struct MyMemory {} unsafe impl LinearMemory for MyMemory { fn size(&self) -> u32 { 0 } fn grow(&self, _delta: u32) -> Option<u32> { None } fn as_ptr(&self) -> *mut u8 { std::ptr::null_mut() } } impl Drop for MyMemory { fn drop(&mut self) { println!("Dropped!") } } struct MyMemoryCreator {} unsafe impl MemoryCreator for MyMemoryCreator { fn new_memory( &self, _ty: MemoryType, _reserved_size_in_bytes: Option<u64>, _guard_size_in_bytes: u64, ) -> Result<Box<dyn LinearMemory>, String> { Ok(Box::new(MyMemory {})) } } fn main() { let mut config = Config::new(); config.with_host_memory(std::sync::Arc::new(MyMemoryCreator {})); let engine = Engine::new(&config); let store = Store::new(&engine); let mut linker = Linker::new(&store); let memory_ty = MemoryType::new(Limits::new(1, None)); let memory = Memory::new(&store, memory_ty); let captured_memory = memory.clone(); linker .func("", "", move || { let _ = captured_memory; }) .unwrap(); }
MyMemoryandMyMemoryCreatorare added here only for the purpose of printing out on drop, but the same behaviour is observed with the defaultMemory.In this example "Dropped!" is never printed out, but if I comment out
let _ = captured_memory;it is. I assume there is somewhere a cycle in reference counting.I'm also aware that the instance memory can be accessed through the first argument (
Caller), but because of some other design decisions I prefer capturing some memories this way. Would this be considered a bug or aMemoryshould never be captured like this?
bkolobara opened Issue #2583:
Hi, I'm using
wasmtime = 0.21and run into a bug where theMemoryis never dropped if moved inside a closure passed toLinker::func.Here is a minimal example:
use wasmtime::*; struct MyMemory {} unsafe impl LinearMemory for MyMemory { fn size(&self) -> u32 { 0 } fn grow(&self, _delta: u32) -> Option<u32> { None } fn as_ptr(&self) -> *mut u8 { std::ptr::null_mut() } } impl Drop for MyMemory { fn drop(&mut self) { println!("Dropped!") } } struct MyMemoryCreator {} unsafe impl MemoryCreator for MyMemoryCreator { fn new_memory( &self, _ty: MemoryType, _reserved_size_in_bytes: Option<u64>, _guard_size_in_bytes: u64, ) -> Result<Box<dyn LinearMemory>, String> { Ok(Box::new(MyMemory {})) } } fn main() { let mut config = Config::new(); config.with_host_memory(std::sync::Arc::new(MyMemoryCreator {})); let engine = Engine::new(&config); let store = Store::new(&engine); let mut linker = Linker::new(&store); let memory_ty = MemoryType::new(Limits::new(1, None)); let memory = Memory::new(&store, memory_ty); let captured_memory = memory.clone(); linker .func("", "", move || { let _ = captured_memory; }) .unwrap(); }
MyMemoryandMyMemoryCreatorare added here only for the purpose of printing out on drop, but the same behaviour is observed with the defaultMemory.In this example "Dropped!" is never printed out, but if I comment out
let _ = captured_memory;it is. I assume there is somewhere a cycle in reference counting.I'm also aware that the instance memory can be accessed through the first argument (
Caller), but because of some other design decisions I prefer capturing some memories this way. Would this be considered a bug or aMemoryshould never be captured like this?
alexcrichton commented on Issue #2583:
This is unfortunately expected behavior right now, albeit one we may want to investigate fixing with additional APIs. The problem is that you're creating a cycle of
Rc(StoreownsFuncdata which ownsMemorywhich owns the sameStore) which never gets freed. This is a known drawback ofRcin Rust, but currently a design decision in Wasmtime.Ideally if you can the best fix would be to use the
Callerstructure which handles this correctly. Otherwise we may need to provide "weak" forms of things likeMemoryso you can close over the weak verisons without inducing a reference cycle.
Last updated: Dec 13 2025 at 21:03 UTC