├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── README.tpl ├── macros ├── Cargo.toml └── macros.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vptr" 3 | version = "0.2.2" 4 | authors = ["Olivier Goffart "] 5 | edition = "2021" 6 | description = "Thin references to trait objects by embedding the virtual table pointer in the struct" 7 | readme = "README.md" 8 | license = "MIT" 9 | keywords = ["vtable", "thin", "light", "trait", "virtual"] 10 | categories = [ "memory-management" ] 11 | repository = "https://github.com/ogoffart/vptr" 12 | documentation = "https://docs.rs/vptr" 13 | rust-version = "1.77" 14 | 15 | [features] 16 | default = ["std"] 17 | std = [] 18 | 19 | [dependencies] 20 | vptr-macros = {path = "./macros", version = "=0.2.2"} 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Olivier Goffart 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/vptr.svg)](https://crates.io/crates/vptr) 2 | [![Documentation](https://docs.rs/vptr/badge.svg)](https://docs.rs/vptr/) 3 | 4 | # vptr 5 | 6 | Enable thin references to trait 7 | 8 | ## Intro 9 | 10 | ### What are trait object and virtual table ? 11 | 12 | In rust, you can have dynamic dispatch with the so-called Trait object. 13 | Here is a typical example 14 | 15 | ```rust 16 | trait Shape { fn area(&self) -> f32; } 17 | struct Rectangle { w: f32, h : f32 } 18 | impl Shape for Rectangle { fn area(&self) -> f32 { self.w * self.h } } 19 | struct Circle { r: f32 } 20 | impl Shape for Circle { fn area(&self) -> f32 { 3.14 * self.r * self.r } } 21 | 22 | // Given an array of Shape, compute the sum of their area 23 | fn total_area(list: &[&dyn Shape]) -> f32 { 24 | list.iter().map(|x| x.area()).fold(0., |a, b| a+b) 25 | } 26 | ``` 27 | In this example the function `total_area` takes a reference of trait objects that implement 28 | the `Shape` trait. Internally, this `&dyn Shape` reference is composed of two pointer: 29 | a pointer to the object, and a pointer to a virtual table. The virtual table is a static 30 | structure containing the function pointer to the `area` function. Such virtual table exist 31 | for each type that implements the trait, but each instance of the same type share the same 32 | virtual table. Having only a pointer to the struct itself would not be enough as the 33 | `total_area` does not know the exact type of what it is pointed to, so it would not know from 34 | which `impl` to call the `area` function. 35 | 36 | This box diagram shows a simplified representation of the memory layout 37 | 38 | ```ascii 39 | &dyn Shape ╭──────> Rectangle ╭─> vtable of Shape for Rectangle 40 | ┏━━━━━━━━━━━━━┓ │ ┏━━━━━━━━━┓ │ ┏━━━━━━━━━┓ 41 | ┃ data ┠───╯ ┃ w ┃ │ ┃ area() ┃ 42 | ┣━━━━━━━━━━━━━┫ ┣━━━━━━━━━┫ │ ┣━━━━━━━━━┫ 43 | ┃ vtable ptr ┠─────╮ ┃ h ┃ │ ┃ drop() ┃ 44 | ┗━━━━━━━━━━━━━┛ │ ┗━━━━━━━━━┛ │ ┣━━━━━━━━━┫ 45 | ╰────────────────────╯ ┃ size ┃ 46 | ╏ ╏ 47 | ``` 48 | 49 | Other languages such as C++ implements that differently: in C++, each instance of a dynamic class 50 | has a pointer to the virtual table, inside of the class. So just a normal pointer to the base class 51 | is enough to do dynamic dispatch 52 | 53 | Both approaches have pros and cons: in Rust, the object themselves are a bit smaller as they 54 | do not have a pointer to the virtual table. They can also implement trait from other crates 55 | which would not work in C++ as it would have to somehow put the pointer to the virtual table 56 | inside the object. But rust pointer to trait are twice as big as normal pointer. Which is 57 | usually not a problem. Unless of course you want to pack many trait object reference in a vector 58 | in constrained memory, or pass them through ffi to C function that only handle pointer as data. 59 | That's where this crate comes in! 60 | 61 | ### Thin references 62 | 63 | This crates allows to easily opt in to thin references to trait for a type, by having 64 | pointers to the virtual table within the object. 65 | 66 | ```rust 67 | use vptr::vptr; 68 | trait Shape { fn area(&self) -> f32; } 69 | #[vptr(Shape)] 70 | struct Rectangle { w: f32, h : f32 } 71 | impl Shape for Rectangle { fn area(&self) -> f32 { self.w * self.h } } 72 | #[vptr(Shape)] 73 | struct Circle { r: f32 } 74 | impl Shape for Circle { fn area(&self) -> f32 { 3.14 * self.r * self.r } } 75 | 76 | // Given an array of Shape, compute the sum of their area 77 | fn total_area(list: &[vptr::ThinRef]) -> f32 { 78 | list.iter().map(|x| x.area()).fold(0., |a, b| a+b) 79 | } 80 | ``` 81 | 82 | Same as before, but we added `#[vptr(Shape)]` and are now using `ThinRef` instead of 83 | `&dyn Shape`. The difference is that the ThinRef has only the size of one pointer 84 | 85 | 86 | ```ascii 87 | ThinRef Rectangle ╭─>VTableData ╭─>vtable of Shape for Rectangle 88 | ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓ ╮ │ ┏━━━━━━━━┓ │ ┏━━━━━━━━━┓ 89 | ┃ ptr ┠──╮ ┃ w ┃ │ ╭──│──┨ offset ┃ │ ┃ area() ┃ 90 | ┗━━━━━━━━━━━━━┛ │ ┣━━━━━━━━━━━━┫ ⎬─╯ │ ┣━━━━━━━━┫ │ ┣━━━━━━━━━┫ 91 | │ ┃ h ┃ │ │ ┃ vtable ┠──╯ ┃ drop() ┃ 92 | │ ┣━━━━━━━━━━━━┫ ╯ │ ┗━━━━━━━━┛ ┣━━━━━━━━━┫ 93 | ╰──>┃ vptr_Shape ┠──────╯ ┃ size ┃ 94 | ┗━━━━━━━━━━━━┛ ╏ ╏ 95 | ``` 96 | 97 | 98 | ## The `#[vptr]` macro 99 | 100 | The `#[vptr(Trait)]` macro can be applied to a struct and it adds members to the struct 101 | with pointer to the vtable, these members are of type VPtr, where S is the struct. 102 | The macro also implements the `HasVPtr` trait which allow the creation of `ThinRef` for this 103 | 104 | You probably want to derive from `Default`, otherwise, the extra fields needs to be initialized 105 | manually (with `Default::default()` or `VPtr::new()`) 106 | 107 | ```rust 108 | trait Shape { fn area(&self) -> f32; } 109 | #[vptr(Shape, ToString)] // There can be several traits 110 | #[derive(Default)] 111 | struct Rectangle { w: f32, h : f32 } 112 | 113 | // The traits within #[vptr(...)] need to be implemented for that type 114 | impl Shape for Rectangle { fn area(&self) -> f32 { self.w * self.h } } 115 | impl Display for Rectangle { 116 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 117 | write!(fmt, "Rectangle ({} x {})", self.w, self.h) 118 | } 119 | } 120 | 121 | // [...] 122 | let mut r1 = Rectangle::default(); 123 | r1.w = 10.; r1.h = 5.; 124 | let ref1 = ThinRef::::from(&r1); 125 | assert_eq!(mem::size_of::>(), mem::size_of::()); 126 | assert_eq!(ref1.area(), 50.); 127 | 128 | // When not initializing with default, you must initialize the vptr's manually 129 | let r2 = Rectangle{ w: 1., h: 2., ..Default::default() }; 130 | let r3 = Rectangle{ w: 1., h: 2., vptr_Shape: VPtr::new(), vptr_ToString: VPtr::new() }; 131 | 132 | // Also work with tuple struct 133 | #[vptr(Shape)] struct Point(u32, u32); 134 | impl Shape for Point { fn area(&self) -> f32 { 0. } } 135 | let p = Point(1, 2, VPtr::new()); 136 | let pointref = ThinRef::from(&p); 137 | assert_eq!(pointref.area(), 0.); 138 | 139 | // The trait can be put in quote if it is too complex for a meta attribute 140 | #[vptr("PartialEq")] 141 | #[derive(Default)] 142 | struct MyString(String); 143 | impl PartialEq for MyString { 144 | fn eq(&self, other: &str) -> bool { self.0 == other } 145 | } 146 | let mystr = MyString("Hi".to_string(), VPtr::new()); 147 | let mystring_ref = ThinRef::from(&mystr); 148 | assert!(*mystring_ref == *"Hi"); 149 | ``` 150 | 151 | ## License 152 | 153 | MIT 154 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/vptr.svg)](https://crates.io/crates/vptr) 2 | [![Documentation](https://docs.rs/vptr/badge.svg)](https://docs.rs/vptr/) 3 | 4 | # {{crate}} 5 | 6 | {{readme}} 7 | 8 | ## License 9 | 10 | {{license}} 11 | 12 | 13 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vptr-macros" 3 | version = "0.2.2" 4 | authors = ["Olivier Goffart "] 5 | description = "Procedural macros for the `vptr` crate" 6 | edition = "2021" 7 | license = "MIT" 8 | repository = "https://github.com/ogoffart/vptr" 9 | documentation = "https://docs.rs/vptr" 10 | 11 | 12 | [lib] 13 | path = "macros.rs" 14 | proc-macro = true 15 | 16 | [dependencies] 17 | quote = "1" 18 | syn = {version = "1", features = ["full", "extra-traits"]} 19 | 20 | -------------------------------------------------------------------------------- /macros/macros.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019 Olivier Goffart 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 4 | associated documentation files (the "Software"), to deal in the Software without restriction, 5 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 7 | subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial 10 | portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 13 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES 15 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #![recursion_limit = "128"] 20 | //! Refer to the [documentation of the `vptr` crate](../vptr/index.html#the-vptr-macro) 21 | 22 | extern crate proc_macro; 23 | use proc_macro::TokenStream; 24 | use quote::quote; 25 | use syn::parse::Parser; 26 | use syn::{self, spanned::Spanned, AttributeArgs, ItemStruct}; 27 | 28 | /// Refer to the [documentation of the `vptr` crate](../vptr/index.html#the-vptr-macro) 29 | #[proc_macro_attribute] 30 | pub fn vptr(attr: TokenStream, item: TokenStream) -> TokenStream { 31 | let attr = syn::parse_macro_input!(attr as AttributeArgs); 32 | let item = syn::parse_macro_input!(item as ItemStruct); 33 | match vptr_impl(attr, item) { 34 | Ok(x) => x, 35 | Err(e) => e.to_compile_error().into(), 36 | } 37 | } 38 | 39 | fn vptr_impl(attr: AttributeArgs, item: ItemStruct) -> Result { 40 | let ItemStruct { 41 | attrs, 42 | vis, 43 | struct_token, 44 | ident, 45 | generics, 46 | fields, 47 | semi_token, 48 | } = item; 49 | 50 | let attr = attr 51 | .iter() 52 | .map(|a| { 53 | if let syn::NestedMeta::Meta(syn::Meta::Path(i)) = a { 54 | Ok(i.clone()) 55 | } else if let syn::NestedMeta::Lit(syn::Lit::Str(lit_str)) = a { 56 | lit_str.parse::() 57 | } else { 58 | Err(syn::Error::new( 59 | a.span(), 60 | "attribute of vptr must be a trait", 61 | )) 62 | } 63 | }) 64 | .collect::, _>>()?; 65 | 66 | if let Some(tp) = generics.type_params().next() { 67 | return Err(syn::Error::new(tp.span(), "vptr does not support generics")); 68 | } 69 | 70 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 71 | 72 | let (fields, attr_with_names) = if let syn::Fields::Named(mut n) = fields { 73 | let attr_with_names: Vec<_> = attr 74 | .iter() 75 | .map(|t| { 76 | let field_name = quote::format_ident!("vptr_{}", t.segments.last().unwrap().ident); 77 | (t, quote! { #field_name }) 78 | }) 79 | .collect(); 80 | let parser = syn::Field::parse_named; 81 | for (trait_, field_name) in &attr_with_names { 82 | n.named 83 | .push(parser.parse( 84 | quote!(#field_name : vptr::VPtr<#ident #ty_generics, dyn #trait_>).into(), 85 | )?); 86 | } 87 | (syn::Fields::Named(n), attr_with_names) 88 | } else { 89 | let mut n = if let syn::Fields::Unnamed(n) = fields { 90 | n 91 | } else { 92 | syn::FieldsUnnamed { 93 | paren_token: Default::default(), 94 | unnamed: Default::default(), 95 | } 96 | }; 97 | let count = n.unnamed.len(); 98 | let parser = syn::Field::parse_unnamed; 99 | for trait_ in &attr { 100 | n.unnamed 101 | .push(parser.parse(quote!(vptr::VPtr<#ident #ty_generics, dyn #trait_>).into())?); 102 | } 103 | let attr_with_names: Vec<_> = attr 104 | .iter() 105 | .enumerate() 106 | .map(|(i, t)| { 107 | let field_name = syn::Index::from(i + count); 108 | (t, quote! { #field_name }) 109 | }) 110 | .collect(); 111 | (syn::Fields::Unnamed(n), attr_with_names) 112 | }; 113 | 114 | let mut result = quote!( 115 | #(#attrs)* #[allow(non_snake_case)] #vis #struct_token #ident #generics #fields #semi_token 116 | ); 117 | 118 | for (trait_, field_name) in attr_with_names { 119 | result = quote!(#result 120 | unsafe impl #impl_generics vptr::HasVPtr for #ident #ty_generics #where_clause { 121 | fn init() -> &'static vptr::VTableData { 122 | use vptr::internal::{TransmuterTO, TransmuterPtr}; 123 | static VTABLE : vptr::VTableData = vptr::VTableData{ 124 | offset: ::core::mem::offset_of!(#ident, #field_name) as isize, 125 | vtable: unsafe { 126 | let x: &'static #ident = TransmuterPtr::<#ident> { int: 0 }.ptr; 127 | TransmuterTO::{ ptr: x }.to.vtable 128 | } 129 | }; 130 | &VTABLE 131 | } 132 | 133 | fn get_vptr(&self) -> &vptr::VPtr { &self.#field_name } 134 | fn get_vptr_mut(&mut self) -> &mut vptr::VPtr { &mut self.#field_name } 135 | } 136 | ); 137 | } 138 | //println!("{}", result.to_string()); 139 | Ok(result.into()) 140 | } 141 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019 Olivier Goffart 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 4 | associated documentation files (the "Software"), to deal in the Software without restriction, 5 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 7 | subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial 10 | portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 13 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES 15 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | /*! Enable thin references to trait 20 | 21 | # Intro 22 | 23 | ## What are trait object and virtual table ? 24 | 25 | In rust, you can have dynamic dispatch with the so-called Trait object. 26 | Here is a typical example 27 | 28 | ```rust 29 | trait Shape { fn area(&self) -> f32; } 30 | struct Rectangle { w: f32, h : f32 } 31 | impl Shape for Rectangle { fn area(&self) -> f32 { self.w * self.h } } 32 | struct Circle { r: f32 } 33 | impl Shape for Circle { fn area(&self) -> f32 { 3.14 * self.r * self.r } } 34 | 35 | // Given an array of Shape, compute the sum of their area 36 | fn total_area(list: &[&dyn Shape]) -> f32 { 37 | list.iter().map(|x| x.area()).fold(0., |a, b| a+b) 38 | } 39 | ``` 40 | In this example the function `total_area` takes a reference of trait objects that implement 41 | the `Shape` trait. Internally, this `&dyn Shape` reference is composed of two pointer: 42 | a pointer to the object, and a pointer to a virtual table. The virtual table is a static 43 | structure containing the function pointer to the `area` function. Such virtual table exist 44 | for each type that implements the trait, but each instance of the same type share the same 45 | virtual table. Having only a pointer to the struct itself would not be enough as the 46 | `total_area` does not know the exact type of what it is pointed to, so it would not know from 47 | which `impl` to call the `area` function. 48 | 49 | This box diagram shows a simplified representation of the memory layout 50 | 51 | ```ascii 52 | &dyn Shape ╭──────> Rectangle ╭─> vtable of Shape for Rectangle 53 | ┏━━━━━━━━━━━━━┓ │ ┏━━━━━━━━━┓ │ ┏━━━━━━━━━┓ 54 | ┃ data ┠───╯ ┃ w ┃ │ ┃ area() ┃ 55 | ┣━━━━━━━━━━━━━┫ ┣━━━━━━━━━┫ │ ┣━━━━━━━━━┫ 56 | ┃ vtable ptr ┠─────╮ ┃ h ┃ │ ┃ drop() ┃ 57 | ┗━━━━━━━━━━━━━┛ │ ┗━━━━━━━━━┛ │ ┣━━━━━━━━━┫ 58 | ╰────────────────────╯ ┃ size ┃ 59 | ╏ ╏ 60 | ``` 61 | 62 | Other languages such as C++ implements that differently: in C++, each instance of a dynamic class 63 | has a pointer to the virtual table, inside of the class. So just a normal pointer to the base class 64 | is enough to do dynamic dispatch 65 | 66 | Both approaches have pros and cons: in Rust, the object themselves are a bit smaller as they 67 | do not have a pointer to the virtual table. They can also implement trait from other crates 68 | which would not work in C++ as it would have to somehow put the pointer to the virtual table 69 | inside the object. But rust pointer to trait are twice as big as normal pointer. Which is 70 | usually not a problem. Unless of course you want to pack many trait object reference in a vector 71 | in constrained memory, or pass them through ffi to C function that only handle pointer as data. 72 | That's where this crate comes in! 73 | 74 | ## Thin references 75 | 76 | This crates allows to easily opt in to thin references to trait for a type, by having 77 | pointers to the virtual table within the object. 78 | 79 | ```rust 80 | use vptr::vptr; 81 | trait Shape { fn area(&self) -> f32; } 82 | #[vptr(Shape)] 83 | struct Rectangle { w: f32, h : f32 } 84 | impl Shape for Rectangle { fn area(&self) -> f32 { self.w * self.h } } 85 | #[vptr(Shape)] 86 | struct Circle { r: f32 } 87 | impl Shape for Circle { fn area(&self) -> f32 { 3.14 * self.r * self.r } } 88 | 89 | // Given an array of Shape, compute the sum of their area 90 | fn total_area(list: &[vptr::ThinRef]) -> f32 { 91 | list.iter().map(|x| x.area()).fold(0., |a, b| a+b) 92 | } 93 | ``` 94 | 95 | Same as before, but we added `#[vptr(Shape)]` and are now using `ThinRef` instead of 96 | `&dyn Shame`. The difference is that the ThinRef has only the size of one pointer 97 | 98 | 99 | ```ascii 100 | ThinRef Rectangle ╭─>VTableData ╭─>vtable of Shape for Rectangle 101 | ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓ ╮ │ ┏━━━━━━━━┓ │ ┏━━━━━━━━━┓ 102 | ┃ ptr ┠──╮ ┃ w ┃ │ ╭──│──┨ offset ┃ │ ┃ area() ┃ 103 | ┗━━━━━━━━━━━━━┛ │ ┣━━━━━━━━━━━━┫ ⎬─╯ │ ┣━━━━━━━━┫ │ ┣━━━━━━━━━┫ 104 | │ ┃ h ┃ │ │ ┃ vtable ┠──╯ ┃ drop() ┃ 105 | │ ┣━━━━━━━━━━━━┫ ╯ │ ┗━━━━━━━━┛ ┣━━━━━━━━━┫ 106 | ╰──>┃ vptr_Shape ┠──────╯ ┃ size ┃ 107 | ┗━━━━━━━━━━━━┛ ╏ ╏ 108 | ``` 109 | 110 | 111 | # The `#[vptr]` macro 112 | 113 | The `#[vptr(Trait)]` macro can be applied to a struct and it adds members to the struct 114 | with pointer to the vtable, these members are of type VPtr, where S is the struct. 115 | The macro also implements the `HasVPtr` trait which allow the creation of `ThinRef` for this 116 | 117 | You probably want to derive from `Default`, otherwise, the extra fields needs to be initialized 118 | manually (with `Default::default()` or `VPtr::new()`) 119 | 120 | ```rust 121 | # use std::{mem, fmt::{self, Display}}; 122 | # use vptr::*; 123 | trait Shape { fn area(&self) -> f32; } 124 | #[vptr(Shape, ToString)] // There can be several traits 125 | #[derive(Default)] 126 | struct Rectangle { w: f32, h : f32 } 127 | 128 | // The traits within #[vptr(...)] need to be implemented for that type 129 | impl Shape for Rectangle { fn area(&self) -> f32 { self.w * self.h } } 130 | impl Display for Rectangle { 131 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 132 | write!(fmt, "Rectangle ({} x {})", self.w, self.h) 133 | } 134 | } 135 | 136 | // [...] 137 | let mut r1 = Rectangle::default(); 138 | r1.w = 10.; r1.h = 5.; 139 | let ref1 = ThinRef::::from(&r1); 140 | assert_eq!(mem::size_of::>(), mem::size_of::()); 141 | assert_eq!(ref1.area(), 50.); 142 | 143 | // When not initializing with default, you must initialize the vptr's manually 144 | let r2 = Rectangle{ w: 1., h: 2., ..Default::default() }; 145 | let r3 = Rectangle{ w: 1., h: 2., vptr_Shape: VPtr::new(), vptr_ToString: VPtr::new() }; 146 | 147 | // Also work with tuple struct 148 | #[vptr(Shape)] struct Point(u32, u32); 149 | impl Shape for Point { fn area(&self) -> f32 { 0. } } 150 | let p = Point(1, 2, VPtr::new()); 151 | let pointref = ThinRef::from(&p); 152 | assert_eq!(pointref.area(), 0.); 153 | 154 | // The trait can be put in quote if it is too complex for a meta attribute 155 | #[vptr("PartialEq")] 156 | #[derive(Default)] 157 | struct MyString(String); 158 | impl PartialEq for MyString { 159 | fn eq(&self, other: &str) -> bool { self.0 == other } 160 | } 161 | let mystr = MyString("Hi".to_string(), VPtr::new()); 162 | let mystring_ref = ThinRef::from(&mystr); 163 | assert!(*mystring_ref == *"Hi"); 164 | ``` 165 | */ 166 | 167 | #![cfg_attr(not(feature = "std"), no_std)] 168 | #![warn(missing_docs)] 169 | #[doc(inline)] 170 | pub use ::vptr_macros::vptr; 171 | use core::borrow::{Borrow, BorrowMut}; 172 | use core::marker::PhantomData; 173 | use core::ops::{Deref, DerefMut}; 174 | use core::pin::Pin; 175 | use core::ptr::NonNull; 176 | #[cfg(feature = "std")] 177 | use std::boxed::Box; 178 | 179 | /// Represent a pointer to a virtual table to the trait `Trait` that is to be embedded in 180 | /// a structure `T` 181 | /// 182 | /// One should not need to use this structure directly, it is going to be created by the `vptr` 183 | /// procedural macro. 184 | #[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd)] 185 | pub struct VPtr 186 | where 187 | T: HasVPtr, 188 | { 189 | vtable: &'static VTableData, 190 | phantom: PhantomData<(*const T, *const Trait)>, 191 | } 192 | 193 | impl VPtr 194 | where 195 | T: HasVPtr, 196 | { 197 | /// Creates a new VPtr initialized to a pointer of the vtable of the `Trait` for the type `T`. 198 | /// Same as VPtr::default() 199 | pub fn new() -> Self { 200 | VPtr { 201 | vtable: T::init(), 202 | phantom: PhantomData, 203 | } 204 | } 205 | } 206 | 207 | impl Default for VPtr 208 | where 209 | T: HasVPtr, 210 | { 211 | // Creates a new VPtr initialized to a pointer of the vtable of the `Trait` for the type `T`. 212 | // Same as VPtr::new() 213 | fn default() -> Self { 214 | VPtr::new() 215 | } 216 | } 217 | 218 | impl core::fmt::Debug for VPtr 219 | where 220 | T: HasVPtr, 221 | { 222 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 223 | f.pad("VPtr") 224 | } 225 | } 226 | 227 | /// This trait indicate that the type has a VPtr field to the trait `Trait` 228 | /// 229 | /// You should not implement this trait yourself, it is implemented by the `vptr` macro 230 | /// 231 | /// Safety: For this to work correctly, the init() function must return a reference to a VTableData 232 | /// with valid content (the offset and vtable pointer need to be correct for this type) and 233 | /// get_vptr must return a reference of a field withi &self. The `#[vptr] macro does the right thing 234 | pub unsafe trait HasVPtr { 235 | /// Initialize a VTableData suitable to initialize the VPtr within Self 236 | fn init() -> &'static VTableData; 237 | 238 | /// return the a reference of the VPtr within Self 239 | fn get_vptr(&self) -> &VPtr 240 | where 241 | Self: Sized; 242 | 243 | /// return the a reference of the VPtr within Self 244 | fn get_vptr_mut(&mut self) -> &mut VPtr 245 | where 246 | Self: Sized; 247 | 248 | /// return a thin reference to self 249 | fn as_thin_ref(&self) -> ThinRef 250 | where 251 | Self: Sized, 252 | { 253 | unsafe { ThinRef::new(self.get_vptr()) } 254 | } 255 | 256 | /// return a thin reference to self 257 | fn as_thin_ref_mut(&mut self) -> ThinRefMut 258 | where 259 | Self: Sized, 260 | { 261 | unsafe { ThinRefMut::new(self.get_vptr_mut()) } 262 | } 263 | 264 | /// Map a pinned reference to to a pinned thin reference 265 | fn as_pin_thin_ref(self: Pin<&Self>) -> Pin> 266 | where 267 | Self: Sized, 268 | { 269 | unsafe { Pin::new_unchecked(self.get_ref().as_thin_ref()) } 270 | } 271 | 272 | /// Map a pinned mutable reference to to a pinned mutable thin reference 273 | fn as_pin_thin_ref_mut(self: Pin<&mut Self>) -> Pin> 274 | where 275 | Self: Sized, 276 | { 277 | unsafe { Pin::new_unchecked(self.get_unchecked_mut().as_thin_ref_mut()) } 278 | } 279 | } 280 | 281 | /// A thin reference (size = `size_of::()`) to an object implementing the trait `Trait` 282 | /// 283 | /// This is like a reference to a trait (`&dyn Trait`) for struct that used 284 | /// the macro `#[vptr(Trait)]` 285 | /// 286 | /// See the crate documentation for example of usage. 287 | /// 288 | /// The size is only the size of a single pointer: 289 | /// ```rust 290 | /// # use vptr::*; 291 | /// # use std::mem; 292 | /// # trait Trait { } 293 | /// assert_eq!(mem::size_of::>(), mem::size_of::()); 294 | /// assert_eq!(mem::size_of::>>(), mem::size_of::()); 295 | /// ``` 296 | pub struct ThinRef<'a, Trait: ?Sized> { 297 | ptr: &'a &'static VTableData, 298 | phantom: PhantomData<&'a Trait>, 299 | } 300 | 301 | impl<'a, Trait: ?Sized> ThinRef<'a, Trait> { 302 | /// Create a new reference from a reference to a VPtr 303 | /// 304 | /// Safety: the ptr must be a field of a reference to T 305 | unsafe fn new>(ptr: &'a VPtr) -> Self { 306 | ThinRef { 307 | ptr: &ptr.vtable, 308 | phantom: PhantomData, 309 | } 310 | } 311 | } 312 | 313 | impl<'a, Trait: ?Sized + 'a> Deref for ThinRef<'a, Trait> { 314 | type Target = Trait; 315 | 316 | fn deref(&self) -> &Self::Target { 317 | unsafe { 318 | let VTableData { offset, vtable } = **self.ptr; 319 | let p = (self.ptr as *const _ as *const u8).offset(-offset) as *const (); 320 | internal::TransmuterTO:: { 321 | to: internal::TraitObject { data: p, vtable }, 322 | } 323 | .ptr 324 | } 325 | } 326 | } 327 | 328 | impl<'a, Trait: ?Sized + 'a> Borrow for ThinRef<'a, Trait> { 329 | fn borrow(&self) -> &Trait { 330 | &**self 331 | } 332 | } 333 | 334 | impl<'a, Trait: ?Sized + 'a, T: HasVPtr> From<&'a T> for ThinRef<'a, Trait> { 335 | fn from(f: &'a T) -> Self { 336 | unsafe { ThinRef::new(f.get_vptr()) } 337 | } 338 | } 339 | 340 | // Cannot use #[derive()] because it gets the bounds wrong (See rust RFC #2353) 341 | impl<'a, Trait: ?Sized> Clone for ThinRef<'a, Trait> { 342 | fn clone(&self) -> Self { 343 | *self 344 | } 345 | } 346 | impl<'a, Trait: ?Sized> Copy for ThinRef<'a, Trait> {} 347 | 348 | /// A thin reference (size = `size_of::()`) to an object implementing the trait `Trait` 349 | /// 350 | /// Same as ThinRef but for mutable references 351 | pub struct ThinRefMut<'a, Trait: ?Sized> { 352 | ptr: &'a mut &'static VTableData, 353 | phantom: PhantomData<&'a mut Trait>, 354 | } 355 | 356 | impl<'a, Trait: ?Sized> ThinRefMut<'a, Trait> { 357 | /// Create a new reference from a reference to a VPtr 358 | /// 359 | /// Safety: the ptr must be a field of a reference to T 360 | unsafe fn new>(ptr: &'a mut VPtr) -> Self { 361 | ThinRefMut { 362 | ptr: &mut ptr.vtable, 363 | phantom: PhantomData, 364 | } 365 | } 366 | } 367 | 368 | impl<'a, Trait: ?Sized + 'a> Deref for ThinRefMut<'a, Trait> { 369 | type Target = Trait; 370 | 371 | fn deref(&self) -> &Self::Target { 372 | unsafe { 373 | let VTableData { offset, vtable } = **self.ptr; 374 | let p = (self.ptr as *const _ as *const u8).offset(-offset) as *const (); 375 | internal::TransmuterTO:: { 376 | to: internal::TraitObject { data: p, vtable }, 377 | } 378 | .ptr 379 | } 380 | } 381 | } 382 | 383 | impl<'a, Trait: ?Sized + 'a> DerefMut for ThinRefMut<'a, Trait> { 384 | fn deref_mut(&mut self) -> &mut Self::Target { 385 | unsafe { 386 | let VTableData { offset, vtable } = **self.ptr; 387 | let p = (self.ptr as *mut _ as *mut u8).offset(-offset) as *mut (); 388 | union Transmuter { 389 | pub ptr: *mut T, 390 | pub to: internal::TraitObject, 391 | } 392 | let ptr = Transmuter:: { 393 | to: internal::TraitObject { data: p, vtable }, 394 | } 395 | .ptr; 396 | &mut *ptr 397 | } 398 | } 399 | } 400 | 401 | impl<'a, Trait: ?Sized + 'a> Borrow for ThinRefMut<'a, Trait> { 402 | fn borrow(&self) -> &Trait { 403 | &**self 404 | } 405 | } 406 | 407 | impl<'a, Trait: ?Sized + 'a> BorrowMut for ThinRefMut<'a, Trait> { 408 | fn borrow_mut(&mut self) -> &mut Trait { 409 | &mut **self 410 | } 411 | } 412 | 413 | impl<'a, Trait: ?Sized + 'a, T: HasVPtr> From<&'a mut T> for ThinRefMut<'a, Trait> { 414 | fn from(f: &'a mut T) -> Self { 415 | unsafe { ThinRefMut::new(f.get_vptr_mut()) } 416 | } 417 | } 418 | 419 | /// A Box of a trait with a size of `size_of::` 420 | /// 421 | /// The ThinBox can be created from a Box which implement the HasVPtr 422 | /// 423 | /// 424 | /// ```rust 425 | /// # use vptr::*; 426 | /// trait Shape { fn area(&self) -> f32; } 427 | /// #[vptr(Shape)] 428 | /// #[derive(Default)] 429 | /// struct Rectangle { w: f32, h : f32 } 430 | /// impl Shape for Rectangle { fn area(&self) -> f32 { self.w * self.h } } 431 | /// 432 | /// let r = Box::new(Rectangle { w: 5., h: 10., ..Default::default() }); 433 | /// let thin = ThinBox::from_box(r); 434 | /// assert_eq!(thin.area(), 50.); 435 | /// ``` 436 | /// 437 | /// The size is the size of a pointer 438 | /// ```rust 439 | /// # use vptr::*; 440 | /// # use std::mem; 441 | /// # trait Trait { } 442 | /// assert_eq!(mem::size_of::>(), mem::size_of::()); 443 | /// assert_eq!(mem::size_of::>>(), mem::size_of::()); 444 | /// ``` 445 | #[cfg(feature = "std")] 446 | #[repr(transparent)] 447 | pub struct ThinBox(NonNull<&'static VTableData>, PhantomData<*mut Trait>); 448 | 449 | #[cfg(feature = "std")] 450 | #[allow(clippy::wrong_self_convention)] 451 | impl ThinBox { 452 | /// Creates a ThinBox from a Box 453 | pub fn from_box>(f: Box) -> Self { 454 | ThinBox( 455 | NonNull::from(&mut Box::leak(f).get_vptr_mut().vtable), 456 | PhantomData, 457 | ) 458 | } 459 | /// Conver the ThinBox into a Box 460 | pub fn into_box(mut b: ThinBox) -> Box { 461 | let ptr = (&mut *ThinBox::as_thin_ref_mut(&mut b)) as *mut Trait; 462 | core::mem::forget(b); 463 | unsafe { Box::from_raw(ptr) } 464 | } 465 | 466 | /// As a ThinRef 467 | pub fn as_thin_ref(b: &ThinBox) -> ThinRef { 468 | ThinRef { 469 | ptr: unsafe { b.0.as_ref() }, 470 | phantom: PhantomData, 471 | } 472 | } 473 | 474 | /// As a ThinRefMut 475 | pub fn as_thin_ref_mut(b: &mut ThinBox) -> ThinRefMut { 476 | ThinRefMut { 477 | ptr: unsafe { b.0.as_mut() }, 478 | phantom: PhantomData, 479 | } 480 | } 481 | } 482 | 483 | #[cfg(feature = "std")] 484 | impl Drop for ThinBox { 485 | fn drop(&mut self) { 486 | let ptr = &mut *ThinBox::as_thin_ref_mut(self) as *mut Trait; 487 | drop(unsafe { Box::from_raw(ptr) }); 488 | } 489 | } 490 | 491 | #[cfg(feature = "std")] 492 | impl Deref for ThinBox { 493 | type Target = Trait; 494 | 495 | fn deref(&self) -> &Self::Target { 496 | let ptr = &*ThinBox::as_thin_ref(self) as *const Trait; 497 | unsafe { &*ptr } 498 | } 499 | } 500 | 501 | #[cfg(feature = "std")] 502 | impl DerefMut for ThinBox { 503 | fn deref_mut(&mut self) -> &mut Self::Target { 504 | let ptr = &mut *ThinBox::as_thin_ref_mut(self) as *mut Trait; 505 | unsafe { &mut *ptr } 506 | } 507 | } 508 | 509 | /// The data structure generated by the `#[vptr]` macro 510 | /// 511 | /// You should normaly not use directly this struct 512 | #[derive(Eq, Hash, PartialEq, PartialOrd)] 513 | pub struct VTableData { 514 | /// Offset, in byte, of the VPtr field within the struct 515 | pub offset: isize, 516 | /// Pointer to the actual vtable generated by rust (i.e., the second pointer in a TraitObject, 517 | /// or core::raw::TraitObject::vtable) 518 | pub vtable: *const (), 519 | } 520 | unsafe impl core::marker::Sync for VTableData {} 521 | 522 | /// A convenience module import the most important items 523 | /// 524 | /// ``` 525 | /// use vptr::prelude::*; 526 | /// ``` 527 | pub mod prelude { 528 | #[doc(no_inline)] 529 | pub use crate::{vptr, HasVPtr}; 530 | } 531 | 532 | #[doc(hidden)] 533 | pub mod internal { 534 | /// Internal struct used by the macro generated code 535 | /// Copy of core::raw::TraitObject since it is unstable 536 | #[doc(hidden)] 537 | #[repr(C)] 538 | #[derive(Copy, Clone)] 539 | pub struct TraitObject { 540 | pub data: *const (), 541 | pub vtable: *const (), 542 | } 543 | 544 | /// Internal struct used by the macro generated code 545 | #[doc(hidden)] 546 | pub union TransmuterPtr { 547 | pub ptr: &'static T, 548 | pub int: isize, 549 | } 550 | 551 | /// Internal struct used by the macro generated code 552 | #[doc(hidden)] 553 | pub union TransmuterTO<'a, T: ?Sized + 'a> { 554 | pub ptr: &'a T, 555 | pub to: TraitObject, 556 | } 557 | } 558 | 559 | #[cfg(test)] 560 | mod tests { 561 | #[cfg(not(feature = "std"))] 562 | extern crate alloc; 563 | use super::*; 564 | #[cfg(not(feature = "std"))] 565 | use alloc::string::{String, ToString as _}; 566 | mod vptr { 567 | // Because otherwise, the generated code cannot access the vptr crate. 568 | pub use crate::*; 569 | } 570 | 571 | trait MyTrait { 572 | fn myfn(&self) -> u32; 573 | } 574 | 575 | #[vptr(MyTrait)] 576 | #[derive(Default)] 577 | struct Foobar2 { 578 | q: u32, 579 | } 580 | 581 | impl MyTrait for Foobar2 { 582 | fn myfn(&self) -> u32 { 583 | self.q + 4 584 | } 585 | } 586 | 587 | #[test] 588 | fn it_works2() { 589 | let mut f = Foobar2::default(); 590 | f.q = 5; 591 | assert_eq!(f.myfn(), 9); 592 | 593 | let xx = f.as_thin_ref(); 594 | assert_eq!(xx.myfn(), 9); 595 | } 596 | 597 | #[vptr(MyTrait, SomeOtherTrait)] 598 | #[derive(Default, Debug)] 599 | struct Foobar3 { 600 | q: u32, 601 | } 602 | 603 | impl MyTrait for Foobar3 { 604 | fn myfn(&self) -> u32 { 605 | self.q + 4 606 | } 607 | } 608 | 609 | trait SomeOtherTrait {} 610 | impl SomeOtherTrait for Foobar3 {} 611 | 612 | #[test] 613 | fn it_works3() { 614 | let mut f = Foobar3::default(); 615 | f.q = 5; 616 | assert_eq!(f.myfn(), 9); 617 | 618 | { 619 | let xx: ThinRef = f.as_thin_ref(); 620 | assert_eq!(xx.myfn(), 9); 621 | } 622 | 623 | { 624 | let xx: ThinRefMut = f.as_thin_ref_mut(); 625 | assert_eq!(xx.myfn(), 9); 626 | } 627 | } 628 | 629 | /* 630 | #[vptr(MyTrait)] 631 | #[derive(Default)] 632 | struct WithGenerics { 633 | pub foo: Vec 634 | } 635 | 636 | impl MyTrait for WithGenerics { 637 | fn myfn(&self) -> u32 { 638 | self.foo.len() as u32 639 | } 640 | } 641 | 642 | #[test] 643 | fn with_generics() { 644 | let mut f = WithGenerics::::default(); 645 | f.foo.push(3); 646 | assert_eq!(f.myfn(), 1); 647 | 648 | let xx : ThinRef = f.as_thin_ref(); 649 | assert_eq!(xx.myfn(), 9); 650 | 651 | } 652 | */ 653 | 654 | #[vptr(MyTrait)] 655 | #[derive(Default)] 656 | struct WithLifeTime<'a> { 657 | pub foo: Option<&'a u32>, 658 | } 659 | 660 | impl<'a> MyTrait for WithLifeTime<'a> { 661 | fn myfn(&self) -> u32 { 662 | *self.foo.unwrap_or(&0) 663 | } 664 | } 665 | 666 | #[test] 667 | fn with_lifetime() { 668 | let mut f = WithLifeTime::default(); 669 | let x = 43; 670 | f.foo = Some(&x); 671 | assert_eq!(f.myfn(), 43); 672 | 673 | let xx: ThinRef = f.as_thin_ref(); 674 | assert_eq!(xx.myfn(), 43); 675 | } 676 | 677 | #[vptr(MyTrait)] 678 | #[allow(unused)] 679 | struct Tuple(u32, u32); 680 | 681 | impl MyTrait for Tuple { 682 | fn myfn(&self) -> u32 { 683 | self.1 684 | } 685 | } 686 | #[test] 687 | fn tuple() { 688 | let f = Tuple(42, 43, Default::default()); 689 | assert_eq!(f.myfn(), 43); 690 | 691 | let xx: ThinRef<_> = f.as_thin_ref(); 692 | assert_eq!(xx.myfn(), 43); 693 | } 694 | 695 | #[vptr(MyTrait)] 696 | struct Empty1; 697 | 698 | impl MyTrait for Empty1 { 699 | fn myfn(&self) -> u32 { 700 | 88 701 | } 702 | } 703 | 704 | #[test] 705 | fn empty_struct() { 706 | let f = Empty1(VPtr::new()); 707 | assert_eq!(f.myfn(), 88); 708 | 709 | let xx: ThinRef = f.as_thin_ref(); 710 | assert_eq!(xx.myfn(), 88); 711 | } 712 | 713 | #[vptr(core::fmt::Display)] 714 | struct TestDisplay { 715 | str: String, 716 | } 717 | impl core::fmt::Display for TestDisplay { 718 | fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 719 | write!(fmt, "Test {}", self.str) 720 | } 721 | } 722 | 723 | #[test] 724 | fn with_path() { 725 | let x = TestDisplay { 726 | str: "Hello".to_string(), 727 | vptr_Display: Default::default(), 728 | }; 729 | let xx: ThinRef = x.as_thin_ref(); 730 | assert_eq!(xx.to_string(), "Test Hello"); 731 | } 732 | 733 | #[test] 734 | fn test_trait_with_gen() { 735 | trait TraitWithGen { 736 | fn compute(&self, x: T) -> u32; 737 | } 738 | #[vptr("TraitWithGen")] 739 | struct TestTraitWithGen { 740 | value: u32, 741 | } 742 | impl TraitWithGen for TestTraitWithGen { 743 | fn compute(&self, x: u64) -> u32 { 744 | self.value + (x as u32) 745 | } 746 | } 747 | 748 | let x = TestTraitWithGen { 749 | value: 44, 750 | vptr_TraitWithGen: Default::default(), 751 | }; 752 | let xx: ThinRef> = x.as_thin_ref(); 753 | assert_eq!(core::mem::size_of_val(&xx), core::mem::size_of::()); 754 | assert_eq!(xx.compute(66u64), 44 + 66); 755 | } 756 | 757 | #[test] 758 | fn copy() { 759 | let f = Tuple(2, 3, Default::default()); 760 | let xx: ThinRef<_> = f.as_thin_ref(); 761 | let xx2 = xx; 762 | assert_eq!(xx.myfn(), 3); 763 | assert_eq!(xx2.myfn(), 3); 764 | } 765 | 766 | #[test] 767 | fn pin() { 768 | use core::pin::Pin; 769 | { 770 | let mut f = Foobar3::default(); 771 | f.q = 5; 772 | let f: Pin<&Foobar3> = unsafe { Pin::new_unchecked(&f) }; 773 | let xx: Pin> = f.as_pin_thin_ref(); 774 | assert_eq!(xx.myfn(), 9); 775 | } 776 | 777 | { 778 | let mut f = Foobar3::default(); 779 | f.q = 8; 780 | let f: Pin<&mut Foobar3> = unsafe { Pin::new_unchecked(&mut f) }; 781 | let xx: Pin> = f.as_pin_thin_ref_mut(); 782 | assert_eq!(xx.myfn(), 12); 783 | } 784 | } 785 | } 786 | --------------------------------------------------------------------------------