vtable/
lib.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4/*!
5This crate allows you to create ffi-friendly virtual tables.
6
7## Features
8
9 - A [`#[vtable]`](macro@vtable) macro to annotate a VTable struct to generate the traits and structure
10   to safely work with it.
11 - [`VRef`]/[`VRefMut`]/[`VBox`] types. They are fat reference/box types which wrap a pointer to
12   the vtable, and a pointer to the object.
13 - [`VRc`]/[`VWeak`] types: equivalent to `std::rc::{Rc, Weak}` types but works with a vtable pointer.
14 - Ability to store constants in a vtable.
15 - These constants can even be a field offset.
16
17## Example of use:
18
19```
20use vtable::*;
21// we are going to declare a VTable structure for an Animal trait
22#[vtable]
23#[repr(C)]
24struct AnimalVTable {
25    /// pointer to a function that makes a noise.  The `VRef<AnimalVTable>` is the type of
26    /// the self object.
27    make_noise: fn(VRef<AnimalVTable>, i32) -> i32,
28
29    /// if there is a 'drop' member, it is considered as the destructor.
30    drop: fn(VRefMut<AnimalVTable>),
31}
32
33struct Dog(i32);
34
35// The #[vtable] macro created the Animal Trait
36impl Animal for Dog {
37    fn make_noise(&self, intensity: i32) -> i32 {
38        println!("Wof!");
39        return self.0 * intensity;
40    }
41}
42
43// the vtable macro also exposed a macro to create a vtable
44AnimalVTable_static!(static DOG_VT for Dog);
45
46// with that, it is possible to instantiate a VBox
47let animal_box = VBox::<AnimalVTable>::new(Dog(42));
48assert_eq!(animal_box.make_noise(2), 42 * 2);
49```
50
51The [`#[vtable]`](macro@vtable) macro created the "Animal" trait.
52
53Note that the [`#[vtable]`](macro@vtable) macro is applied to the VTable struct so
54that `cbindgen` can see the actual vtable.
55
56*/
57
58#![warn(missing_docs)]
59#![no_std]
60extern crate alloc;
61
62#[doc(no_inline)]
63pub use const_field_offset::*;
64use core::marker::PhantomData;
65use core::ops::{Deref, DerefMut, Drop};
66use core::{pin::Pin, ptr::NonNull};
67#[doc(inline)]
68pub use vtable_macro::*;
69
70mod vrc;
71pub use vrc::*;
72
73/// Internal trait that is implemented by the [`#[vtable]`](macro@vtable) macro.
74///
75/// # Safety
76///
77/// The Target object needs to be implemented correctly.
78/// And there should be a `VTable::VTable::new<T>` function that returns a
79/// VTable suitable for the type T.
80pub unsafe trait VTableMeta {
81    /// That's the trait object that implements the functions
82    ///
83    /// NOTE: the size must be `2*size_of::<usize>`
84    /// and a `repr(C)` with `(vtable, ptr)` so it has the same layout as
85    /// the inner and VBox/VRef/VRefMut
86    type Target;
87
88    /// That's the VTable itself (so most likely Self)
89    type VTable: 'static;
90}
91
92/// This trait is implemented by the [`#[vtable]`](macro@vtable) macro.
93///
94/// It is implemented if the macro has a "drop" function.
95///
96/// # Safety
97/// Only the [`#[vtable]`](macro@vtable) macro should implement this trait.
98pub unsafe trait VTableMetaDrop: VTableMeta {
99    /// # Safety
100    /// `ptr` needs to be pointing to a valid allocated pointer
101    unsafe fn drop(ptr: *mut Self::Target);
102    /// allocate a new [`VBox`]
103    fn new_box<X: HasStaticVTable<Self>>(value: X) -> VBox<Self>;
104}
105
106/// Allow to associate a VTable with a type.
107///
108/// # Safety
109///
110/// The VTABLE and STATIC_VTABLE need to be a valid virtual table
111/// corresponding to pointer to Self instance.
112pub unsafe trait HasStaticVTable<VT>
113where
114    VT: ?Sized + VTableMeta,
115{
116    /// Safety: must be a valid VTable for Self
117    fn static_vtable() -> &'static VT::VTable;
118}
119
120#[derive(Copy, Clone)]
121/// The inner structure of VRef, VRefMut, and VBox.
122///
123/// Invariant: _vtable and _ptr are valid pointer for the lifetime of the container.
124/// _ptr is an instance of the object represented by _vtable
125#[allow(dead_code)]
126#[repr(C)]
127struct Inner {
128    vtable: NonNull<u8>,
129    ptr: NonNull<u8>,
130}
131
132impl Inner {
133    /// Transmute a reference to self into a reference to T::Target.
134    fn deref<T: ?Sized + VTableMeta>(&self) -> *const T::Target {
135        debug_assert_eq!(core::mem::size_of::<T::Target>(), core::mem::size_of::<Inner>());
136        self as *const Inner as *const T::Target
137    }
138
139    /// Same as [`Self::deref`].
140    fn deref_mut<T: ?Sized + VTableMeta>(&mut self) -> *mut T::Target {
141        debug_assert_eq!(core::mem::size_of::<T::Target>(), core::mem::size_of::<Inner>());
142        self as *mut Inner as *mut T::Target
143    }
144}
145
146/// An equivalent of a Box that holds a pointer to a VTable and a pointer to an instance.
147/// A VBox frees the instance when dropped.
148///
149/// The type parameter is supposed to be the VTable type.
150///
151/// The VBox implements Deref so one can access all the members of the vtable.
152///
153/// This is only valid if the VTable has a `drop` function (so that the [`#[vtable]`](macro@vtable) macro
154/// implements the `VTableMetaDrop` trait for it)
155#[repr(transparent)]
156pub struct VBox<T: ?Sized + VTableMetaDrop> {
157    inner: Inner,
158    phantom: PhantomData<T::Target>,
159}
160
161impl<T: ?Sized + VTableMetaDrop> Deref for VBox<T> {
162    type Target = T::Target;
163    fn deref(&self) -> &Self::Target {
164        unsafe { &*self.inner.deref::<T>() }
165    }
166}
167impl<T: ?Sized + VTableMetaDrop> DerefMut for VBox<T> {
168    fn deref_mut(&mut self) -> &mut Self::Target {
169        unsafe { &mut *(self.inner.deref_mut::<T>() as *mut _) }
170    }
171}
172
173impl<T: ?Sized + VTableMetaDrop> Drop for VBox<T> {
174    fn drop(&mut self) {
175        unsafe {
176            T::drop(self.inner.deref::<T>() as *mut _);
177        }
178    }
179}
180
181impl<T: ?Sized + VTableMetaDrop> VBox<T> {
182    /// Create a new VBox from an instance of a type that can be associated with a VTable.
183    ///
184    /// Will move the instance on the heap.
185    ///
186    /// (the `HasStaticVTable` is implemented by the `“MyTrait”VTable_static!` macro generated by
187    /// the #[vtable] macro)
188    pub fn new<X: HasStaticVTable<T>>(value: X) -> Self {
189        T::new_box(value)
190    }
191
192    /// Create a new VBox from raw pointers
193    /// # Safety
194    /// The `ptr` needs to be a valid object fitting the `vtable`.
195    /// ptr must be properly allocated so it can be dropped.
196    pub unsafe fn from_raw(vtable: NonNull<T::VTable>, ptr: NonNull<u8>) -> Self {
197        Self { inner: Inner { vtable: vtable.cast(), ptr }, phantom: PhantomData }
198    }
199
200    /// Gets a VRef pointing to this box
201    pub fn borrow(&self) -> VRef<'_, T> {
202        unsafe { VRef::from_inner(self.inner) }
203    }
204
205    /// Gets a VRefMut pointing to this box
206    pub fn borrow_mut(&mut self) -> VRefMut<'_, T> {
207        unsafe { VRefMut::from_inner(self.inner) }
208    }
209
210    /// Leaks the content of the box.
211    pub fn leak(self) -> VRefMut<'static, T> {
212        let inner = self.inner;
213        core::mem::forget(self);
214        unsafe { VRefMut::from_inner(inner) }
215    }
216}
217
218/// `VRef<'a MyTraitVTable>` can be thought as a `&'a dyn MyTrait`
219///
220/// It will dereference to a structure that has the same members as MyTrait.
221#[repr(transparent)]
222pub struct VRef<'a, T: ?Sized + VTableMeta> {
223    inner: Inner,
224    phantom: PhantomData<&'a T::Target>,
225}
226
227// Need to implement manually otherwise it is not implemented if T does not implement Copy / Clone
228impl<T: ?Sized + VTableMeta> Copy for VRef<'_, T> {}
229
230impl<T: ?Sized + VTableMeta> Clone for VRef<'_, T> {
231    fn clone(&self) -> Self {
232        *self
233    }
234}
235
236impl<T: ?Sized + VTableMeta> Deref for VRef<'_, T> {
237    type Target = T::Target;
238    fn deref(&self) -> &Self::Target {
239        unsafe { &*self.inner.deref::<T>() }
240    }
241}
242
243impl<'a, T: ?Sized + VTableMeta> VRef<'a, T> {
244    /// Create a new VRef from an reference of a type that can be associated with a VTable.
245    ///
246    /// (the `HasStaticVTable` is implemented by the `“MyTrait”VTable_static!` macro generated by
247    /// the #[vtable] macro)
248    pub fn new<X: HasStaticVTable<T>>(value: &'a X) -> Self {
249        Self {
250            inner: Inner {
251                vtable: NonNull::from(X::static_vtable()).cast(),
252                ptr: NonNull::from(value).cast(),
253            },
254            phantom: PhantomData,
255        }
256    }
257
258    /// Create a new Pin<VRef<_>> from a pinned reference. This is similar to `VRef::new`.
259    pub fn new_pin<X: HasStaticVTable<T>>(value: core::pin::Pin<&'a X>) -> Pin<Self> {
260        // Since Value is pinned, this means it is safe to construct a Pin
261        unsafe {
262            Pin::new_unchecked(Self {
263                inner: Inner {
264                    vtable: NonNull::from(X::static_vtable()).cast(),
265                    ptr: NonNull::from(value.get_ref()).cast(),
266                },
267                phantom: PhantomData,
268            })
269        }
270    }
271
272    unsafe fn from_inner(inner: Inner) -> Self {
273        Self { inner, phantom: PhantomData }
274    }
275
276    /// Create a new VRef from raw pointers
277    /// # Safety
278    /// The `ptr` needs to be a valid object fitting the `vtable`.
279    /// Both vtable and ptr lifetime must outlive 'a
280    pub unsafe fn from_raw(vtable: NonNull<T::VTable>, ptr: NonNull<u8>) -> Self {
281        Self { inner: Inner { vtable: vtable.cast(), ptr }, phantom: PhantomData }
282    }
283
284    /// Return a reference of the given type if the type is matching.
285    pub fn downcast<X: HasStaticVTable<T>>(&self) -> Option<&X> {
286        if self.inner.vtable == NonNull::from(X::static_vtable()).cast() {
287            // Safety: We just checked that the vtable fits
288            unsafe { Some(self.inner.ptr.cast().as_ref()) }
289        } else {
290            None
291        }
292    }
293
294    /// Return a reference of the given type if the type is matching
295    pub fn downcast_pin<X: HasStaticVTable<T>>(this: Pin<Self>) -> Option<Pin<&'a X>> {
296        let inner = unsafe { Pin::into_inner_unchecked(this).inner };
297        if inner.vtable == NonNull::from(X::static_vtable()).cast() {
298            // Safety: We just checked that the vtable fits
299            unsafe { Some(Pin::new_unchecked(inner.ptr.cast().as_ref())) }
300        } else {
301            None
302        }
303    }
304
305    /// Returns a pointer to the VRef's instance. This is primarily useful for comparisons.
306    pub fn as_ptr(this: Self) -> NonNull<u8> {
307        this.inner.ptr
308    }
309}
310
311/// `VRefMut<'a MyTraitVTable>` can be thought as a `&'a mut dyn MyTrait`
312///
313/// It will dereference to a structure that has the same members as MyTrait.
314#[repr(transparent)]
315pub struct VRefMut<'a, T: ?Sized + VTableMeta> {
316    inner: Inner,
317    phantom: PhantomData<&'a mut T::Target>,
318}
319
320impl<T: ?Sized + VTableMeta> Deref for VRefMut<'_, T> {
321    type Target = T::Target;
322    fn deref(&self) -> &Self::Target {
323        unsafe { &*self.inner.deref::<T>() }
324    }
325}
326
327impl<T: ?Sized + VTableMeta> DerefMut for VRefMut<'_, T> {
328    fn deref_mut(&mut self) -> &mut Self::Target {
329        unsafe { &mut *(self.inner.deref_mut::<T>() as *mut _) }
330    }
331}
332
333impl<'a, T: ?Sized + VTableMeta> VRefMut<'a, T> {
334    /// Create a new VRef from a mutable reference of a type that can be associated with a VTable.
335    ///
336    /// (the `HasStaticVTable` is implemented by the `“MyTrait”VTable_static!` macro generated by
337    /// the #[vtable] macro)
338    pub fn new<X: HasStaticVTable<T>>(value: &'a mut X) -> Self {
339        Self {
340            inner: Inner {
341                vtable: NonNull::from(X::static_vtable()).cast(),
342                ptr: NonNull::from(value).cast(),
343            },
344            phantom: PhantomData,
345        }
346    }
347
348    unsafe fn from_inner(inner: Inner) -> Self {
349        Self { inner, phantom: PhantomData }
350    }
351
352    /// Create a new VRefMut from raw pointers
353    /// # Safety
354    /// The `ptr` needs to be a valid object fitting the `vtable`.
355    /// Both vtable and ptr lifetime must outlive 'a.
356    /// Can create mutable reference to ptr, so no other code can create mutable reference of ptr
357    /// during the life time 'a.
358    pub unsafe fn from_raw(vtable: NonNull<T::VTable>, ptr: NonNull<u8>) -> Self {
359        Self { inner: Inner { vtable: vtable.cast(), ptr }, phantom: PhantomData }
360    }
361
362    /// Borrow this to obtain a VRef.
363    pub fn borrow(&self) -> VRef<'_, T> {
364        unsafe { VRef::from_inner(self.inner) }
365    }
366
367    /// Borrow this to obtain a new VRefMut.
368    pub fn borrow_mut(&mut self) -> VRefMut<'_, T> {
369        unsafe { VRefMut::from_inner(self.inner) }
370    }
371
372    /// Create a VRef with the same lifetime as the original lifetime.
373    pub fn into_ref(self) -> VRef<'a, T> {
374        unsafe { VRef::from_inner(self.inner) }
375    }
376
377    /// Return a reference of the given type if the type is matching.
378    pub fn downcast<X: HasStaticVTable<T>>(&mut self) -> Option<&mut X> {
379        if self.inner.vtable == NonNull::from(X::static_vtable()).cast() {
380            // Safety: We just checked that the vtable fits
381            unsafe { Some(self.inner.ptr.cast().as_mut()) }
382        } else {
383            None
384        }
385    }
386}
387
388/** Creates a [`VRef`] or a [`VRefMut`] suitable for an instance that implements the trait
389
390When possible, [`VRef::new`] or [`VRefMut::new`] should be preferred, as they use a static vtable.
391But when using the generated `XxxVTable_static!` macro that is not possible and this macro can be
392used instead.
393Note that the `downcast` will not work with references created with this macro.
394
395```
396use vtable::*;
397#[vtable]
398struct MyVTable { /* ... */ }
399struct Something { /* ... */};
400impl My for Something {};
401
402let mut s = Something { /* ... */};
403// declare a my_vref variable for the said VTable
404new_vref!(let my_vref : VRef<MyVTable> for My = &s);
405
406// same but mutable
407new_vref!(let mut my_vref_m : VRefMut<MyVTable> for My = &mut s);
408
409```
410*/
411#[macro_export]
412macro_rules! new_vref {
413    (let $ident:ident : VRef<$vtable:ty> for $trait_:path = $e:expr) => {
414        // ensure that the type of the expression is correct
415        let vtable = {
416            use $crate::VTableMeta;
417            fn get_vt<X: $trait_>(_: &X) -> <$vtable as VTableMeta>::VTable {
418                <$vtable as VTableMeta>::VTable::new::<X>()
419            }
420            get_vt($e)
421        };
422
423        let $ident = {
424            use $crate::VTableMeta;
425            fn create<'a, X: $trait_>(
426                vtable: &'a <$vtable as VTableMeta>::VTable,
427                val: &'a X,
428            ) -> $crate::VRef<'a, <$vtable as VTableMeta>::VTable> {
429                use ::core::ptr::NonNull;
430                // Safety: we constructed the vtable such that it fits for the value
431                unsafe { $crate::VRef::from_raw(NonNull::from(vtable), NonNull::from(val).cast()) }
432            }
433            create(&vtable, $e)
434        };
435    };
436    (let mut $ident:ident : VRefMut<$vtable:ty> for $trait_:path = $e:expr) => {
437        // ensure that the type of the expression is correct
438        let vtable = {
439            use $crate::VTableMeta;
440            fn get_vt<X: $trait_>(_: &mut X) -> <$vtable as VTableMeta>::VTable {
441                <$vtable as VTableMeta>::VTable::new::<X>()
442            }
443            get_vt($e)
444        };
445
446        let mut $ident = {
447            use $crate::VTableMeta;
448            fn create<'a, X: $trait_>(
449                vtable: &'a <$vtable as VTableMeta>::VTable,
450                val: &'a mut X,
451            ) -> $crate::VRefMut<'a, <$vtable as VTableMeta>::VTable> {
452                use ::core::ptr::NonNull;
453                // Safety: we constructed the vtable such that it fits for the value
454                unsafe {
455                    $crate::VRefMut::from_raw(NonNull::from(vtable), NonNull::from(val).cast())
456                }
457            }
458            create(&vtable, $e)
459        };
460    };
461}
462
463/// Represents an offset to a field of type matching the vtable, within the Base container structure.
464#[repr(C)]
465pub struct VOffset<Base, T: ?Sized + VTableMeta, PinFlag = NotPinned> {
466    vtable: &'static T::VTable,
467    /// Safety invariant: the vtable is valid, and the field at the given offset within Base is
468    /// matching with the vtable
469    offset: usize,
470    phantom: PhantomData<FieldOffset<Base, (), PinFlag>>,
471}
472
473impl<Base, T: ?Sized + VTableMeta, PinFlag> core::fmt::Debug for VOffset<Base, T, PinFlag> {
474    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
475        write!(f, "VOffset({})", self.offset)
476    }
477}
478
479impl<Base, T: ?Sized + VTableMeta, Flag> VOffset<Base, T, Flag> {
480    /// Apply this offset to a reference to the base to obtain a [`VRef`] with the same
481    /// lifetime as the base lifetime
482    #[inline]
483    pub fn apply(self, base: &Base) -> VRef<'_, T> {
484        let ptr = base as *const Base as *const u8;
485        unsafe {
486            VRef::from_raw(
487                NonNull::from(self.vtable),
488                NonNull::new_unchecked(ptr.add(self.offset) as *mut _),
489            )
490        }
491    }
492
493    /// Apply this offset to a reference to the base to obtain a [`VRefMut`] with the same
494    /// lifetime as the base lifetime
495    #[inline]
496    pub fn apply_mut(self, base: &mut Base) -> VRefMut<'_, T> {
497        let ptr = base as *mut Base as *mut u8;
498        unsafe {
499            VRefMut::from_raw(
500                NonNull::from(self.vtable),
501                NonNull::new_unchecked(ptr.add(self.offset)),
502            )
503        }
504    }
505
506    /// Create an new VOffset from a [`FieldOffset`] where the target type implement the
507    /// [`HasStaticVTable`] trait.
508    #[inline]
509    pub fn new<X: HasStaticVTable<T>>(o: FieldOffset<Base, X, Flag>) -> Self {
510        Self { vtable: X::static_vtable(), offset: o.get_byte_offset(), phantom: PhantomData }
511    }
512
513    /// Create a new VOffset from raw data
514    ///
515    /// # Safety
516    /// There must be a field that matches the vtable at offset T in base.
517    #[inline]
518    pub unsafe fn from_raw(vtable: &'static T::VTable, offset: usize) -> Self {
519        Self { vtable, offset, phantom: PhantomData }
520    }
521}
522
523impl<Base, T: ?Sized + VTableMeta> VOffset<Base, T, AllowPin> {
524    /// Apply this offset to a reference to the base to obtain a `Pin<VRef<'a, T>>` with the same
525    /// lifetime as the base lifetime
526    #[inline]
527    pub fn apply_pin(self, base: Pin<&Base>) -> Pin<VRef<'_, T>> {
528        let ptr = base.get_ref() as *const Base as *mut u8;
529        unsafe {
530            Pin::new_unchecked(VRef::from_raw(
531                NonNull::from(self.vtable),
532                NonNull::new_unchecked(ptr.add(self.offset)),
533            ))
534        }
535    }
536}
537
538// Need to implement manually otherwise it is not implemented if T does not implement Copy / Clone
539impl<Base, T: ?Sized + VTableMeta, Flag> Copy for VOffset<Base, T, Flag> {}
540
541impl<Base, T: ?Sized + VTableMeta, Flag> Clone for VOffset<Base, T, Flag> {
542    fn clone(&self) -> Self {
543        *self
544    }
545}
546
547#[cfg(doctest)]
548mod compile_fail_tests;
549
550/// re-export for the macro
551#[doc(hidden)]
552pub mod internal {
553    pub use alloc::alloc::dealloc;
554    pub use alloc::boxed::Box;
555}