summaryrefslogtreecommitdiff
path: root/content/blog/2019-02-11-handmade-rust-2-unq.md
diff options
context:
space:
mode:
Diffstat (limited to 'content/blog/2019-02-11-handmade-rust-2-unq.md')
-rw-r--r--content/blog/2019-02-11-handmade-rust-2-unq.md107
1 files changed, 107 insertions, 0 deletions
diff --git a/content/blog/2019-02-11-handmade-rust-2-unq.md b/content/blog/2019-02-11-handmade-rust-2-unq.md
new file mode 100644
index 0000000..cd6b213
--- /dev/null
+++ b/content/blog/2019-02-11-handmade-rust-2-unq.md
@@ -0,0 +1,107 @@
++++
+title = "Handmade Rust Part 2: Unq, an allocator-aware Box"
+[taxonomies]
+tags = ["Rust", "Programming"]
++++
+
+In the Rust standard library, `Box` is a RAII wrapper for an object on the heap. It's actually a special type that's not implemented purely in the library, but also use special features called lang items. It uses the global allocator to allocate its memory. We want a similar type that also has an allocator associated to it. We'll call it `Unq`, which mirror C++'s `unique_ptr`.
+
+<!-- more -->
+
+Defining `Unq`
+=====================
+
+```rust
+pub struct Unq<T: ?Sized, A: Allocator>
+{
+ ptr: NonNull<T>,
+ alloc: A,
+}
+```
+
+`T` is the wrapped type. It is optionally sized because we want to allow trait objects to be wrapped in there. For those unfamiliar with traits, they are basically Rust's interfaces, but they allow both static and dynamic dispatch. A trait object is an object and its accompanying virtual functions table that allows dynamic dispatch. They use the syntax `dyn Trait`. For example, `&mut dyn Trait` and `Unq<dyn Trait, A>` are both trait objects.
+
+Creating and dropping a `Unq`
+===================
+
+When creating a `Unq`, we pass to it a value of type `T` and an allocator. The necessary memory is allocated. Then we can write the value using `core::ptr::write`, which moves a value into a potentially uninitialized memory location.
+
+*Edit 2019-03-25*: I originally used `forget(replace(ptr, value))` to write the value, but [@myrrlyn](https://twitter.com/myrrlyn) pointed out that `ptr::write` is actually a better choice here because it doesn't require initialized memory like `replace` does.
+
+```rust
+impl<T, A: Allocator> Unq<T, A>
+{
+ pub fn new(value: T, mut alloc: A) -> Self
+ {
+ let mut ptr = /* Allocate memory */;
+
+ unsafe
+ {
+ core::ptr::write(ptr.as_mut(), value);
+ }
+
+ // ...
+ }
+}
+```
+
+Note that this means that the value first has to live on the stack before being copied to the heap. Maybe in the future we'll implement a method to create a `Unq` from a type that implements `Default` in place.
+
+When dropping the `Unq`, before deallocating the memory, we first want to drop the contained value. To do so we could use `core::mem::drop`, but we would first need to copy the value, which we don't want. Instead we can use `core::ptr::drop_in_place` to drop the value at a given memory location.
+
+```rust
+impl<T: ?Sized, A: Allocator> Drop for Unq<T, A>
+{
+ fn drop(&mut self)
+ {
+ unsafe
+ {
+ drop_in_place(self.ptr.as_ptr());
+ // Deallocate
+ }
+ }
+}
+```
+
+Making `Unq` usable
+=========================
+
+We can now create a `Unq`, but then there is no way to access the underlying value. To do so, we'll implement `core::ops::Deref` and `core::ops::DerefMut`. Those are basically operator overloadings that tells the compiler what to do when dereferencing a `Unq`. By returning `self.ptr.as_ref()` and `self.ptr.as_mut()` respectively, we allow the user to access members and methods of the wrapped object simply by writing `my_unq.member = new_value;` or `my_unq.do_something();`.
+
+Finally, we need `Unq` to be able to do dynamic dispatch, so we need to tell the compiler that `Unq` is covariant over `T`, meaning that when `T` is a subtype of `U`, `Unq<T>` is a subtype of `Unq<U>`. We'll be able to write something like this:
+
+```rust
+trait MyTrait
+{
+ fn do_something(&self);
+}
+
+struct MyObject;
+
+impl MyTrait for MyObject
+{
+ fn do_something(&self) {}
+}
+
+fn foo()
+{
+ let alloc = ...;
+ let my_unq : Unq<dyn MyTrait, ...> = Unq::new(MyObject{}, ...);
+ my_unq.do_something();
+}
+```
+
+Do to so, we need to implement `core::ops::CoerceUnsized` which is not stable (so we have to use the nightly toolchain).
+
+```rust
+impl<T: ?Sized + Unsize<U>, U: ?Sized, A: Allocator>
+ CoerceUnsized<Unq<U, A>>
+ for Unq<T, A> {}
+```
+
+In this snippet, `T` would be `MyObject` and `U` `dyn MyTrait`. `T: Unsize<U>` basically means that `T` can be seen as the dynamically sized type `U`. I won't go into too much details because I'm not sure I understand all of it myself, but there are plenty of resources to learn about DSTs.
+
+Wrap up
+=====================
+
+The current implementation of `Unq` is available here: [fnd::unique::Unq](https://github.com/stevenlr/HandmadeRust/blob/fb4084420c3263d800b1674527403095ddea20ea/fnd/src/unique.rs). I'm sure we'll have to add some stuff to it in the future, but this is the minimal implementation for now. Next time we'll implement a growable array type. As always don't hesitate to ping me on [Twitter](https://twitter.com/steven_lr).