diff --git a/book.toml b/book.toml index 29d8026..6a5b4a8 100644 --- a/book.toml +++ b/book.toml @@ -4,3 +4,6 @@ language = "en" multilingual = false src = "src" title = "Futures Explained in 200 Lines of Rust" + +[output.html.playpen] +editable = true \ No newline at end of file diff --git a/src/0_1_background_information.md b/src/0_1_background_information.md index 5bf6078..79e526a 100644 --- a/src/0_1_background_information.md +++ b/src/0_1_background_information.md @@ -44,10 +44,104 @@ fn main() { println!("Size of &Box: {}", size_of::<&Box>()); println!("Size of Box: {}", size_of::>()); println!("Size of &dyn Trait: {}", size_of::<&dyn SomeTrait>()); - println!("Size of Rc: {}", size_of::>()); println!("Size of &[i32]: {}", size_of::<&[i32]>()); println!("Size of &[&dyn Trait]: {}", size_of::<&[&dyn SomeTrait]>()); println!("Size of [i32; 10]: {}", size_of::<[i32; 10]>()); println!("Size of [&dyn Trait; 10]: {}", size_of::<[&dyn SomeTrait; 10]>()); } -``` \ No newline at end of file +``` + +As you see from the output after running this, the sizes of the references varies. +Most are 8 bytes (which is a pointer size on 64 bit systems), but some are 16 +bytes. + +The reason for this varies. For example, in the case of `&[i32]` the first 8 +bytes is the pointer to the first element in an array, and the second 8 bytes is +the length of the slice. + +The one we'll concern ourselves about is the references to traits, or +_trait objects_ as they're called in Rust when we're referring to any type that +implements this trait. `&dyn SomeTrait` is a pointer to such a _trait object_ +and as you see it has a size of 16 bytes. + +- The first 8 bytes points to the `data` for the trait object +- The second 8 bytes points to the `vtable` for the trait object + +When implementing a `Trait` you pass in `self` as the first parameter. The +`data` pointer is a pointer to this `self` object which is then passed in to +the functions in the `vtable`. + +Let's explain this in code instead of words by implementing our own trait +object from these parts: + +```rust, editable +// A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr) +trait Test { + fn add(&self) -> i32; + fn sub(&self) -> i32; + fn mul(&self) -> i32; +} + +// This will represent our home brewn fat pointer to a trait object +#[repr(C)] +struct FatPointer<'a> { + /// A reference is a pointer to an instantiated `Data` instance + data: &'a mut Data, + /// Since we need to pass in literal values like length and alignment it's + /// easiest for us to convert pointers to usize-integers instead of the other way around. + vtable: *const usize, +} + +// This is the data in our trait object. It's just two numbers we want to operate on. +struct Data { + a: i32, + b: i32, +} + +// ====== function definitions ====== +fn add(s: &Data) -> i32 { + s.a + s.b +} +fn sub(s: &Data) -> i32 { + s.a - s.b +} +fn mul(s: &Data) -> i32 { + s.a * s.b +} + +fn main() { + let mut data = Data {a: 3, b: 2}; + // vtable is like special purpose array of pointer-length types with a fixed + // format where the three first values has a special meaning like the + // length of the array is encoded in the array itself as the second value. + let vtable = vec![ + 0, // pointer to `Drop` (which we're not implementing here) + 6, // lenght of vtable + 8, // alignment + // we need to make sure we add these in the same order as defined in the Trait. + // Try changing the order of add and sub and see what happens. + add as usize, // function pointer + sub as usize, // function pointer + mul as usize, // function pointer + ]; + + let fat_pointer = FatPointer { data: &mut data, vtable: vtable.as_ptr()}; + let test = unsafe { std::mem::transmute::(fat_pointer) }; + + // And voalá, it's now a trait object we can call methods on + println!("Add: 3 + 2 = {}", test.add()); + println!("Sub: 3 - 2 = {}", test.sub()); + println!("Mul: 3 * 2 = {}", test.mul()); +} + +``` + +If you run this code by pressing the "play" button at the top you'll se it +outputs just what we expect. This code example is editable so you can change it +and run it to see what happens. + +The reason we go through this will be apparent when we implement our own +`Waker` later on since we'll actually set up a `vtable` like we do here to +define methods like `wake`. + +Let's move on to actually implement an example. \ No newline at end of file