From 16a9bd95717f2548796a82ad8b5aa9615608207f Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Wed, 27 May 2026 00:28:27 +0200 Subject: [PATCH 1/6] Resource allocation override option --- crates/rust/src/interface.rs | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index c81c3271e..193789331 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -337,6 +337,30 @@ macro_rules! {macro_name} {{ resource_traits: impl Iterator, ) { uwriteln!(self.src, "pub trait {trait_name} {{"); + let box_path = self.path_to_box(); + uwriteln!( + self.src, + r#"#[doc(hidden)] +/// Place the value on the heap or in an arena, return the raw pointer. +/// Override for custom resource allocators. +fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized +{{ + {box_path}::into_raw({box_path}::new(val)) +}} + +#[doc(hidden)] +/// Consumes the value from the handle, handle is invalid afterwards. +/// +/// # Safety +/// +/// See Box::from_raw +unsafe fn _resource_from_raw(handle: *mut Option) -> Option where Self: Sized +{{ + *unsafe {{ {box_path}::from_raw(handle) }} +}} + + "# + ); for (id, trait_name) in resource_traits { let name = self.resolve.types[id] .name @@ -2716,7 +2740,6 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { Identifier::World(_) => unimplemented!("resource exports from worlds"), Identifier::StreamOrFuturePayload => unreachable!(), }; - let box_path = self.path_to_box(); uwriteln!( self.src, r#" @@ -2737,8 +2760,7 @@ impl {camel} {{ pub fn new(val: T) -> Self {{ Self::type_guard::(); let val: _{camel}Rep = Some(val); - let ptr: *mut _{camel}Rep = - {box_path}::into_raw({box_path}::new(val)); + let ptr: *mut _{camel}Rep = T::_resource_into_raw(val); unsafe {{ Self::from_handle(T::_resource_new(ptr.cast())) }} @@ -2797,9 +2819,9 @@ impl {camel} {{ }} #[doc(hidden)] - pub unsafe fn dtor(handle: *mut u8) {{ + pub unsafe fn dtor(handle: *mut u8) {{ Self::type_guard::(); - let _ = unsafe {{ {box_path}::from_raw(handle as *mut _{camel}Rep) }}; + let _ = unsafe {{ T::_resource_from_raw(handle as *mut _{camel}Rep) }}; }} fn as_ptr(&self) -> *mut _{camel}Rep {{ From e9cc4e3eb75a79c26d8fb676227707a9740d5119 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sat, 6 Jun 2026 22:48:45 +0200 Subject: [PATCH 2/6] custom resource allocation --- crates/rust/src/interface.rs | 48 +++---- .../rust/arena-allocated-resources/runner.rs | 16 +++ .../rust/arena-allocated-resources/test.rs | 127 ++++++++++++++++++ .../rust/arena-allocated-resources/test.wit | 18 +++ 4 files changed, 185 insertions(+), 24 deletions(-) create mode 100644 tests/runtime/rust/arena-allocated-resources/runner.rs create mode 100644 tests/runtime/rust/arena-allocated-resources/test.rs create mode 100644 tests/runtime/rust/arena-allocated-resources/test.wit diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 193789331..81fb71881 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -239,6 +239,30 @@ fn _resource_rep(handle: u32) -> *mut u8 "# ); + let box_path = self.path_to_box(); + uwriteln!( + self.src, + r#"#[doc(hidden)] +/// Place the value on the heap or in an arena, return the raw pointer. +/// Override for custom resource allocators. +fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized +{{ + {box_path}::into_raw({box_path}::new(val)) +}} + +#[doc(hidden)] +/// Consumes the value from the handle, handle is invalid afterwards. +/// +/// # Safety +/// +/// See Box::from_raw +unsafe fn _resource_from_raw(handle: *mut Option) -> Option where Self: Sized +{{ + *unsafe {{ {box_path}::from_raw(handle) }} +}} + + "# + ); for method in methods { self.src.push_str(method); } @@ -337,30 +361,6 @@ macro_rules! {macro_name} {{ resource_traits: impl Iterator, ) { uwriteln!(self.src, "pub trait {trait_name} {{"); - let box_path = self.path_to_box(); - uwriteln!( - self.src, - r#"#[doc(hidden)] -/// Place the value on the heap or in an arena, return the raw pointer. -/// Override for custom resource allocators. -fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized -{{ - {box_path}::into_raw({box_path}::new(val)) -}} - -#[doc(hidden)] -/// Consumes the value from the handle, handle is invalid afterwards. -/// -/// # Safety -/// -/// See Box::from_raw -unsafe fn _resource_from_raw(handle: *mut Option) -> Option where Self: Sized -{{ - *unsafe {{ {box_path}::from_raw(handle) }} -}} - - "# - ); for (id, trait_name) in resource_traits { let name = self.resolve.types[id] .name diff --git a/tests/runtime/rust/arena-allocated-resources/runner.rs b/tests/runtime/rust/arena-allocated-resources/runner.rs new file mode 100644 index 000000000..665a07632 --- /dev/null +++ b/tests/runtime/rust/arena-allocated-resources/runner.rs @@ -0,0 +1,16 @@ +include!(env!("BINDINGS")); + +use crate::test::arena_allocated_resources::to_test::Thing; + +struct Component; + +export!(Component); + +impl Guest for Component { + fn run() { + let thing1 = Thing::new(3); + let thing2 = Thing::new(5); + assert_eq!(3, thing1.get()); + assert_eq!(5, thing2.get()); + } +} diff --git a/tests/runtime/rust/arena-allocated-resources/test.rs b/tests/runtime/rust/arena-allocated-resources/test.rs new file mode 100644 index 000000000..cc746d697 --- /dev/null +++ b/tests/runtime/rust/arena-allocated-resources/test.rs @@ -0,0 +1,127 @@ +include!(env!("BINDINGS")); + +use crate::exports::test::arena_allocated_resources::to_test::{Guest, GuestThing}; + +export!(Component); + +struct Component; + +impl Guest for Component { + type Thing = MyThing; +} + +mod arena { + + use core::{cell::UnsafeCell, mem::MaybeUninit}; + + /// A simple no_std arena allocator for fixed-size allocations. + /// + /// The arena allocates items of type T sequentially from a pre-allocated buffer + /// and does not support individual deallocation. Memory is reclaimed + /// only when the entire arena is reset. + pub struct Arena { + buffer: [MaybeUninit; SIZE], + offset: usize, + } + + impl Arena { + /// Allocates space for a single item of type T. + /// Returns a mutable reference to the allocated memory, or None if there's insufficient space. + pub fn alloc_one(&mut self) -> Option<&mut T> { + if self.offset < SIZE { + let ptr = self.buffer[self.offset].as_mut_ptr(); + self.offset += 1; + Some(unsafe { &mut *ptr }) + } else { + None + } + } + } + + /// A static-safe wrapper for Arena that uses interior mutability. + /// + /// This allows an Arena to be stored in a static variable and accessed safely + /// in single-threaded contexts without requiring std or alloc. + /// + /// # Safety + /// + /// This type is safe to use in single-threaded environments. In multi-threaded + /// contexts, external synchronization is required. + pub struct StaticArena { + arena: UnsafeCell>, + } + + // SAFETY: StaticArena is Sync because we enforce single-threaded access through + // the API. It can be safely shared across threads as long as only one thread + // accesses it at a time (which is the responsibility of the user). + unsafe impl Sync for StaticArena where T: Sync {} + + // SAFETY: StaticArena is Send because the underlying Arena can be moved between + // threads, and T itself must be Send. + unsafe impl Send for StaticArena where T: Send {} + + impl StaticArena { + /// Creates a new static arena. + pub const fn new() -> Self { + StaticArena { + arena: UnsafeCell::new(Arena { + buffer: [const { MaybeUninit::uninit() }; SIZE], + offset: 0, + }), + } + } + + /// Gets mutable access to the arena. + /// + /// # Safety + /// + /// This is safe in single-threaded contexts. In multi-threaded contexts, + /// the caller must ensure exclusive access. + #[inline] + pub fn get_mut(&self) -> &mut Arena { + unsafe { &mut *self.arena.get() } + } + + /// Allocates a single item. + pub fn alloc_one(&self) -> Option<&mut T> { + self.get_mut().alloc_one() + } + } +} + +use arena::StaticArena; + +#[derive(Clone)] +struct MyThing { + contents: u32, +} + +static ARENA: StaticArena, 4> = StaticArena::new(); + +impl GuestThing for MyThing { + fn new(v: u32) -> MyThing { + MyThing { contents: v } + } + fn get(&self) -> u32 { + self.contents + } + fn _resource_into_raw(val: Option) -> *mut Option + where + Self: Sized, + { + val.and_then(|v| { + ARENA.alloc_one().map(|x| { + *x = Some(v); + x as *mut _ + }) + }) + .unwrap_or(core::ptr::null_mut()) + } + unsafe fn _resource_from_raw(handle: *mut Option) -> Option + where + Self: Sized, + { + let res = unsafe { &mut *handle }.take(); + res + } +} diff --git a/tests/runtime/rust/arena-allocated-resources/test.wit b/tests/runtime/rust/arena-allocated-resources/test.wit new file mode 100644 index 000000000..9a2ffc934 --- /dev/null +++ b/tests/runtime/rust/arena-allocated-resources/test.wit @@ -0,0 +1,18 @@ +package test:arena-allocated-resources; + +interface to-test { + resource thing { + constructor(v: u32); + get: func() -> u32; + } +} + +world test { + export to-test; +} + +world runner { + import to-test; + + export run: func(); +} From 4263a8be6b52855231de43e2d5f34cfbcf7eda95 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sun, 14 Jun 2026 20:52:46 +0200 Subject: [PATCH 3/6] cut the dirty tricks out of the arena --- .../rust/arena-allocated-resources/test.rs | 94 +++++++------------ 1 file changed, 35 insertions(+), 59 deletions(-) diff --git a/tests/runtime/rust/arena-allocated-resources/test.rs b/tests/runtime/rust/arena-allocated-resources/test.rs index cc746d697..3e0331b07 100644 --- a/tests/runtime/rust/arena-allocated-resources/test.rs +++ b/tests/runtime/rust/arena-allocated-resources/test.rs @@ -12,6 +12,7 @@ impl Guest for Component { mod arena { + use core::sync::atomic::{AtomicUsize, Ordering}; use core::{cell::UnsafeCell, mem::MaybeUninit}; /// A simple no_std arena allocator for fixed-size allocations. @@ -20,91 +21,65 @@ mod arena { /// and does not support individual deallocation. Memory is reclaimed /// only when the entire arena is reset. pub struct Arena { - buffer: [MaybeUninit; SIZE], - offset: usize, + buffer: [UnsafeCell>; SIZE], + offset: AtomicUsize, } - impl Arena { - /// Allocates space for a single item of type T. - /// Returns a mutable reference to the allocated memory, or None if there's insufficient space. - pub fn alloc_one(&mut self) -> Option<&mut T> { - if self.offset < SIZE { - let ptr = self.buffer[self.offset].as_mut_ptr(); - self.offset += 1; - Some(unsafe { &mut *ptr }) - } else { - None - } - } - } - - /// A static-safe wrapper for Arena that uses interior mutability. - /// - /// This allows an Arena to be stored in a static variable and accessed safely - /// in single-threaded contexts without requiring std or alloc. - /// - /// # Safety - /// - /// This type is safe to use in single-threaded environments. In multi-threaded - /// contexts, external synchronization is required. - pub struct StaticArena { - arena: UnsafeCell>, - } + // Element allocation is atomic and elements are exclusively handed out after allocation, + // so the arena can be send to other threads and simultaneosly accessed by multiple threads + unsafe impl Sync for Arena {} + unsafe impl Send for Arena {} - // SAFETY: StaticArena is Sync because we enforce single-threaded access through - // the API. It can be safely shared across threads as long as only one thread - // accesses it at a time (which is the responsibility of the user). - unsafe impl Sync for StaticArena where T: Sync {} - - // SAFETY: StaticArena is Send because the underlying Arena can be moved between - // threads, and T itself must be Send. - unsafe impl Send for StaticArena where T: Send {} - - impl StaticArena { - /// Creates a new static arena. + impl Arena { pub const fn new() -> Self { - StaticArena { - arena: UnsafeCell::new(Arena { - buffer: [const { MaybeUninit::uninit() }; SIZE], - offset: 0, - }), + Self { + buffer: [const { UnsafeCell::new(MaybeUninit::uninit()) }; SIZE], + offset: AtomicUsize::new(0), } } - /// Gets mutable access to the arena. - /// - /// # Safety - /// - /// This is safe in single-threaded contexts. In multi-threaded contexts, - /// the caller must ensure exclusive access. - #[inline] - pub fn get_mut(&self) -> &mut Arena { - unsafe { &mut *self.arena.get() } - } - - /// Allocates a single item. + /// Allocates space for a single item of type T. + /// Returns a mutable reference to the allocated memory, or None if there's insufficient space. pub fn alloc_one(&self) -> Option<&mut T> { - self.get_mut().alloc_one() + // short circuit the exhausted state (don't increment if full) + if self.offset.load(Ordering::Relaxed) >= SIZE { + None + } else { + // now try to allocate for real + let pos = self.offset.fetch_add(1, Ordering::Acquire); + if pos >= SIZE { + // now self.offset is already beyond SIZE, reduce our increment and return none + self.offset.fetch_sub(1, Ordering::Release); + None + } else { + let ptr = self.buffer[pos].get(); + // SAFETY: we demand exclusive ownership of the item in the arena + let uninit = unsafe { &mut *ptr }; + Some(uninit.write(Default::default())) + } + } } } } -use arena::StaticArena; +use arena::Arena; #[derive(Clone)] struct MyThing { contents: u32, } -static ARENA: StaticArena, 4> = StaticArena::new(); +static ARENA: Arena, 4> = Arena::new(); impl GuestThing for MyThing { fn new(v: u32) -> MyThing { MyThing { contents: v } } + fn get(&self) -> u32 { self.contents } + fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized, @@ -117,6 +92,7 @@ impl GuestThing for MyThing { }) .unwrap_or(core::ptr::null_mut()) } + unsafe fn _resource_from_raw(handle: *mut Option) -> Option where Self: Sized, From 582cc1164a228ce2d920cf2fdacd3341e9476712 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Mon, 15 Jun 2026 00:53:31 +0200 Subject: [PATCH 4/6] hide that it is an Option (typedef), put underscore to the end --- crates/rust/src/interface.rs | 16 +++++++++++----- .../rust/arena-allocated-resources/test.rs | 6 +++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 81fb71881..630112262 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -240,12 +240,16 @@ fn _resource_rep(handle: u32) -> *mut u8 "# ); let box_path = self.path_to_box(); + let camel = resource_name.to_pascal_case(); uwriteln!( self.src, r#"#[doc(hidden)] /// Place the value on the heap or in an arena, return the raw pointer. /// Override for custom resource allocators. -fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized +/// +/// The pointed object needs to live for the entire lifecycle of the resource and +/// should only be freed once via the matching resource_from_raw_ function. +fn resource_into_raw_(val: {camel}Storage) -> *mut {camel}Storage where Self: Sized {{ {box_path}::into_raw({box_path}::new(val)) }} @@ -255,8 +259,8 @@ fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized /// /// # Safety /// -/// See Box::from_raw -unsafe fn _resource_from_raw(handle: *mut Option) -> Option where Self: Sized +/// See Box::from_raw (call exactly once and only on pointers received from resource_into_raw). +unsafe fn resource_from_raw_(handle: *mut {camel}Storage) -> {camel}Storage where Self: Sized {{ *unsafe {{ {box_path}::from_raw(handle) }} }} @@ -2750,6 +2754,8 @@ pub struct {camel} {{ }} type _{camel}Rep = Option; +/// Data type for arena allocation of resources +pub type {camel}Storage = Option; impl {camel} {{ /// Creates a new resource from the specified representation. @@ -2760,7 +2766,7 @@ impl {camel} {{ pub fn new(val: T) -> Self {{ Self::type_guard::(); let val: _{camel}Rep = Some(val); - let ptr: *mut _{camel}Rep = T::_resource_into_raw(val); + let ptr: *mut _{camel}Rep = T::resource_into_raw_(val); unsafe {{ Self::from_handle(T::_resource_new(ptr.cast())) }} @@ -2821,7 +2827,7 @@ impl {camel} {{ #[doc(hidden)] pub unsafe fn dtor(handle: *mut u8) {{ Self::type_guard::(); - let _ = unsafe {{ T::_resource_from_raw(handle as *mut _{camel}Rep) }}; + let _ = unsafe {{ T::resource_from_raw_(handle as *mut _{camel}Rep) }}; }} fn as_ptr(&self) -> *mut _{camel}Rep {{ diff --git a/tests/runtime/rust/arena-allocated-resources/test.rs b/tests/runtime/rust/arena-allocated-resources/test.rs index 3e0331b07..a9e7b189d 100644 --- a/tests/runtime/rust/arena-allocated-resources/test.rs +++ b/tests/runtime/rust/arena-allocated-resources/test.rs @@ -1,6 +1,6 @@ include!(env!("BINDINGS")); -use crate::exports::test::arena_allocated_resources::to_test::{Guest, GuestThing}; +use crate::exports::test::arena_allocated_resources::to_test::{Guest, GuestThing, ThingStorage}; export!(Component); @@ -80,7 +80,7 @@ impl GuestThing for MyThing { self.contents } - fn _resource_into_raw(val: Option) -> *mut Option + fn resource_into_raw_(val: ThingStorage) -> *mut ThingStorage where Self: Sized, { @@ -93,7 +93,7 @@ impl GuestThing for MyThing { .unwrap_or(core::ptr::null_mut()) } - unsafe fn _resource_from_raw(handle: *mut Option) -> Option + unsafe fn resource_from_raw_(handle: *mut ThingStorage) -> ThingStorage where Self: Sized, { From 2ae4a31ef81b36ed990194dc8e210597d8120b2a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 15 Jun 2026 14:01:35 -0700 Subject: [PATCH 5/6] Some review comments of mine and refactorings --- crates/guest-rust/src/lib.rs | 2 + crates/guest-rust/src/resource.rs | 57 +++++++++++ crates/guest-rust/src/rt/mod.rs | 2 + crates/rust/src/bindgen.rs | 4 +- crates/rust/src/interface.rs | 95 +++++++++++-------- .../rust/arena-allocated-resources/test.rs | 15 +-- 6 files changed, 126 insertions(+), 49 deletions(-) create mode 100644 crates/guest-rust/src/resource.rs diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index 3e55de02a..3b3d7f4f5 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -878,6 +878,8 @@ pub mod examples; #[doc(hidden)] pub mod rt; +pub mod resource; + #[cfg(feature = "inter-task-wakeup")] pub use rt::async_support::UnitStreamOps; #[cfg(feature = "async-spawn")] diff --git a/crates/guest-rust/src/resource.rs b/crates/guest-rust/src/resource.rs new file mode 100644 index 000000000..075502fad --- /dev/null +++ b/crates/guest-rust/src/resource.rs @@ -0,0 +1,57 @@ +//! Helper traits, types, and utilities for managing resources in the component +//! model. + +/// A trait implemented by all resources that a component might export. +/// +/// This is an implementation detail primarily for the code generated by +/// exported resources. The primary purpose of this trait is to serve as an +/// abstraction for the in-memory storage of a resource. +pub trait Resource: Sized + 'static { + /// The type which is actually stored in-memory for this resource. + /// + /// By default this is `Option`. + type Rep: ResourceRep; +} + +impl Resource for T { + type Rep = Option; +} + +/// A trait used to define how to access the underlying data `T` from an +/// in-memory representation. +/// +/// This is used as a bound on the [`Resource::Rep`] associated type which is in +/// turn used to access data within a resources. +pub unsafe trait ResourceRep { + /// Creates a new instance of `Self` which wraps the provided data. + fn rep_new(inner: T) -> Self; + + /// Acquires `&T` from a raw pointer to `Self`. + unsafe fn rep_as_ref<'a>(ptr: *const Self) -> &'a T; + + /// Acquires `&mut T` from a raw pointer to `Self`. + unsafe fn rep_as_mut<'a>(ptr: *mut Self) -> &'a mut T; + + /// Takes the value out of `Self` at the provided pointer. + /// + /// Note that `ptr` will later be deallocated meaning that it must not run + /// the destructor of `T` after this method is called. This is guaranteed to + /// be called at most once, however. Additionally after calling this method + /// it's guaranteed that the `rep_as_*` method above will not be called. + unsafe fn rep_take<'a>(ptr: *mut Self) -> T; +} + +unsafe impl ResourceRep for Option { + fn rep_new(inner: T) -> Option { + Some(inner) + } + unsafe fn rep_as_ref<'a>(ptr: *const Option) -> &'a T { + unsafe { (*ptr).as_ref().unwrap() } + } + unsafe fn rep_as_mut<'a>(ptr: *mut Option) -> &'a mut T { + unsafe { (*ptr).as_mut().unwrap() } + } + unsafe fn rep_take(ptr: *mut Option) -> T { + unsafe { (*ptr).take().unwrap() } + } +} diff --git a/crates/guest-rust/src/rt/mod.rs b/crates/guest-rust/src/rt/mod.rs index 40736f6ef..e62c52640 100644 --- a/crates/guest-rust/src/rt/mod.rs +++ b/crates/guest-rust/src/rt/mod.rs @@ -1,6 +1,8 @@ use core::alloc::Layout; use core::ptr::{self, NonNull}; +pub use crate::resource::{Resource, ResourceRep}; + // Re-export `bitflags` so that we can reference it from macros. #[cfg(feature = "bitflags")] pub use bitflags; diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index bee880167..767beb6a9 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -406,7 +406,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = if is_own { format!("{name}::from_handle({op} as u32)") } else if self.r#gen.is_exported_resource(*resource) { - format!("{name}Borrow::lift({op} as u32 as usize)") + format!( + "{name}Borrow::lift(core::ptr::with_exposed_provenance({op} as u32 as usize))" + ) } else { let tmp = format!("handle{}", self.tmp()); self.handle_decls.push(format!("let {tmp};")); diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 630112262..148249be0 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -196,9 +196,10 @@ impl<'i> InterfaceGenerator<'i> { .map(|(resource, (trait_name, ..))| (resource.unwrap(), trait_name.as_str())), ) } + let rt = self.r#gen.runtime_path().to_string(); for (resource, (trait_name, methods)) in traits.iter() { - uwriteln!(self.src, "pub trait {trait_name}: 'static {{"); + uwriteln!(self.src, "pub trait {trait_name}: {rt}::Resource {{"); let resource = resource.unwrap(); let resource_name = self.resolve.types[resource].name.as_ref().unwrap(); let (_, interface_name) = interface.unwrap(); @@ -240,27 +241,48 @@ fn _resource_rep(handle: u32) -> *mut u8 "# ); let box_path = self.path_to_box(); - let camel = resource_name.to_pascal_case(); uwriteln!( self.src, - r#"#[doc(hidden)] -/// Place the value on the heap or in an arena, return the raw pointer. -/// Override for custom resource allocators. -/// -/// The pointed object needs to live for the entire lifecycle of the resource and -/// should only be freed once via the matching resource_from_raw_ function. -fn resource_into_raw_(val: {camel}Storage) -> *mut {camel}Storage where Self: Sized + r#" +/// Place this resource's representation into a location with a stable pointer, +/// returning a pointer to that location. +/// +/// This method is used to place `val` on the heap, for example, or possibly in +/// an arena. The returned pointer must remain valid for the lifetime of this +/// resource. The default implementation of this metho will place `val` onto the +/// heap with `Box`. The returned pointer will be deallocated by the sibling +/// `resource_from_raw_` function. +/// +/// # Safety +/// +/// Note that this method is not `unsafe` to call, but it is `unsafe` to +/// define. When overriding this method you must additionally override the +/// `resource_from_raw_` to insert an appropriate deallocation for the returned +/// pointer. In the future this might become a default associated trait bound, +/// but that's not stable in Rust right now. +#[doc(hidden)] +unsafe fn resource_into_raw_(val: Self::Rep) -> *mut Self::Rep {{ {box_path}::into_raw({box_path}::new(val)) }} -#[doc(hidden)] -/// Consumes the value from the handle, handle is invalid afterwards. -/// +/// Consumes and deallocates a pointer previously returned by +/// `resource_into_raw_`. +/// +/// This function is used to deallocate resources and allocations associated +/// with the allocation routine when creating a resource. The default +/// implementation of this method will read `handle`'s value and deallocate it +/// as a `Box`. +/// +/// Note that when overriding this method you'll almost surely want to override +/// `resource_into_raw_` as well. +/// /// # Safety -/// -/// See Box::from_raw (call exactly once and only on pointers received from resource_into_raw). -unsafe fn resource_from_raw_(handle: *mut {camel}Storage) -> {camel}Storage where Self: Sized +/// +/// This method is only safe to call with pointers previously created by +/// `resource_into_raw_`. For more information see `Box::from_raw` for examples. +#[doc(hidden)] +unsafe fn resource_from_raw_(handle: *mut Self::Rep) -> Self::Rep {{ *unsafe {{ {box_path}::from_raw(handle) }} }} @@ -2744,6 +2766,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { Identifier::World(_) => unimplemented!("resource exports from worlds"), Identifier::StreamOrFuturePayload => unreachable!(), }; + let rt = self.r#gen.runtime_path(); uwriteln!( self.src, r#" @@ -2753,10 +2776,6 @@ pub struct {camel} {{ handle: {resource}<{camel}>, }} -type _{camel}Rep = Option; -/// Data type for arena allocation of resources -pub type {camel}Storage = Option; - impl {camel} {{ /// Creates a new resource from the specified representation. /// @@ -2765,30 +2784,30 @@ impl {camel} {{ /// create a handle. The owned handle is then returned as `{camel}`. pub fn new(val: T) -> Self {{ Self::type_guard::(); - let val: _{camel}Rep = Some(val); - let ptr: *mut _{camel}Rep = T::resource_into_raw_(val); + let rep = >::rep_new(val); unsafe {{ + let ptr = T::resource_into_raw_(rep); Self::from_handle(T::_resource_new(ptr.cast())) }} }} /// Gets access to the underlying `T` which represents this resource. pub fn get(&self) -> &T {{ - let ptr = unsafe {{ &*self.as_ptr::() }}; - ptr.as_ref().unwrap() + let ptr = self.as_ptr::(); + unsafe {{ >::rep_as_ref(ptr) }} }} /// Gets mutable access to the underlying `T` which represents this /// resource. pub fn get_mut(&mut self) -> &mut T {{ - let ptr = unsafe {{ &mut *self.as_ptr::() }}; - ptr.as_mut().unwrap() + let ptr = self.as_ptr::(); + unsafe {{ >::rep_as_mut(ptr) }} }} /// Consumes this resource and returns the underlying `T`. pub fn into_inner(self) -> T {{ - let ptr = unsafe {{ &mut *self.as_ptr::() }}; - ptr.take().unwrap() + let ptr = self.as_ptr::(); + unsafe {{ >::rep_take(ptr) }} }} #[doc(hidden)] @@ -2811,7 +2830,7 @@ impl {camel} {{ // It's theoretically possible to implement the `Guest{camel}` trait twice // so guard against using it with two different types here. #[doc(hidden)] - fn type_guard() {{ + fn type_guard() {{ use core::any::TypeId; static mut LAST_TYPE: Option = None; unsafe {{ @@ -2825,12 +2844,14 @@ impl {camel} {{ }} #[doc(hidden)] - pub unsafe fn dtor(handle: *mut u8) {{ + pub unsafe fn dtor(handle: *mut u8) {{ Self::type_guard::(); - let _ = unsafe {{ T::resource_from_raw_(handle as *mut _{camel}Rep) }}; + unsafe {{ + let _rep = T::resource_from_raw_(handle.cast()); + }} }} - fn as_ptr(&self) -> *mut _{camel}Rep {{ + fn as_ptr(&self) -> *mut T::Rep {{ {camel}::type_guard::(); T::_resource_rep(self.handle()).cast() }} @@ -2841,29 +2862,29 @@ impl {camel} {{ #[derive(Debug)] #[repr(transparent)] pub struct {camel}Borrow<'a> {{ - rep: *mut u8, + rep: *const u8, _marker: core::marker::PhantomData<&'a {camel}>, }} impl<'a> {camel}Borrow<'a>{{ #[doc(hidden)] - pub unsafe fn lift(rep: usize) -> Self {{ + pub unsafe fn lift(rep: *const u8) -> Self {{ Self {{ - rep: rep as *mut u8, + rep, _marker: core::marker::PhantomData, }} }} /// Gets access to the underlying `T` in this resource. pub fn get(&self) -> &'a T {{ - let ptr = unsafe {{ &mut *self.as_ptr::() }}; - ptr.as_ref().unwrap() + let ptr = self.as_ptr::(); + unsafe {{ >::rep_as_ref(ptr) }} }} // NB: mutable access is not allowed due to the component model allowing // multiple borrows of the same resource. - fn as_ptr(&self) -> *mut _{camel}Rep {{ + fn as_ptr(&self) -> *const T::Rep {{ {camel}::type_guard::(); self.rep.cast() }} diff --git a/tests/runtime/rust/arena-allocated-resources/test.rs b/tests/runtime/rust/arena-allocated-resources/test.rs index a9e7b189d..71fb684e0 100644 --- a/tests/runtime/rust/arena-allocated-resources/test.rs +++ b/tests/runtime/rust/arena-allocated-resources/test.rs @@ -1,6 +1,6 @@ include!(env!("BINDINGS")); -use crate::exports::test::arena_allocated_resources::to_test::{Guest, GuestThing, ThingStorage}; +use crate::exports::test::arena_allocated_resources::to_test::{Guest, GuestThing}; export!(Component); @@ -80,10 +80,7 @@ impl GuestThing for MyThing { self.contents } - fn resource_into_raw_(val: ThingStorage) -> *mut ThingStorage - where - Self: Sized, - { + unsafe fn resource_into_raw_(val: Self::Rep) -> *mut Self::Rep { val.and_then(|v| { ARENA.alloc_one().map(|x| { *x = Some(v); @@ -93,11 +90,7 @@ impl GuestThing for MyThing { .unwrap_or(core::ptr::null_mut()) } - unsafe fn resource_from_raw_(handle: *mut ThingStorage) -> ThingStorage - where - Self: Sized, - { - let res = unsafe { &mut *handle }.take(); - res + unsafe fn resource_from_raw_(handle: *mut Self::Rep) -> Self::Rep { + unsafe { &mut *handle }.take() } } From dc970be8d87151ef4a748daa820f0ba7570e2a1c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 15 Jun 2026 14:08:52 -0700 Subject: [PATCH 6/6] Fix doc example --- crates/guest-rust/src/examples/_4_exported_resources.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/guest-rust/src/examples/_4_exported_resources.rs b/crates/guest-rust/src/examples/_4_exported_resources.rs index 5d1281be5..e47914d4c 100644 --- a/crates/guest-rust/src/examples/_4_exported_resources.rs +++ b/crates/guest-rust/src/examples/_4_exported_resources.rs @@ -23,4 +23,5 @@ crate::generate!({ } } "#, + runtime_path: "crate::rt", // only needed for this in-crate example. });