<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>jamie-blogs</title>
    <link>https://rant.li/jamie-blogs/</link>
    <description>the word &#34;amateur&#34; descends from Latin &#34;amare,&#34; meaning to love</description>
    <pubDate>Fri, 31 Jul 2026 20:35:32 +0000</pubDate>
    <item>
      <title>Higher-Order-Types and You</title>
      <link>https://rant.li/jamie-blogs/higher-order-types-and-you</link>
      <description>&lt;![CDATA[alt. title: Forcefully Haskell-fying Rust, for Fun and Profit&#xA;!--more--&#xA; If you&#39;re as much of a rust fanatic as I am you&#39;ve probably realized that generics are really cool. In my opinion, rust&#39;s composition-based parametric polymorphism is one of the best things its inherited from its functional roots. Today I want to talk about what it left out, and hopefully expand how you think about type systems.&#xA;!--pad--&#xA;Proper Types&#xA;When we talk about types, we often mean something like this:&#xA;struct MyData {&#xA;&#x9;name: String,&#xA;&#x9;val: i32&#xA;}&#xA;This is a simple collection of data. The properties of this type are completely static— name will always be a String and val will always be an i32. We call these proper types. They are in a fixed and usable state; in other words, we know everything we can know about them.&#xA;!--pad--&#xA;Types.. kinda&#xA;Lets generalize this type to hold any value we could want:&#xA;struct MyDataT {&#xA;&#x9;name: String,&#xA;&#x9;val: T&#xA;}&#xA;Now this type can composed of any other type, and the &#34;type&#34; T is actually just a placeholder for what we could later place inside this.&#xA;&#xA;So.. this clearly doesn&#39;t fit the definition of a proper type that we previously defined, so what is it? Lets try analyzing what the compiler does with this type under the hood. Lets take the following code:&#xA;let dataa = MyData::new(&#34;asdf&#34;.into(), 12);&#xA;let datab = MyData::new(&#34;asdf&#34;.into(), true);&#xA;And try searching for a MyData symbol:&#xA;nm target/debug/higherorderstypes | grep MyData&#xA;And we should see some output like this:&#xA;ZN4core3ptr59dropinplace$LT$higherorderstypes..MyData$LT$i32$GT$$GT$17h08f26c464aa8b7e0E&#xA;ZN4core3ptr60dropinplace$LT$higherorderstypes..MyData$LT$bool$GT$$GT$17hf107324ff7a39b31E&#xA;Bingo!&#xA;&#xA;These lines say MyData$LT$i32 and MyData$LT$bool, so we can see that rust will actually generate concrete types for whatever gets used inside the generic! &#xA;&#xA;Lets generalize this idea back into a type theory context: we can say that MyData is a special kind of type, that takes a proper type, and returns a proper type.&#xA;!--pad--&#xA;Improper Types&#xA;Just with higher-order-functions, we can see that this type is a level above concrete types. These are called kinds. A kind is a type constructor that takes a type, and produces a new type. &#xA;&#xA;So now that we have a new concept to play with, what can we do with it? Well, lets see what Haskell does with it:&#xA;class Functor f where&#xA;    fmap :: (a -  b) -  f a -  f b&#xA;If you don&#39;t know any haskell, this likely looks very arcane to you. Lets write it in the style of rust:&#xA;trait Functor {&#xA;&#x9;fn fmapA, B(&#xA;&#x9;&#x9;f: impl Fn(A) -  B, &#xA;&#x9;&#x9;SelfA&#xA;&#x9;) -  SelfB;&#xA;}&#xA;So fmap takes a function of A -  B and a SelfA, maps said function on the inner value, and returns a SelfB. If you try to compile this, you&#39;ll get an error akin to &#34;Self takes 0 parameters.&#34; Rust doesn&#39;t have a notion of kinds, only parametric polymorphism, so we cant refer to a kind as something on its own. So making this trait will be a little messier, but we can still do it:&#xA;trait FunctorA, B: Sized {&#xA;    //type Ma; will be Self&#xA;    type Mb;&#xA;    &#xA;    fn fmap(f: impl Fn(A) -  B, rhs: Self) -  Self::Mb;&#xA;}&#xA;We can&#39;t guarantee that Mb will be SelfB, but we can at least now use this trait on Option.&#xA;implA, B FunctorA, B for OptionA {&#xA;    type Mb = OptionB;&#xA;&#xA;    fn fmap(f: impl Fn(A) -  B, rhs: Self) -  Self::Mb {&#xA;        match rhs {&#xA;            Some(v) =  Some(f(v)),&#xA;            None =  None,&#xA;        }&#xA;    }&#xA;}&#xA;Actually, while we&#39;re rusti-fying this, lets turn it into a method.&#xA;fn fmap(self, f: impl Fn(A) -  B) -  Self::Mb {&#xA;    match self {&#xA;        Some(v) =  Some(f(v)),&#xA;        None =  None,&#xA;    }&#xA;}&#xA;You&#39;ve most definitely realized at this point that this is identical to a method that already exists, map. If you squint a little, you&#39;ll even notice that lots of rust methods come from haskell typeclasses!&#xA;&#xA;Since haskell supports kinds, its able to soundly model these sorts of actions. Its so capable that haskell actually uses a monad to introduce side effects into a purely functional language! Its monads all the way down.&#xA;&#xA;!--pad--&#xA;Footnotes&#xA;in this case you can see that rust is actually generating the dropinplace function, so that it can call any destructors when these variables go out of scope&#xA;there are actually a lot of ways you could go about solving this, decided to go with a relatively  naive/straightforward path&#xA;its probably more correct to say &#34;haskell supports curried type constructors&#34;&#xA;i lightly refered to a type theory page) (mostly just to make sure i was using the right words) on wikipedia when i was writing this, check that out for more fp mumbo-jumbo!&#xA;&#xA;maybe sometime ill write more about monads for real, but thats a whole thing that i didnt want to get into. the point of this article was to bridge the gap and expose some of rusts fp roots, and why i think its so cool. thnx for reading! &lt;3]]&gt;</description>
      <content:encoded><![CDATA[<p><em>alt. title: Forcefully Haskell-fying Rust, for Fun and Profit</em>

 If you&#39;re as much of a rust fanatic as I am you&#39;ve probably realized that generics are really cool. In my opinion, rust&#39;s composition-based parametric polymorphism is one of the best things its inherited from its functional roots. Today I want to talk about what it left out, and hopefully expand how you think about type systems.
</p>

<h2 id="proper-types">Proper Types</h2>

<p>When we talk about types, we often mean something like this:</p>

<pre><code class="language-rust">struct MyData {
	name: String,
	val: i32
}
</code></pre>

<p>This is a simple collection of data. The properties of this type are completely static— <code>name</code> will always be a <code>String</code> and <code>val</code> will always be an <code>i32</code>. We call these <em>proper types</em>. They are in a fixed and usable state; in other words, we know everything we can know about them.
</p>

<h2 id="types-kinda">Types.. kinda</h2>

<p>Lets generalize this type to hold any value we could want:</p>

<pre><code class="language-rust">struct MyData&lt;T&gt; {
	name: String,
	val: T
}
</code></pre>

<p>Now this type can composed of any other type, and the “type” <code>T</code> is actually just a placeholder for what we could later place inside this.</p>

<p>So.. this clearly doesn&#39;t fit the definition of a <em>proper type</em> that we previously defined, so what is it? Lets try analyzing what the compiler does with this type under the hood. Lets take the following code:</p>

<pre><code class="language-rust">let data_a = MyData::new(&#34;asdf&#34;.into(), 12);
let data_b = MyData::new(&#34;asdf&#34;.into(), true);
</code></pre>

<p>And try searching for a <code>MyData</code> symbol:</p>

<pre><code class="language-bash">nm target/debug/higher_orders_types | grep MyData
</code></pre>

<p>And we should see some output like this:</p>

<pre><code class="language-bash">_ZN4core3ptr59drop_in_place$LT$higher_orders_types..MyData$LT$i32$GT$$GT$17h08f26c464aa8b7e0E
_ZN4core3ptr60drop_in_place$LT$higher_orders_types..MyData$LT$bool$GT$$GT$17hf107324ff7a39b31E
</code></pre>

<p>Bingo!</p>

<p>These lines say <code>MyData$LT$i32</code> and <code>MyData$LT$bool</code>, so we can see that rust will actually generate concrete types for whatever gets used inside the generic! [^1]</p>

<p>Lets generalize this idea back into a type theory context: we can say that <code>MyData</code> is a special kind of type, that takes a <em>proper type</em>, and returns a <em>proper type</em>.
</p>

<h2 id="improper-types">Improper Types</h2>

<p>Just with higher-order-functions, we can see that this type is a level above concrete types. These are called <em>kinds</em>. A kind is a type constructor that takes a type, and produces a new type.</p>

<p>So now that we have a new concept to play with, what can we do with it? Well, lets see what Haskell does with it:</p>

<pre><code class="language-haskell">class Functor f where
    fmap :: (a -&gt; b) -&gt; f a -&gt; f b
</code></pre>

<p>If you don&#39;t know any haskell, this likely looks very arcane to you. Lets write it in the style of rust:</p>

<pre><code class="language-rust">trait Functor {
	fn fmap&lt;A, B&gt;(
		f: impl Fn(A) -&gt; B, 
		Self&lt;A&gt;
	) -&gt; Self&lt;B&gt;;
}
</code></pre>

<p>So <code>fmap</code> takes a function of <code>A -&gt; B</code> and a <code>Self&lt;A&gt;</code>, maps said function on the inner value, and returns a <code>Self&lt;B&gt;</code>. If you try to compile this, you&#39;ll get an error akin to “Self takes 0 parameters.” Rust doesn&#39;t have a notion of kinds, only parametric polymorphism, so we cant refer to a kind as something on its own. So making this trait will be a little messier, but we can still do it:</p>

<pre><code class="language-rust">trait Functor&lt;A, B&gt;: Sized {
    //type Ma; will be Self
    type Mb;
    
    fn fmap(f: impl Fn(A) -&gt; B, rhs: Self) -&gt; Self::Mb;
}
</code></pre>

<p>We can&#39;t guarantee that <code>Mb</code> will be <code>Self&lt;B&gt;</code>[^2], but we can at least now use this trait on <code>Option</code>.</p>

<pre><code class="language-rust">impl&lt;A, B&gt; Functor&lt;A, B&gt; for Option&lt;A&gt; {
    type Mb = Option&lt;B&gt;;

    fn fmap(f: impl Fn(A) -&gt; B, rhs: Self) -&gt; Self::Mb {
        match rhs {
            Some(v) =&gt; Some(f(v)),
            None =&gt; None,
        }
    }
}
</code></pre>

<p>Actually, while we&#39;re rusti-fying this, lets turn it into a method.</p>

<pre><code class="language-rust">fn fmap(self, f: impl Fn(A) -&gt; B) -&gt; Self::Mb {
    match self {
        Some(v) =&gt; Some(f(v)),
        None =&gt; None,
    }
}
</code></pre>

<p>You&#39;ve most definitely realized at this point that this is identical to a method that already exists, <a href="https://doc.rust-lang.org/std/option/enum.Option.html#method.map" rel="nofollow">map</a>. If you squint a little, you&#39;ll even notice that lots of <a href="https://doc.rust-lang.org/std/option/enum.Option.html#method.and_then" rel="nofollow">rust methods</a> come from <a href="https://hackage.haskell.org/package/base-4.19.1.0/docs/Control-Monad.html#v:-62--62--61-" rel="nofollow">haskell typeclasses</a>!</p>

<p>Since haskell supports kinds[^3], its able to soundly model these sorts of actions. Its so capable that haskell actually <a href="https://hackage.haskell.org/package/base-4.19.1.0/docs/System-IO.html#t:IO" rel="nofollow">uses a monad</a> to introduce side effects into a purely functional language! <em>Its monads all the way down.</em></p>



<h3 id="footnotes"><em>Footnotes</em></h3>
<ol><li>in this case you can see that rust is actually generating the <a href="https://doc.rust-lang.org/core/ptr/fn.drop_in_place.html" rel="nofollow"><code>drop_in_place</code></a> function, so that it can call any destructors when these variables go out of scope</li>
<li>there are actually a lot of ways you could go about solving this, decided to go with a relatively  naive/straightforward path</li>
<li>its probably more correct to say “haskell supports curried type constructors”</li>
<li>i lightly refered to <a href="https://en.wikipedia.org/wiki/Kind_(type_theory)" rel="nofollow">a type theory page</a> (mostly just to make sure i was using the right words) on wikipedia when i was writing this, check that out for more fp mumbo-jumbo!</li></ol>

<p><em>maybe sometime ill write more about monads for real, but thats a whole thing that i didnt want to get into. the point of this article was to bridge the gap and expose some of rusts fp roots, and why i think its so cool. thnx for reading! &lt;3</em></p>
]]></content:encoded>
      <guid>https://rant.li/jamie-blogs/higher-order-types-and-you</guid>
      <pubDate>Sat, 24 Feb 2024 23:13:42 +0000</pubDate>
    </item>
    <item>
      <title>Demystifying Rust pt 2: Vec</title>
      <link>https://rant.li/jamie-blogs/demystifying-rust-pt-2-vec</link>
      <description>&lt;![CDATA[In the first part, we learned about the allocator api and how to use it. This will assume you&#39;ve read that part, and build off of knowledge from it.&#xA;!--more--&#xA;!--pad--&#xA;Structure&#xA;Heres our base:&#xA;pub struct VecT {&#xA;    ptr: NonNullT,&#xA;    len: usize,&#xA;    cap: usize,&#xA;}&#xA;&#xA;implT VecT {&#xA;    const LAYOUT: Layout = Layout::new::T();&#xA;    const GLOBAL: Global = Global;&#xA;    &#xA;    pub fn new() -  Self {&#xA;        Self { ptr: NonNull::dangling(), len: 0, cap: 0 }&#xA;    }&#xA;}&#xA;A couple things are structured a little differently than before.&#xA;&#xA;we have addictional len and cap fields&#xA;a Layout is generated as an associated constant for any given type&#xA;We get a &#34;hook&#34; to global so that we dont need to call default() every time&#xA;&#xA;This way we don&#39;t need to call Global::default() or Layout::new::T() every time we want to use them. For new, we get a &#34;dangling&#34; pointer, or one that doesn&#39;t point to allocated memory, just so we have something to put in our struct. This is okay, because no elements will be read if cap, and by extention len, is 0.&#xA;!--pad--&#xA;Push&#xA;The first real code we&#39;ll write is for adding an element to the end of the block. Lets map out what should happen.&#xA;pub fn push(&amp;mut self, x: T) {&#xA;&#x9;if self.len == self.cap {&#xA;&#x9;&#x9;//allocate more memory&#xA;&#x9;&#x9;//increase self.cap&#xA;&#x9;}&#xA;&#xA;&#x9;//increase self.len&#xA;&#x9;//write x into self[self.len-1]&#xA;}&#xA;First, lets handle reallocating our vector when it needs to grow. I&#39;ll be using the alloclayoutextra feature for better layout syntax, but this isnt necessary.&#xA;The Allocator trait has a method called grow, which takes a pointer, its layout, and the new layout to fit.&#xA;// repeat() will error on overflow&#xA;// also returns a tuple with other data&#xA;// that we dont need&#xA;let currlay = Self::LAYOUT.repeat(self.cap).unwrap().0;&#xA;let newlay = Self::LAYOUT.repeat(self.cap+1).unwrap().0;&#xA;&#xA;let newptr = unsafe {&#xA;&#x9;Self::GLOBAL.grow(&#xA;&#x9;&#x9;self.ptr.cast(),&#xA;&#x9;&#x9;currlay,&#xA;&#x9;&#x9;newlay,&#xA;&#x9;)&#xA;};&#xA;The repeat() method creates a new layout based on sizeof::T()  n, so we use it to create layouts representative of our current and desired allocations.&#xA;&#xA;newptr is currently a result whos error type is equivalent to the pointer just being null, so lets unwrap it and update our data.&#xA;self.ptr = newptr.unwrap().cast();&#xA;self.cap += 1;&#xA;Actually, lets extract this procedure into a more generic function:&#xA;fn realloc(&amp;mut self, size: usize) {&#xA;&#x9;let currlay = Self::LAYOUT.repeat(self.cap).unwrap().0;&#xA;&#x9;let newlay = Self::LAYOUT.repeat(self.cap+size).unwrap().0;&#xA;&#x9;&#xA;&#x9;let newptr = unsafe { Self::GLOBAL.grow(&#xA;&#x9;&#x9;self.ptr.cast(),&#xA;&#x9;&#x9;currlay,&#xA;&#x9;&#x9;newlay,&#xA;&#x9;)};&#xA;&#x9;&#xA;&#x9;self.ptr = newptr.unwrap().cast();&#xA;&#x9;self.cap += size;&#x9;&#xA;}&#xA;Thats the hard part, so lets go back to our push function. Now the rest of the function is pretty self-explanatory:&#xA;pub fn push(&amp;mut self, x: T) {&#xA;&#x9;if self.len == self.cap {&#xA;&#x9;&#x9;self.realloc(1);&#xA;&#x9;}&#xA;&#xA;&#x9;//increase self.len&#xA;&#x9;//write x into self[self.len-1]&#xA;&#x9;self.len += 1;&#xA;&#x9;unsafe { self.ptr.add(self.len-1).write(x) };&#xA;}&#xA;!--pad--&#xA;Pop&#xA;Next lets make a function to remove the last element in the vector.&#xA;pub fn pop(&amp;mut self) -  OptionT {&#xA;&#x9;if self.len == 0 { return None }&#xA;&#x9;self.len -= 1;&#xA;}&#xA;Now we need to get the last element. We want to preserve move semantics, but this isn&#39;t safely possible with raw pointers. &#xA;!--pad--&#xA;A Note on Memory Safety&#xA;When we read the data from the pointer, we effectively make a bitwise copy of that data. As is discussed in the Copy trait docs, doing this when you shouldn&#39;t can lead to some very serious problems. We need to guarantee that only 1 instance of that data can ever be accessed safely .&#xA;&#xA;So, if we want to move data from the heap to the stack, we need to make sure that the heap copy is dead in the water. For that rule to be violated, self.len needs to be increased without writing data to the expanded memory. We aren&#39;t responsible for memory past self.len, and it could be literally anything. Therefore, we need to make sure self.len can only be changed when we take special precautions . Luckily our push and pop methods will do exactly that.&#xA;&#xA;So with that out of the way, lets write our 3 lines of code that necessitated that divergence:&#xA;let last = unsafe { self.ptr.add(self.len) };&#xA;self.len -= 1;&#xA;&#xA;//moves out of pointer&#xA;Some(unsafe { last.read() })&#xA;Theres our core functionality of a vector! Lets write a couple index functions:&#xA;pub fn get(&amp;self, idx: usize) -  Option&amp;T {&#xA;    if self.len == 0 { return None }&#xA;&#xA;    Some(unsafe { self.ptr.add(idx).asref() })&#xA;}&#xA;pub fn getmut(&amp;mut self, idx: usize) -  Option&amp;mut T {&#xA;    if self.len == 0 { return None }&#xA;    Some(unsafe { self.ptr.add(idx).asmut() })&#xA;}&#xA;And quickly implement drop:&#xA;implT Drop for VecT {&#xA;    fn drop(&amp;mut self) {&#xA;        for i in 0..self.len {&#xA;&#x9;&#x9;//call all destructors&#xA;        &#x9;unsafe { self.ptr.add(i).dropin_place() };&#xA;        }&#xA;    }&#xA;}&#xA;And we&#39;ve recreated the most important functions of Vec!&#xA;&#xA;we dont need to make it completely impossible, we just need to make sure that safe rust can&#39;t&#xA;it wouldnt be a bad idea to consider changing self.len unsafe, in fact std&#39;s Vec does this&#xA;&#xA;!--pad--&#xA;thanks for reading part 2! This was super fun to write, and i might make a part 3 to implement a bunch of common traits on our vec, lmk if you think that would be cool!*]]&gt;</description>
      <content:encoded><![CDATA[<p>In the <a href="https://rant.li/jamie-blogs/demystifying-rust-implementing-box-from-scratch-9n6y" rel="nofollow">first part</a>, we learned about the allocator api and how to use it. This will assume you&#39;ve read that part, and build off of knowledge from it.

</p>

<h2 id="structure">Structure</h2>

<p>Heres our base:</p>

<pre><code class="language-rust">pub struct Vec&lt;T&gt; {
    ptr: NonNull&lt;T&gt;,
    len: usize,
    cap: usize,
}

impl&lt;T&gt; Vec&lt;T&gt; {
    const LAYOUT: Layout = Layout::new::&lt;T&gt;();
    const GLOBAL: Global = Global;
    
    pub fn new() -&gt; Self {
        Self { ptr: NonNull::dangling(), len: 0, cap: 0 }
    }
}
</code></pre>

<p>A couple things are structured a little differently than before.</p>
<ol><li>we have addictional <code>len</code> and <code>cap</code> fields</li>
<li>a <code>Layout</code> is generated as an associated constant for any given type</li>
<li>We get a “hook” to global so that we dont need to call <code>default()</code> every time</li></ol>

<p>This way we don&#39;t need to call <code>Global::default()</code> or <code>Layout::new::&lt;T&gt;()</code> every time we want to use them. For <code>new</code>, we get a “dangling” pointer, or one that doesn&#39;t point to allocated memory, just so we have something to put in our struct. This is okay, because no elements will be read if <code>cap</code>, and by extention <code>len</code>, is 0.
</p>

<h2 id="push">Push</h2>

<p>The first real code we&#39;ll write is for adding an element to the end of the block. Lets map out what should happen.</p>

<pre><code class="language-rust">pub fn push(&amp;mut self, x: T) {
	if self.len == self.cap {
		//allocate more memory
		//increase self.cap
	}

	//increase self.len
	//write x into self[self.len-1]
}
</code></pre>

<p>First, lets handle reallocating our vector when it needs to grow. I&#39;ll be using the <code>alloc_layout_extra</code> feature for better layout syntax, but this isnt necessary.
The <code>Allocator</code> trait has a method called <code>grow</code>, which takes a pointer, its layout, and the new layout to fit.</p>

<pre><code class="language-rust">// repeat() will error on overflow
// also returns a tuple with other data
// that we dont need
let curr_lay = Self::LAYOUT.repeat(self.cap).unwrap().0;
let new_lay = Self::LAYOUT.repeat(self.cap+1).unwrap().0;

let new_ptr = unsafe {
	Self::GLOBAL.grow(
		self.ptr.cast(),
		curr_lay,
		new_lay,
	)
};
</code></pre>

<p>The <code>repeat()</code> method creates a new layout based on <code>size_of::&lt;T&gt;() * n</code>, so we use it to create layouts representative of our current and desired allocations.</p>

<p><code>new_ptr</code> is currently a result whos error type is equivalent to the pointer just being null, so lets unwrap it and update our data.</p>

<pre><code class="language-rust">self.ptr = new_ptr.unwrap().cast();
self.cap += 1;
</code></pre>

<p>Actually, lets extract this procedure into a more generic function:</p>

<pre><code class="language-rust">fn realloc(&amp;mut self, size: usize) {
	let curr_lay = Self::LAYOUT.repeat(self.cap).unwrap().0;
	let new_lay = Self::LAYOUT.repeat(self.cap+size).unwrap().0;
	
	let new_ptr = unsafe { Self::GLOBAL.grow(
		self.ptr.cast(),
		curr_lay,
		new_lay,
	)};
	
	self.ptr = new_ptr.unwrap().cast();
	self.cap += size;	
}
</code></pre>

<p>Thats the hard part, so lets go back to our push function. Now the rest of the function is pretty self-explanatory:</p>

<pre><code class="language-rust">pub fn push(&amp;mut self, x: T) {
	if self.len == self.cap {
		self.realloc(1);
	}

	//increase self.len
	//write x into self[self.len-1]
	self.len += 1;
	unsafe { self.ptr.add(self.len-1).write(x) };
}
</code></pre>



<h2 id="pop">Pop</h2>

<p>Next lets make a function to remove the last element in the vector.</p>

<pre><code class="language-rust">pub fn pop(&amp;mut self) -&gt; Option&lt;T&gt; {
	if self.len == 0 { return None }
	self.len -= 1;
}
</code></pre>

<p>Now we need to get the last element. We want to preserve move semantics, but this isn&#39;t safely possible with raw pointers.
</p>

<h3 id="a-note-on-memory-safety">A Note on Memory Safety</h3>

<p>When we read the data from the pointer, we effectively make a bitwise copy of that data. As is discussed in the <a href="https://doc.rust-lang.org/std/marker/trait.Copy.html#when-cant-my-type-be-copy" rel="nofollow">Copy trait</a> docs, doing this when you shouldn&#39;t can lead to some very serious problems. We need to guarantee that only 1 instance of that data can ever be accessed safely [^1].</p>

<p>So, if we want to move data from the heap to the stack, we need to make sure that the heap copy is dead in the water. For that rule to be violated, <code>self.len</code> needs to be increased <em>without</em> writing data to the expanded memory. We aren&#39;t responsible for memory past <code>self.len</code>, and it could be literally anything. Therefore, we need to make sure <code>self.len</code> can only be changed when we take special precautions [^2]. Luckily our <code>push</code> and <code>pop</code> methods will do exactly that.</p>

<p>So with that out of the way, lets write our 3 lines of code that necessitated that divergence:</p>

<pre><code class="language-rust">let last = unsafe { self.ptr.add(self.len) };
self.len -= 1;

//moves out of pointer
Some(unsafe { last.read() })
</code></pre>

<p>Theres our core functionality of a vector! Lets write a couple index functions:</p>

<pre><code class="language-rust">pub fn get(&amp;self, idx: usize) -&gt; Option&lt;&amp;T&gt; {
    if self.len == 0 { return None }

    Some(unsafe { self.ptr.add(idx).as_ref() })
}
pub fn get_mut(&amp;mut self, idx: usize) -&gt; Option&lt;&amp;mut T&gt; {
    if self.len == 0 { return None }
    Some(unsafe { self.ptr.add(idx).as_mut() })
}
</code></pre>

<p>And quickly implement drop:</p>

<pre><code class="language-rust">impl&lt;T&gt; Drop for Vec&lt;T&gt; {
    fn drop(&amp;mut self) {
        for i in 0..self.len {
		//call all destructors
        	unsafe { self.ptr.add(i).drop_in_place() };
        }
    }
}
</code></pre>

<p>And we&#39;ve recreated the most important functions of <code>Vec</code>!</p>
<ol><li>we dont need to make it completely impossible, we just need to make sure that <em>safe</em> rust can&#39;t</li>
<li>it wouldnt be a bad idea to consider changing <code>self.len</code> unsafe, in fact <a href="https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#1921" rel="nofollow">std&#39;s Vec</a> does this</li></ol>



<p><em>thanks for reading part 2! This was super fun to write, and i might make a part 3 to implement a bunch of common traits on our vec, lmk if you think that would be cool!</em></p>
]]></content:encoded>
      <guid>https://rant.li/jamie-blogs/demystifying-rust-pt-2-vec</guid>
      <pubDate>Mon, 19 Feb 2024 17:57:15 +0000</pubDate>
    </item>
    <item>
      <title>Demystifying Rust: Implementing Box from Scratch</title>
      <link>https://rant.li/jamie-blogs/demystifying-rust-implementing-box-from-scratch-9n6y</link>
      <description>&lt;![CDATA[For a language that touts its low level capabilities, rust has a very rich and complete standard library. In fact, you effectively never directly interact with the allocator, and its actually very discouraged.&#xA;!--more--&#xA;The reason for that, of course, is that its pretty easy to mess up. As such, allocation management is perfect for showing off the advantage of the unsafe system: it explicitly marks when you should be careful and gives you a very small scope to prove the soundness of. Its much easier to prove 2 or 3 lines of code will never segfault, versus an entire C++ class.&#xA;Smart pointers (Box, Arc, Mutex) utilize this tool to create sound abstractions over allocation management, and in this article we&#39;ll go over how. Lets begin!&#xA;!--pad--&#xA;Step 0: Learn the Allocator&#xA;Rust&#39;s allocator API is hidden behind the aptly named feature:&#xA;![feature(allocatorapi)]&#xA;![feature(nonnullconvenience)] //more methods for a type later on&#xA;This gives us a lot of things, but for this we just need to worry about Global, Layout, and the Allocator trait:&#xA;use std::alloc::{Layout, Global, Allocator};&#xA;Lets get started and re-invent Box: a type that allocates a single item. All it is is a pointer!&#xA;pub struct BoxT(mut T);&#xA;&#xA;implT BoxT {&#xA;    pub fn new(item: T) -  Self {&#xA;        todo!()&#xA;    }&#xA;}&#xA;The thing we interface with is Global. Because of the way it works, there isn&#39;t a single static to refer to, we need to just use default() to get a handle to it .&#xA;Lets take a look at the allocator trait. Immediately, we find the most important function:&#xA;fn allocate(&amp;self, layout: Layout) -  ResultNonNull&lt;[u8], AllocError  This takes something called a Layout, and returns something called NonNull. Lets cover these.&#xA;!--pad--&#xA;The Layout Type&#xA;Theres actually a lot more info about a pointer than just the exact address it points to. We also need to know how big the item is, so that we know how many bytes to read. Usually this is static and provided by the type, but with dynamically sized types like [T] it needs to be carried around with the pointer itself (something called a &#34;wide-pointer&#34;). On top of that, pointers have something called an alignment, which requires that they begin on an address divisible by a power of two.&#xA;Layout carries all of this info for us, and actually provides a method to generate it from a type:&#xA;let layout = Layout::new::T();&#xA;!--pad--&#xA;The NonNull Type&#xA;Pointers shouldn&#39;t be null. Rust provides functional error handling schemes like Option that are very preferred over sentinel values for a multitude of reasons. This leads to the situation where OxO is a representable value, but should never be constructed. NonNull enforces this invariant: it asserts that it will never be null. The main caveat of Option is that we need to carry along another entire boolean byte just to keep track of which variant it is. However, with NonNull we now have a forbidden value that the compiler promises will never exist! As such, NonNull provides whats called niche optimization: Option will use the forbidden 0x0 value as the discriminant of None, and means that it doesn&#39;t need any more size than the type itself. Cool stuff!&#xA;asserteq!(sizeof::NonNull&lt;u8  (), sizeof::Option&lt;NonNull&lt;u8    ());&#xA;Lets change the pointer type in our box, as it&#39;ll always be valid.&#xA;pub struct BoxT(NonNullT);&#xA;!--pad--&#xA;Getting Memory&#xA;Back to the function! Lets get the allocator and give it a layout of our type:&#xA;let layout = Layout::new::T();&#xA;let ptr = Global::default().allocate(layout).unwrap();&#xA;let ptr = ptr.cast::T();&#xA;Now we have a pointer to some of our own data on the heap! First thing you&#39;ll notice is that we actually need to manually cast the pointer to the type we need, as allocate simply returns a slice of bytes. Lets not forget to actually write our data into that allocation:&#xA;unsafe { ptr.write(item) };&#xA;This is unsafe as we need to guarantee that we are allowed to write to that space in memory. Since we just allocated that memory with a layout generated from T, we know that this is okay.&#xA;!--pad--&#xA;Getting rid of the Memory&#xA;We have a situation now where the default drop actually doesn&#39;t suffice. All rust does right now is delete the pointer when the box gets dropped. But the allocator doesn&#39;t know that happened! This is called a memory leak, where data has been allocated on the heap, and all pointers to that memory are destroyed without reallocating. Our data is doomed to forever sit untouched on the heap, alone and forgotten.&#xA;Luckily, rust has destructors. Implementing the Drop trait lets us run arbitrary code before the data gets destroyed, so this is the perfect place to deallocate!&#xA;implT Drop for BoxT {&#xA;    fn drop(&amp;mut self) {&#xA;        unsafe { self.0.dropinplace() };&#xA;&#xA;        unsafe { &#xA;            Global::default().deallocate(&#xA;                self.0.cast(), Layout::new::T()&#xA;            ) &#xA;        };&#xA;    }&#xA;}&#xA;This is pretty similar to when we created the box, just in reverse. Once again, we need to provide a NonNullu8, which is why we call cast(), and the layout of our type. We also want to make sure that whatever deconstructor T has is run, with dropinplace. This method is unsafe because it doesn&#39;t actually remvove the data in that memory, it simply runs the Drop implementation. Reading that memory from that point forward is undefined behavior.&#xA;&#xA;And thats all she wrote! Along with a simple Deref impl, we&#39;ve created a safe abstraction for storing data on the heap!&#xA;implT BoxT {&#xA;    pub fn new(item: T) -  Self {&#xA;        let layout = Layout::new::T();&#xA;        let ptr = Global::default().allocate(layout).unwrap();&#xA;        let ptr = ptr.cast::T();&#xA;        unsafe { ptr.write(item) };&#xA;&#xA;        Self(ptr)&#xA;    }&#xA;}&#xA;&#xA;implT Drop for BoxT {&#xA;    fn drop(&amp;mut self) {&#xA;        unsafe { self.0.dropinplace() };&#xA;        unsafe { &#xA;            Global::default().deallocate(&#xA;                self.0.cast(), Layout::new::T()&#xA;            ) &#xA;        };&#xA;    }&#xA;}&#xA;&#xA;implT Deref for BoxT {&#xA;    type Target = T;&#xA;&#xA;    fn deref(&amp;self) -  &amp;Self::Target {&#xA;        //we know that this is safe because we can only get a Box from a safe function&#xA;        unsafe { self.0.as_ref() }&#xA;    }&#xA;}&#xA;&#xA;Alternatively, you could store the allocator in the struct, as it takes up no size.&#xA;Alignment is admittedly something I barely understand, I simply take it as a weird optimization that hardware does, and it really doesn&#39;t like when you break &#xA;it.&#xA;&#xA;thank you for reading &lt;3 i plan to write a part 2 soon that implements vec. i figured it would be better to teach the allocator api in its own smaller article. sorry to those who read this before i added the deallocation part. oops...*&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>For a language that touts its low level capabilities, rust has a very rich and complete standard library. In fact, you effectively never directly interact with the allocator, and its actually very discouraged.

The reason for that, of course, is that its pretty easy to mess up. As such, allocation management is perfect for showing off the advantage of the unsafe system: it explicitly marks when you should be careful and gives you a very small scope to prove the soundness of. Its much easier to prove 2 or 3 lines of code will never segfault, versus an entire C++ class.
Smart pointers (<code>Box</code>, <code>Arc</code>, <code>Mutex</code>) utilize this tool to create sound abstractions over allocation management, and in this article we&#39;ll go over how. Lets begin!
</p>

<h2 id="step-0-learn-the-allocator">Step 0: Learn the Allocator</h2>

<p>Rust&#39;s allocator API is hidden behind the aptly named feature:</p>

<pre><code class="language-rust">#![feature(allocator_api)]
#![feature(non_null_convenience)] //more methods for a type later on
</code></pre>

<p>This gives us a lot of things, but for this we just need to worry about <code>Global</code>, <code>Layout</code>, and the <code>Allocator</code> trait:</p>

<pre><code class="language-rust">use std::alloc::{Layout, Global, Allocator};
</code></pre>

<p>Lets get started and re-invent <code>Box</code>: a type that allocates a single item. All it is is a pointer!</p>

<pre><code class="language-rust">pub struct Box&lt;T&gt;(*mut T);

impl&lt;T&gt; Box&lt;T&gt; {
    pub fn new(item: T) -&gt; Self {
        todo!()
    }
}
</code></pre>

<p>The thing we interface with is <code>Global</code>. Because of the way it works, there isn&#39;t a single static to refer to, we need to just use <code>default()</code> to get a handle to it [^1].
Lets take a look at the allocator trait. Immediately, we find the most important function:</p>

<pre><code class="language-rust">fn allocate(&amp;self, layout: Layout) -&gt; Result&lt;NonNull&lt;[u8]&gt;, AllocError&gt;
</code></pre>

<p>This takes something called a <code>Layout</code>, and returns something called <code>NonNull</code>. Lets cover these.
</p>

<h2 id="the-layout-type">The Layout Type</h2>

<p>Theres actually a lot more info about a pointer than just the exact address it points to. We also need to know how big the item is, so that we know how many bytes to read. Usually this is static and provided by the type, but with dynamically sized types like <code>[T]</code> it needs to be carried around with the pointer itself (something called a “wide-pointer”). On top of that, pointers have something called an alignment, which requires that they begin on an address divisible by a power of two[^2].
<code>Layout</code> carries all of this info for us, and actually provides a method to generate it from a type:</p>

<pre><code class="language-rust">let layout = Layout::new::&lt;T&gt;();
</code></pre>



<h2 id="the-nonnull-type">The NonNull Type</h2>

<p>Pointers shouldn&#39;t be null. Rust provides functional error handling schemes like <code>Option</code> that are very preferred over sentinel values for a multitude of reasons. This leads to the situation where <code>OxO</code> is a representable value, but should never be constructed. <code>NonNull</code> enforces this invariant: it asserts that it will <strong>never</strong> be null. The main caveat of <code>Option</code> is that we need to carry along another entire boolean byte just to keep track of which variant it is. However, with <code>NonNull</code> we now have a forbidden value that the compiler promises will never exist! As such, <code>NonNull</code> provides whats called <em>niche optimization</em>: <code>Option</code> will use the forbidden <code>0x0</code> value as the discriminant of <code>None</code>, and means that it doesn&#39;t need any more size than the type itself. Cool stuff!</p>

<pre><code class="language-rust">assert_eq!(size_of::&lt;NonNull&lt;u8&gt;&gt;(), size_of::&lt;Option&lt;NonNull&lt;u8&gt;&gt;&gt;());
</code></pre>

<p>Lets change the pointer type in our box, as it&#39;ll always be valid.</p>

<pre><code class="language-rust">pub struct Box&lt;T&gt;(NonNull&lt;T&gt;);
</code></pre>



<h2 id="getting-memory">Getting Memory</h2>

<p>Back to the function! Lets get the allocator and give it a layout of our type:</p>

<pre><code class="language-rust">let layout = Layout::new::&lt;T&gt;();
let ptr = Global::default().allocate(layout).unwrap();
let ptr = ptr.cast::&lt;T&gt;();
</code></pre>

<p>Now we have a pointer to some of our own data on the heap! First thing you&#39;ll notice is that we actually need to manually cast the pointer to the type we need, as <code>allocate</code> simply returns a slice of bytes. Lets not forget to actually write our data into that allocation:</p>

<pre><code class="language-rust">unsafe { ptr.write(item) };
</code></pre>

<p>This is unsafe as we need to guarantee that we are allowed to write to that space in memory. Since we just allocated that memory with a layout generated from <code>T</code>, we know that this is okay.
</p>

<h3 id="getting-rid-of-the-memory">Getting rid of the Memory</h3>

<p>We have a situation now where the default drop actually doesn&#39;t suffice. All rust does right now is delete the pointer when the box gets dropped. But the allocator doesn&#39;t know that happened! This is called a <em>memory leak</em>, where data has been allocated on the heap, and all pointers to that memory are destroyed without reallocating. Our data is doomed to forever sit untouched on the heap, alone and forgotten.
Luckily, rust has destructors. Implementing the <code>Drop</code> trait lets us run arbitrary code before the data gets destroyed, so this is the perfect place to deallocate!</p>

<pre><code class="language-rust">impl&lt;T&gt; Drop for Box&lt;T&gt; {
    fn drop(&amp;mut self) {
        unsafe { self.0.drop_in_place() };

        unsafe { 
            Global::default().deallocate(
                self.0.cast(), Layout::new::&lt;T&gt;()
            ) 
        };
    }
}
</code></pre>

<p>This is pretty similar to when we created the box, just in reverse. Once again, we need to provide a <code>NonNull&lt;u8&gt;</code>, which is why we call <code>cast()</code>, and the layout of our type. We also want to make sure that whatever deconstructor <code>T</code> has is run, with <code>drop_in_place</code>. This method is unsafe because it doesn&#39;t actually remvove the data in that memory, it simply runs the <code>Drop</code> implementation. Reading that memory from that point forward is undefined behavior.</p>

<p>And thats all she wrote! Along with a simple <code>Deref</code> impl, we&#39;ve created a safe abstraction for storing data on the heap!</p>

<pre><code class="language-rust">impl&lt;T&gt; Box&lt;T&gt; {
    pub fn new(item: T) -&gt; Self {
        let layout = Layout::new::&lt;T&gt;();
        let ptr = Global::default().allocate(layout).unwrap();
        let ptr = ptr.cast::&lt;T&gt;();
        unsafe { ptr.write(item) };

        Self(ptr)
    }
}

impl&lt;T&gt; Drop for Box&lt;T&gt; {
    fn drop(&amp;mut self) {
        unsafe { self.0.drop_in_place() };
        unsafe { 
            Global::default().deallocate(
                self.0.cast(), Layout::new::&lt;T&gt;()
            ) 
        };
    }
}

impl&lt;T&gt; Deref for Box&lt;T&gt; {
    type Target = T;

    fn deref(&amp;self) -&gt; &amp;Self::Target {
        //we know that this is safe because we can only get a Box from a safe function
        unsafe { self.0.as_ref() }
    }
}
</code></pre>
<ol><li>Alternatively, you could store the allocator in the struct, as it takes up no size.</li>
<li>Alignment is admittedly something I barely understand, I simply take it as a weird optimization that hardware does, and it really doesn&#39;t like when you break
it.</li></ol>

<p><em>thank you for reading &lt;3 i plan to write a part 2 soon that implements vec. i figured it would be better to teach the allocator api in its own smaller article. sorry to those who read this before i added the deallocation part. oops...</em></p>
]]></content:encoded>
      <guid>https://rant.li/jamie-blogs/demystifying-rust-implementing-box-from-scratch-9n6y</guid>
      <pubDate>Sat, 17 Feb 2024 19:36:22 +0000</pubDate>
    </item>
  </channel>
</rss>