<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Illia</title>
    <description>The latest articles on Forem by Illia (@lvc1d).</description>
    <link>https://forem.com/lvc1d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1393751%2Fd889c9ef-01cf-45da-8e72-a773ae8fc85a.png</url>
      <title>Forem: Illia</title>
      <link>https://forem.com/lvc1d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/lvc1d"/>
    <language>en</language>
    <item>
      <title>Cache Optimization in Rust: From HashMap Surprises to 4x Image Processing Speedup</title>
      <dc:creator>Illia</dc:creator>
      <pubDate>Wed, 26 Nov 2025 16:52:33 +0000</pubDate>
      <link>https://forem.com/lvc1d/cache-optimization-in-rust-from-hashmap-surprises-to-4x-image-processing-speedup-256l</link>
      <guid>https://forem.com/lvc1d/cache-optimization-in-rust-from-hashmap-surprises-to-4x-image-processing-speedup-256l</guid>
      <description>&lt;h2&gt;
  
  
  The Surprising Benchmark
&lt;/h2&gt;

&lt;p&gt;Imagine we have a task, me and you: a file with 10000 words in it - count the occurrence of each word, and present us with top-10 of the most common words there.&lt;/p&gt;

&lt;p&gt;We come up with some ideas, write those ideas out on an IDE, and now we benchmark it to see how blazingly fast our API is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;word_counting_hashing   &lt;span class="nb"&gt;time&lt;/span&gt;:   &lt;span class="o"&gt;[&lt;/span&gt;4.6504 ms 4.6591 ms 4.6686 ms]
Found 13 outliers among 100 measurements &lt;span class="o"&gt;(&lt;/span&gt;13.00%&lt;span class="o"&gt;)&lt;/span&gt;
  6 &lt;span class="o"&gt;(&lt;/span&gt;6.00%&lt;span class="o"&gt;)&lt;/span&gt; high mild
  7 &lt;span class="o"&gt;(&lt;/span&gt;7.00%&lt;span class="o"&gt;)&lt;/span&gt; high severe

word_counting_binary    &lt;span class="nb"&gt;time&lt;/span&gt;:   &lt;span class="o"&gt;[&lt;/span&gt;29.797 ms 30.042 ms 30.304 ms]
Found 6 outliers among 100 measurements &lt;span class="o"&gt;(&lt;/span&gt;6.00%&lt;span class="o"&gt;)&lt;/span&gt;
  6 &lt;span class="o"&gt;(&lt;/span&gt;6.00%&lt;span class="o"&gt;)&lt;/span&gt; high mild

Benchmarking word_counting_linear: Warming up &lt;span class="k"&gt;for &lt;/span&gt;3.0000 s
Warning: Unable to &lt;span class="nb"&gt;complete &lt;/span&gt;100 samples &lt;span class="k"&gt;in &lt;/span&gt;5.0s. You may wish to increase target &lt;span class="nb"&gt;time &lt;/span&gt;to 12.0s, or reduce sample count to 40.
word_counting_linear    &lt;span class="nb"&gt;time&lt;/span&gt;:   &lt;span class="o"&gt;[&lt;/span&gt;120.82 ms 121.11 ms 121.39 ms]

end_to_end              &lt;span class="nb"&gt;time&lt;/span&gt;:   &lt;span class="o"&gt;[&lt;/span&gt;4.8724 ms 4.8830 ms 4.8946 ms]
                        change: &lt;span class="o"&gt;[&lt;/span&gt;−0.3363% +0.9093% +1.7798%] &lt;span class="o"&gt;(&lt;/span&gt;p &lt;span class="o"&gt;=&lt;/span&gt; 0.09 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 0.05&lt;span class="o"&gt;)&lt;/span&gt;
                        No change &lt;span class="k"&gt;in &lt;/span&gt;performance detected.
Found 12 outliers among 100 measurements &lt;span class="o"&gt;(&lt;/span&gt;12.00%&lt;span class="o"&gt;)&lt;/span&gt;
  7 &lt;span class="o"&gt;(&lt;/span&gt;7.00%&lt;span class="o"&gt;)&lt;/span&gt; high mild
  5 &lt;span class="o"&gt;(&lt;/span&gt;5.00%&lt;span class="o"&gt;)&lt;/span&gt; high severe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In essence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linear: 121 ms&lt;/li&gt;
&lt;li&gt;Binary: 30 ms (4x improvement, right?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HashMap: 4.65 ms&lt;/strong&gt; - What?! But how?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fantastic question! Before I answer the How, we can both agree that a binary-search algorithm is still not bad, right? Because we perform up to log(N) iterations and not up to N iterations.&lt;/p&gt;

&lt;p&gt;So what's the deal here?&lt;/p&gt;

&lt;p&gt;When you hear a word "cache", what's the first thing that comes to your mind? &lt;/p&gt;

&lt;p&gt;Browser's cache and cookies? &lt;br&gt;
A physical safe-like box to store something important to retrieve it later in the future? &lt;br&gt;
Something else?...&lt;/p&gt;

&lt;p&gt;In either case, the idea is pretty much that: a space to put something in for an easy retrieval later in the future.&lt;/p&gt;

&lt;p&gt;In the technical sense, 'cache-friendly' code means structuring your data so the CPU can access it efficiently. &lt;/p&gt;

&lt;p&gt;The difference? Milliseconds versus seconds.&lt;/p&gt;

&lt;p&gt;Now, the secret lies in how CPUs actually access memory. &lt;br&gt;
Let me show you what's happening under the hood - and why it matters for every Rust program you write.&lt;/p&gt;
&lt;h2&gt;
  
  
  How CPU Cache Actually Works
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Cache Hierarchy
&lt;/h3&gt;

&lt;p&gt;The cache is not just 1 area of quick storage of data for a later retrieval. There are 3 layers of it, each with its own characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;L1: fastest yet smallest (on-CPU, can hold up to 32 - 64 KB)&lt;/li&gt;
&lt;li&gt;L2: not as fast as L1, yet still much faster than RAM (on modern CPU architectures - also on CPU: ~512 KB - 2 MB on average)&lt;/li&gt;
&lt;li&gt;L3: typically a shared resource between several CPU cores ( 2 &amp;lt; x &amp;lt; 10 MB)&lt;/li&gt;
&lt;li&gt;RAM: the slowest, yet the largest (you typically want to avoid having all the operational data stored here - otherwise, it hurts the optimization and performance - nowadays can store dozens of GB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The better-designed the API is, the closer to L1 your data will stick to - ensuring the most possible throughput in the shortest possible timeframe.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cache Lines - The 64-Byte Unit
&lt;/h3&gt;

&lt;p&gt;When the CPU grabs some data for processing in some CPU-bound task (i.e.: read a file) - it does not grab that data individually&lt;br&gt;
by bytes. It loads something called a cache line - a 64-byte-sized chunk of memory that contains this data at that given moment&lt;br&gt;
to iterate through quickly.&lt;/p&gt;

&lt;p&gt;Since this chunk is only 64 bytes, it easily fits into the L1 cache layer, so the processing of it is virtually instant (we are talking roughly microseconds).&lt;/p&gt;

&lt;p&gt;If we take a concrete example, like a vector of u32 integers - that are each 4 bytes (32 bits / 8 = 4 bytes) - when you&lt;br&gt;
iterate over it, the CPU will load a 64-byte cache line that contains the part of the vector just large enough to fit in and very quickly iterate through. In this case, it would be able to host up to 16 elements per cache line.&lt;/p&gt;
&lt;h3&gt;
  
  
  Spatial Locality
&lt;/h3&gt;

&lt;p&gt;Now, when it comes to cache optimization, there are two types of the locality (locating and accessing the data in memory on cache):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spatial locality&lt;/li&gt;
&lt;li&gt;Temporal locality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spatial locality refers to the data being located contiguously (like in a vector, sequentially), and the cache line loading means grabbing one contiguous chunk of that&lt;br&gt;
data structure to quickly iterate through at a time.&lt;/p&gt;

&lt;p&gt;If you remember from before, our benchmark showed some rather decent latency results for both a linear and a binary approaches in iterating over the words and counting up the occurrences.&lt;/p&gt;

&lt;p&gt;The reason is spatial locality. The CPU loads 64-byte chunks of the vector, processing multiple words per cache line instead of fetching each word individually. What this provides us is a rather high chance of so-called "cache hits" and reduces an excessive need to reload the cache line per chunk.&lt;/p&gt;
&lt;h3&gt;
  
  
  Temporal Locality
&lt;/h3&gt;

&lt;p&gt;Temporal locality means accessing the same memory location repeatedly over time. If you keep hitting the same data (also known as "cache hit"), it stays hot in cache.&lt;br&gt;
The opposite of the above is called the cache miss.&lt;/p&gt;

&lt;p&gt;What's worth noting regarding the latter is upon the cache miss - the CPU has to reload a fresh 64-byte cache line with that new data to be stored "temporally"&lt;br&gt;
until a data searched for, not-yet existent on the CPU, gets replaced in the cache line.&lt;/p&gt;

&lt;p&gt;Looking back again at our benchmarks, we noticed a significant increase in performance for the HashMap approach in the same task for finding word duplicates.&lt;br&gt;
Let's analyze deeper why that is.&lt;/p&gt;

&lt;p&gt;In that particular case, the text in question had quite a lot of repeating common words such as "the, a, most" etc.&lt;/p&gt;

&lt;p&gt;HashMap can be viewed as a table with some memory buckets that contain a pointer to the key (the key itself living in the Heap memory), and the associated value linked to that key.&lt;/p&gt;

&lt;p&gt;Once we encounter a cache miss upon grabbing an index that contains the pointer to the key that shows what value is associated with it, that bucket is loaded into the cache line.&lt;/p&gt;

&lt;p&gt;The more duplicates encounter upon each search, the hotter the cache line is - since it will be reused and stay intact - hence the key advantage of this approach here.&lt;/p&gt;

&lt;p&gt;Sure, you could still stick to the algorithm that favour spatial locality by checking in on the neighboring data elements, but that &lt;br&gt;
would always require to reload the cache line, even if we encounter duplicate words multiple times.&lt;/p&gt;
&lt;h3&gt;
  
  
  Issue with the Binary Search
&lt;/h3&gt;

&lt;p&gt;Despite the binary search having performed in around 30 ms, there may be a concern regarding when this algorithm may be beneficial or when it may be an overhead on the CPU&lt;br&gt;
and on memory access.&lt;/p&gt;

&lt;p&gt;Think about it: in this algorithm, assuming it's a large vector in question, we get the index in the middle of the collection.&lt;br&gt;
And then, based on that value, we check if it's greater or less than the middle - requiring us to jump to a completely different memory region. Based on that we load another cache line, and the procedure repeats.&lt;/p&gt;

&lt;p&gt;Imagine how many jumps across various memory regions we need to perform and how many cache misses we encounter...&lt;/p&gt;

&lt;p&gt;Sure - in regards to time complexity, we still win over the linear approach. But is it always the case?&lt;br&gt;
The CPU cache might prefer sometimes to just feed it one vector, iterate over it linearly by chunking in cache lines and just get onto the next one until we find the necessary value.&lt;br&gt;
We do not need to reload the cache line that much - only if the cache line is fully iterated over and finds no necessary result.&lt;/p&gt;

&lt;p&gt;In essence, nearly every step in binary search triggers a cache miss because each comparison jumps to a distant memory location, loading a fresh cache line while discarding the previous one.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cache Coherency and False Sharing
&lt;/h2&gt;

&lt;p&gt;Although we have now understood the essence of how cache performs during several simple examples of word counting, we still need to understand what happens when multiple threads are involved.&lt;br&gt;
Let's take a look at a more complex scenario: blurring an image using multiple threads.&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;Link to the project's slightly-messy GitHub repo &lt;a href="https://github.com/LVC1D/image-blur" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Coherency: The Foundation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine you want to speed up image processing by using multiple CPU cores. You split the work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thread 1 (Core 1): Process the red channel&lt;/li&gt;
&lt;li&gt;Thread 2 (Core 2): Process the green channel&lt;/li&gt;
&lt;li&gt;Thread 3 (Core 3): Process the blue channel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds perfect, right? But there's a catch.&lt;br&gt;
Each CPU core has its own L1 cache. When Core 1 loads some red channel data into its L1 cache and modifies it, the other cores need to know about this change. Otherwise, Core 2 might read stale data that's no longer correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is cache coherency&lt;/strong&gt; - the mechanism that keeps all the caches in sync across multiple cores.&lt;br&gt;
Here's what happens when Core 1 modifies data:&lt;/p&gt;

&lt;p&gt;Core 1 writes to its L1 cache&lt;br&gt;
The cache line is marked as "modified"&lt;br&gt;
Other cores that have this cache line must invalidate their copies&lt;br&gt;
Next time Core 2 needs that data, it must fetch the updated version&lt;/p&gt;

&lt;p&gt;In essence, cache coherency ensures correctness in multi-threaded programs, but it comes with a performance cost - cores must communicate and synchronize their caches.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;False Sharing: When Cache Lines Betray You&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now here's where things get tricky. Remember that the CPU loads entire 64-byte cache lines, not individual variables?&lt;br&gt;
False sharing happens when &lt;strong&gt;two or more threads modify DIFFERENT variables that happen to live in the SAME cache line&lt;/strong&gt;.&lt;br&gt;
Here's a concrete example. Suppose we're tracking progress from multiple threads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ThreadCounters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;thread1_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Bytes 0-7&lt;/span&gt;
    &lt;span class="n"&gt;thread2_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Bytes 8-15  &lt;/span&gt;
    &lt;span class="n"&gt;thread3_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Bytes 16-23&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// All three counters fit in ONE 64-byte cache line!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now watch what happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Core 1 increments thread1_count → invalidates the cache line on Core 2 and Core 3&lt;/li&gt;
&lt;li&gt;Core 2 increments thread2_count → invalidates the cache line on Core 1 and Core 3&lt;/li&gt;
&lt;li&gt;Core 3 increments thread3_count → invalidates the cache line on Core 1 and Core 2&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The threads aren't sharing data logically&lt;/strong&gt; (each has its own counter), &lt;br&gt;
but they're &lt;strong&gt;sharing a cache line physically&lt;/strong&gt;. Hence the name: false sharing.&lt;/p&gt;

&lt;p&gt;The result? &lt;em&gt;Cache ping-pong&lt;/em&gt;. &lt;br&gt;
Performance tanks even though the threads are supposedly independent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution?&lt;/strong&gt; Force each counter into its own cache line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[repr(align(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="nd"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;CachePadded&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ThreadCounters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;thread1_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CachePadded&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Gets its own 64-byte line&lt;/span&gt;
    &lt;span class="n"&gt;thread2_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CachePadded&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Gets its own 64-byte line&lt;/span&gt;
    &lt;span class="n"&gt;thread3_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CachePadded&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Gets its own 64-byte line&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now each thread can work independently without invalidating the others' cache lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How This Relates to Image Blur&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You might be wondering: does our image blur code suffer from false sharing?&lt;br&gt;
Actually, no! Here's why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each Vec is a separate heap allocation. When Thread 1 processes the red channel, it's modifying memory in a completely different region from where Thread 2 is processing green. They're not sharing cache lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt; - and this is important - the choice between Array of Structs (AoS) and Struct of Arrays (SoA) &lt;strong&gt;does&lt;/strong&gt; affect cache behavior in a single-threaded context, which is what we'll explore next.&lt;/p&gt;

&lt;p&gt;Now that we've covered both cache coherency (how cores stay in sync) and false sharing (when proximity hurts), let's see how data layout choices affect our image blur performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Image Blur Optimization
&lt;/h2&gt;

&lt;p&gt;As a note to keep in mind before we dive into each approach. &lt;br&gt;
The algorithm that will serve as the foundation how we will blur the image is via a 3x3 Box Blurring.&lt;br&gt;
The pixel's value will be the sum of that pixel and all 8 of its neighbors divided by 9 (the pixel count).&lt;/p&gt;

&lt;p&gt;For the sake of simplicity, we will not opt in for a toroidal looping over the edges to blur those - but rather will just return those as they are.&lt;/p&gt;

&lt;p&gt;As a reference, see below the last benchmark results (some may not seen as intuitive, but we will cover these in just a moment).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Gnuplot not found, using plotters backend
blur_naive              &lt;span class="nb"&gt;time&lt;/span&gt;:   &lt;span class="o"&gt;[&lt;/span&gt;2.0894 ms 2.0918 ms 2.0942 ms]
                        change: &lt;span class="o"&gt;[&lt;/span&gt;−0.2921% −0.1217% +0.0394%] &lt;span class="o"&gt;(&lt;/span&gt;p &lt;span class="o"&gt;=&lt;/span&gt; 0.15 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 0.05&lt;span class="o"&gt;)&lt;/span&gt;
                        No change &lt;span class="k"&gt;in &lt;/span&gt;performance detected.
Found 2 outliers among 100 measurements &lt;span class="o"&gt;(&lt;/span&gt;2.00%&lt;span class="o"&gt;)&lt;/span&gt;
  1 &lt;span class="o"&gt;(&lt;/span&gt;1.00%&lt;span class="o"&gt;)&lt;/span&gt; low mild
  1 &lt;span class="o"&gt;(&lt;/span&gt;1.00%&lt;span class="o"&gt;)&lt;/span&gt; high mild

blur_cache_optimized    &lt;span class="nb"&gt;time&lt;/span&gt;:   &lt;span class="o"&gt;[&lt;/span&gt;2.2716 ms 2.2772 ms 2.2842 ms]
                        change: &lt;span class="o"&gt;[&lt;/span&gt;−18.085% −17.836% −17.549%] &lt;span class="o"&gt;(&lt;/span&gt;p &lt;span class="o"&gt;=&lt;/span&gt; 0.00 &amp;lt; 0.05&lt;span class="o"&gt;)&lt;/span&gt;
                        Performance has improved.
Found 6 outliers among 100 measurements &lt;span class="o"&gt;(&lt;/span&gt;6.00%&lt;span class="o"&gt;)&lt;/span&gt;
  4 &lt;span class="o"&gt;(&lt;/span&gt;4.00%&lt;span class="o"&gt;)&lt;/span&gt; high mild
  2 &lt;span class="o"&gt;(&lt;/span&gt;2.00%&lt;span class="o"&gt;)&lt;/span&gt; high severe

Benchmarking blur_separable: Warming up &lt;span class="k"&gt;for &lt;/span&gt;3.0000 s
Warning: Unable to &lt;span class="nb"&gt;complete &lt;/span&gt;100 samples &lt;span class="k"&gt;in &lt;/span&gt;5.0s. You may wish to increase target &lt;span class="nb"&gt;time &lt;/span&gt;to 9.3s, &lt;span class="nb"&gt;enable &lt;/span&gt;flat sampling, or reduce sample count to 50.
blur_separable          &lt;span class="nb"&gt;time&lt;/span&gt;:   &lt;span class="o"&gt;[&lt;/span&gt;1.8354 ms 1.8374 ms 1.8395 ms]
Found 2 outliers among 100 measurements &lt;span class="o"&gt;(&lt;/span&gt;2.00%&lt;span class="o"&gt;)&lt;/span&gt;
  1 &lt;span class="o"&gt;(&lt;/span&gt;1.00%&lt;span class="o"&gt;)&lt;/span&gt; low mild
  1 &lt;span class="o"&gt;(&lt;/span&gt;1.00%&lt;span class="o"&gt;)&lt;/span&gt; high mild
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To simplify (on average):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Naive: 2.01 ms&lt;/li&gt;
&lt;li&gt;Cache-optimized (standard): 2.27 ms&lt;/li&gt;
&lt;li&gt;Cache-optimized (separable filtering): 1.83 ms&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Naive approach
&lt;/h3&gt;

&lt;p&gt;In a naive approach, we went for an AoS data structure type (Array of Structs).&lt;br&gt;
This is what it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Pixel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ImageAoS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pixel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means if we want to blur this image, we will have to loop over the vector once, grab each field's red / green / blue - and return a new vector of blurred image data.&lt;/p&gt;

&lt;p&gt;The function signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;blur_naive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Pixel&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pixel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* the core logic */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look at the pros and cons of this approach&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one loop -&amp;gt; which means one load of 64 bytes into the cache line&lt;/li&gt;
&lt;li&gt;simple design&lt;/li&gt;
&lt;li&gt;Temporal locality: accessing r→g→b of same pixel happens immediately&lt;/li&gt;
&lt;li&gt;Cache line holds all three channels for ~21 pixels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;locating the neighbors: since we are dealing with a 1D vector, identifying the vertical and horizontal neighbors requires some specific index arithmetics
(based on the provided &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; arguments)&lt;/li&gt;
&lt;li&gt;non-contiguous channel data: while the pixels as a whole are located contiguously in memory, the channel values are not&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cache-optimized (standard)
&lt;/h3&gt;

&lt;p&gt;By the logic, if we want to process one entire channel at a time (all reds, then all greens etc.), we would want a SoA structure&lt;br&gt;
(Struct of Arrays).&lt;/p&gt;

&lt;p&gt;As a gentle reminder from our False Sharing section, here is what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each field is a vector of all the relevant channel's pixel values.&lt;/p&gt;

&lt;p&gt;Let's take a quick look at the function signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;blur_cache_optimized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* the core logic */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we want to respect the intent that each channel should be processed one at a time - we cannot have the same loop to deal with THREE distinct vectors.&lt;br&gt;
Not only would this lead to even more memory hopping but for three different vectors per one iteration, but this function call would last longer if we were to profile its performance in a flamegraph.&lt;/p&gt;

&lt;p&gt;Let's look at some pros and cons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one loop -&amp;gt; which means one load of 64 bytes into the cache line&lt;/li&gt;
&lt;li&gt;Spatial locality: one channel vector - all elements are contiguous&lt;/li&gt;
&lt;li&gt;Cache line holds one channel per loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;three loops: instead of having one looping, we need three loops to handle each channel separately - that is because our project was single-threaded&lt;/li&gt;
&lt;li&gt;worse performance: relaying the above-demonstrated results from the benchmark, the average latency shows a slowdown by roughly 200 microseconds - contradicting to the original intent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In regards to the contradictory intent, it seems like we overlooked another piece of the puzzle that didn't seem as directly-related yet that would greatly affect the outcome and bring all the components discussed up until now together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separable filtering
&lt;/h3&gt;

&lt;p&gt;In order to understand what we mean by separable filtering, it is important to be aware of what a professional-grade algorithm would look like to achieve a goal within the same context.&lt;br&gt;
Let us reference &lt;a href="https://docs.rs/imageproc/latest/src/imageproc/filter/mod.rs.html#320-560" rel="noopener noreferrer"&gt;this source code&lt;/a&gt; of an actual crate publicly available: &lt;em&gt;imageproc&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What the author utilizes there is something that aligns with something called separation of concerns, which means each piece of logic performs this task AND ONLY this task, which does not interfere in the underlying logic of another code's functionality.&lt;br&gt;
What we mean by it here is that you shall see we have components that make up for the main function: &lt;code&gt;filter_horizontal()&lt;/code&gt; (in our case - &lt;code&gt;blur_horizontal()&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;filter_vertical()&lt;/code&gt; (in our case - &lt;code&gt;blur_vertical()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;If you were to go back to the standard cache-optimized approach, we still had to perform the average-pixel-value calculation from adding up 9 elements.&lt;br&gt;
In this case, however ,each component needs to get the average pixel value of 3 (horizontally: left-center-right, and vertically: top-center-bottom).&lt;/p&gt;

&lt;p&gt;Together, not only do we perform 6 additions instead of 9, but we jump across scattered memory regions in much less quantities compared to the previous approach, hence the improved result by 200 microseconds form the naive approach.&lt;/p&gt;

&lt;p&gt;Now what does this all tell us regarding the caching, false sharing and deciding what approach works best when?&lt;/p&gt;

&lt;h2&gt;
  
  
  Results &amp;amp; Lessons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Give me them numbers!
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Word counting&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HashMap: 4.6 ms avg&lt;/li&gt;
&lt;li&gt;Vector - Binary search: 30 ms avg &lt;/li&gt;
&lt;li&gt;Vector - Linear: 121 ms avg&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Image blurring&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Naive (AoS): 2.01 ms avg&lt;/li&gt;
&lt;li&gt;Cache-optimized (SoA: Standard): 2.2 ms avg &lt;/li&gt;
&lt;li&gt;Cache-optimized (SoA: Separable blurring): 1.8 ms &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you will see below, the reason for such results don't have to do necessarily only with how cache-friendly our data structure is, but rather how our logic interacts with it, and what the Rust compiler does to the data on runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  What do the Rust compiler and the algorithm picks have to do with cache-friendliness?
&lt;/h3&gt;

&lt;p&gt;If we are dealing with a set of data that has a great deal of repeated values, a HashMap approach will be most suitable in order to have a higher probability of retaining the hot cache line with the stored values in it.&lt;/p&gt;

&lt;p&gt;However, that is not to say that a linear - binary search should be ignored. Either of these approaches is feasible if looking up data is not as frequent and / or the data set size is not as substantial for storage - here, it will be more of a question whether we need to speed up this infrequent iterations or not (O(log(N)) vs O(N) time complexity)&lt;/p&gt;

&lt;p&gt;In regards to the image-blurring project, the concern is presented from a different angle that is equally important to keep in mind. &lt;br&gt;
The benchmark results demonstrated that an algorithm choice matters much more than just picking between AoS or SoA - because picking a data structure is one part of the story - it's a whole different part when it comes to how our API interacts with that data structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  What can I learn from all this?
&lt;/h3&gt;

&lt;p&gt;Let me briefly go over each key lessons I have cemented over the course of weeks 6 and 7 when covering cache coherence, cache optimisation and how to interpret various relevant benchmark results in this regard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Always measure, never assume - as practice showed twice in my case here, do not assume that just because a certain approach looks solid on paper - will always present as such on binary. Code them up and test each hypothesis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Study professional implementations (like imageproc's separable filter) - there's a reason why certain implementations work the way they do. Studying production code (like Rust crates on crates.io) teaches you patterns you wouldn't discover on your own.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test across different scenarios (small vs large datasets, different patterns) - HashMap dominated my word-counting benchmark because the text had many duplicates. But what if the data were different - unique formulas, random UUIDs, or different sizes? The optimal choice changes with the workload. Test your actual use case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data structure choice isn't just about API ergonomics - it directly controls memory layout, which determines cache behavior. This makes it a first-class performance consideration, not an afterthought.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The North Star
&lt;/h3&gt;

&lt;p&gt;If you want your Rust code to be blazingly fast as marketed by some people online, you need to keep in mind and be constantly aware how your code actually interacts with your AND someone else's CPU + memory you wish to distribute with - that shall be your northern light in picking the right algorithm, the right data structure and the right optimisations to apply.&lt;/p&gt;

&lt;p&gt;No matter where you are in your learning journey of Rust programming (or any systems programming language) - you are in charge of how you want your code to perform. &lt;br&gt;
No matter the results you see at the current moment - there will always be at least one way how the API could be different to chop down those extra milliseconds / microseconds. Your users (and your future debugger self) will thank you for that!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>hashmap</category>
      <category>cache</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>From 'Why the F@&amp;k Do I Need This?' to 'Oh, That's Why' - My GAT Journey</title>
      <dc:creator>Illia</dc:creator>
      <pubDate>Fri, 07 Nov 2025 17:35:40 +0000</pubDate>
      <link>https://forem.com/lvc1d/from-why-the-fk-do-i-need-this-to-oh-thats-why-my-gat-journey-1i80</link>
      <guid>https://forem.com/lvc1d/from-why-the-fk-do-i-need-this-to-oh-thats-why-my-gat-journey-1i80</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"Understanding GATs isn't about memorizing syntax - it's about that moment when you finally see WHY they exist."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Anyone can find the knowledge needed to just get the essence of what Async Rust, or some of the advanced types are all about. But what's the actual process of truly understanding them like? &lt;br&gt;
It's different for everyone - and for that, let me share with you how it went so far from my perspective.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Journey
&lt;/h2&gt;

&lt;p&gt;Before I even started the RBES (Rust Blockchain &amp;amp; Embedded Systems) curriculum - you have to keep in mind that I didn't just find the passion to start learning Rust from scratch out of thin air.&lt;br&gt;
No no - I actually learned quite a few other languages to test out my passion for programing - and that includes JavaScript / TypeScript, Golang, Swift (where all started because I wanted to be in the Apple ecosystem).&lt;/p&gt;

&lt;p&gt;When I heard about Rust - the whole idea of stringent memory safety, low-level programming attracted me to get to know it more - and it was hyped quite a bunch, so I decided to grab&lt;br&gt;
the Rust book in the official site and to just have a crack at it.&lt;/p&gt;

&lt;p&gt;After SIX iterations of the curriculum design (took several months, now that I remember) - I can proudly say I am now at Week 5 of the RBES program where the prior weeks&lt;br&gt;
are synthesized to fully solidify the material covered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What those weeks covered, you might ask?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Well, let me just briefly go through them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Week 1 + 2 Async Rust - Fundamentals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn't just use Tokio - I built a mini runtime with &lt;code&gt;Pin&lt;/code&gt;, &lt;code&gt;Poll&lt;/code&gt;, and custom wakers to understand what &lt;code&gt;.await&lt;/code&gt; actually does under the hood.&lt;/p&gt;

&lt;p&gt;On top of that, I covered the importance of &lt;code&gt;Arc&lt;/code&gt; and &lt;code&gt;Mutex&lt;/code&gt; and passing resources over the Tokio-powered channels such as &lt;code&gt;mpsc&lt;/code&gt; - &lt;br&gt;
and the underlying marker traits that allow us to work in the async logic safely - the &lt;code&gt;Send&lt;/code&gt; and &lt;code&gt;Sync&lt;/code&gt; marker traits&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Week 3 + 4: GATs + HRTBs + PhantomTypes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here, we got to know what those types are, how they are different from, say, a normal trait bonud in a type defintion or in a function signature - that allows us to write more flexible&lt;br&gt;
APIs, deal with several lifetimes, avoiding runtime costs and allocating much less data to the heap.&lt;/p&gt;

&lt;p&gt;Check my Week 5 progress to get a better understanding of how that learnign process went (spolier alert: NOT as quick and smooth as it sounded theoretically).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Week 5 (where we're at)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of the above material has been brought together to attempt to solidify the knowledge accumulated before &lt;br&gt;
moving forward to something a little bit scarier but a whole lot more exciting:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Unsafe Rust!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the week where I got to further strengthen my understanding of, primarily, Week 3 + 4 material, namely the GATs.&lt;br&gt;
And I thought that in the beginning of Week 3 I understood what GATs' true power is - boy was I wrong... &lt;/p&gt;

&lt;p&gt;Let me just illustrate briefly how my concept knowledge went from Week 3 to Week 5:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;GATs: "We can work with generics and lifetimes when design a more expressive trait with more flexible associated types" -&amp;gt; "GATs allow us to &lt;br&gt;
returned borrowed values into the implementor's data as long as the implementor is alive - no memory allocaiton is done, whereas with a normal associated type -&amp;gt; the borrow is released when you return an owned associated type value"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HRTBs: That pretty much stayed the same - we work with a function that take a closure (or a generic by itself) that can work with ANY lifetime the caller decides on - per each call.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PhantomTypes (the easiest for me) - An efficient tool that utilizes zero-size data types for building state machines that validate type states at compile-time.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That bit went from "I'm not sure why they are necessary in real code", along with "why the f**k you gave a me a task to use GAT when you told me afterwards it was not needed?!", all the way to&lt;br&gt;
"Here is my idea of how a GAT could be useful" (I will show you that example in just a bit).&lt;/p&gt;

&lt;p&gt;But first... Let's talk about some of my [modest] portfolio highlights.&lt;/p&gt;
&lt;h2&gt;
  
  
  Portfolio Highlights
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;1. Async Payment Processor - The GAT "Aha" Moment&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is where GATs stopped being a theoretical concept and became a necessary tool.&lt;br&gt;
The task was simple on the surface: build a payment processor that could handle transactions asynchronously. But here's the catch - I needed to return borrowed data from the processor's internal state without cloning it on every operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Technical Challenge:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without GATs, I had two bad options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clone everything:&lt;/strong&gt; Return owned Strings and Vecs, which means heap allocations on every transaction query. When you're processing thousands of payments, every clone is latency you can't afford.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use lifetimes everywhere:&lt;/strong&gt; But then every caller needs to manage lifetimes explicitly, making the API a nightmare to use.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The GAT Solution:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;AsyncPaymentProcessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TransactionData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;get_transaction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TxId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TransactionData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That 'a parameter in the associated type? That's the magic. It says: "I'm returning borrowed data that lives as long as the processor does." The caller gets a reference tied to self's lifetime - no clones, no unnecessary heap allocations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What This Proves:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I can identify when zero-cost abstractions actually matter. In systems where performance isn't just nice-to-have (blockchain consensus, game engines, real-time trading), understanding the difference between "elegant code" and "efficient code" is the difference between shipping and failing.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Mini Async Runtime - Understanding What .await Actually Does&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Anyone can use Tokio. I built my own minimal runtime to understand what happens &lt;em&gt;under the hood&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Core Insight:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The biggest "aha" wasn't about Pin or Poll syntax - it was understanding the &lt;em&gt;handoff dance&lt;/em&gt; between futures and the IO driver:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Future yields:&lt;/strong&gt; "I'm waiting on network IO, here's my waker handle"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Executor parks it:&lt;/strong&gt; Future goes into a HashMap (not a queue!)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OS completes IO:&lt;/strong&gt; Driver receives notification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Waker activates:&lt;/strong&gt; Driver looks up the TaskId and wakes that &lt;em&gt;specific&lt;/em&gt; future&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Executor re-polls:&lt;/strong&gt; Future resumes exactly where it left off&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why HashMap vs VecDeque Matters:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When an IO operation completes, the OS needs to wake &lt;em&gt;that specific future&lt;/em&gt;, not scan through a queue. HashMap lookup by TaskId is O(1) - this is how real async runtimes scale to thousands of concurrent tasks without wasting CPU cycles polling futures that aren't ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What This Proves:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I don't just use abstractions - I understand their implementation trade-offs. When debugging production async code or optimizing hot paths, knowing &lt;em&gt;why&lt;/em&gt; the runtime makes certain choices means I can write code that works &lt;em&gt;with&lt;/em&gt; the system, not against it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. HTTP Request Builder - Compile-Time State Machines&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This one showcases phantom types and the builder pattern, but more importantly, it demonstrates how Rust's type system can &lt;em&gt;prevent entire classes of bugs at compile time&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Design:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those PhantomData markers don't exist at runtime (zero-size types), but at compile time they enforce state transitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Request can call .with_header()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Request can call .with_body()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Request can call .send()&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You &lt;em&gt;cannot&lt;/em&gt; call .send() on a request without headers. Not "you shouldn't" - you &lt;em&gt;cannot&lt;/em&gt;. The compiler won't let you compile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Matters:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you're building APIs at scale - whether it's a blockchain RPC client or a game's network layer - catching state machine errors at compile-time means your users can't misuse your API. It's the difference between "safe by convention" (documentation saying "don't do this") and "safe by construction" (the type system enforcing it).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What This Proves:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I can design APIs that are both &lt;em&gt;ergonomic&lt;/em&gt; (builder pattern feels natural) and &lt;em&gt;bulletproof&lt;/em&gt; (invalid states are unrepresentable). This is the kind of API design that matters when mistakes are expensive - and in blockchain or embedded systems, mistakes &lt;em&gt;are&lt;/em&gt; expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The GAT Breakthrough (Week 4-5)&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;The Setup: Confidence Before the Fall&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Week 3, I learned about GATs. I understood the syntax. I could explain that they're "generic associated types that let you add lifetime parameters." I thought I got it.&lt;br&gt;
Week 4, you (Claude) gave me a task: &lt;em&gt;"Build an HTTP request builder using phantom types. Find a way to incorporate GATs."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Cool. I'd mastered GATs, right? Let's do this.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;em&gt;The Struggle: When Theory Meets Reality&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;I tried &lt;em&gt;everything&lt;/em&gt; to make GATs work with the state machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;BuilderStep&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// GAT for state transitions!&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;next_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But wait... if the GAT method gets to &lt;em&gt;decide&lt;/em&gt; the return type based on the generic parameter S, how do I &lt;strong&gt;enforce&lt;/strong&gt; that .with_header() &lt;em&gt;must&lt;/em&gt; return HeadersSet state?&lt;br&gt;
The whole point of phantom types is compile-time guarantees. &lt;br&gt;
If the method itself is generic over the next state, I've just opened the door to any state transition - which defeats the entire purpose of a state machine.&lt;br&gt;
I kept hitting this wall. The compiler would accept my code, but it was &lt;em&gt;wrong&lt;/em&gt;. Too flexible. No guarantees.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;The Realization: Not Every Problem Needs a Hammer&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;After what felt like hours of wrestling with syntax, you said something that changed how I think about these tools:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"GATs aren't for strict type transitions. They're for when you need &lt;strong&gt;lifetime flexibility&lt;/strong&gt; or &lt;strong&gt;borrowing from self&lt;/strong&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oh.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Oh.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;GATs weren't wrong for state machines because I was bad at using them. They were wrong because &lt;strong&gt;that's not what they're for.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The HTTP builder needs &lt;em&gt;specific, enforced transitions&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Initialized&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nb"&gt;Into&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Your implementation&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;with_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nb"&gt;Into&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nb"&gt;Into&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HeadersSet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.headers&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No GAT. No lifetime parameter. Just: &lt;em&gt;"This state goes to this state, period."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Proof: Finding Where GATs Actually Belong&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Then came the async payment processor.&lt;/p&gt;

&lt;p&gt;This time, the problem was different: I needed to return borrowed data from the processor's internal state - transaction records, account balances - without cloning them on every query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;AsyncPaymentProcessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TransactionData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;get_transaction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TxId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransactionData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where GATs shine. The lifetime 'a ties the returned data to how long self is borrowed. &lt;br&gt;
The caller can hold a reference as long as they're holding the processor - no clones, no heap allocations, no unnecessary overhead.&lt;/p&gt;

&lt;p&gt;Without GATs? I'd be returning owned Strings and Vecs on every transaction query. With thousands of concurrent transactions, that's latency I can't afford.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Lesson: Judgment Over Memorization&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The breakthrough wasn't learning GAT syntax - it was developing &lt;strong&gt;engineering judgment&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Phantom types:&lt;/strong&gt; When you need compile-time state enforcement with zero runtime cost&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GATs:&lt;/strong&gt; When you need to return borrowed data with flexible lifetimes tied to self&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Regular associated types:&lt;/strong&gt; When you just need type-level abstraction without borrowing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not about which tool is "better" - it's about recognizing which problem you're actually solving.&lt;/p&gt;

&lt;p&gt;And honestly? That's the lesson I wish I'd understood in Week 3. &lt;br&gt;
But struggling through the HTTP builder, being wrong, getting frustrated, and &lt;em&gt;then&lt;/em&gt; building the payment processor with the right tool for the right job - that's what made it stick.&lt;/p&gt;

&lt;p&gt;Anyone can memorize syntax. Understanding &lt;em&gt;why&lt;/em&gt; a feature exists? That takes bruises.&lt;strong&gt;Technical Deep Dives&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Week 1-2: Async Rust - From Surface to System&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Before the RBES curriculum, I thought async Rust was just async/.await syntax and maybe using mpsc channels for message passing. That's it. I'd read the Rust book's async section, understood the surface layer because I had JavaScript experience, and figured that was enough.&lt;/p&gt;

&lt;p&gt;I was &lt;em&gt;painfully&lt;/em&gt; wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Changed:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now I understand the entire runtime executor cycle - how futures are polled, how the IO driver signals the waker when operations complete, how the executor knows which specific future to re-poll. It's not magic syntax - it's a coordinated dance between the executor, futures, and the OS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Can Explain Confidently:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Async functions are lazy until polled (via .await, tokio::join!, tokio::select!, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Send/Sync marker traits and when resources need to be wrapped in Arc&amp;gt; for multi-threaded safety&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That TCP server handling 500 concurrent connections? I can explain why the mpsc receiver needed Arc&amp;gt;for multiple workers to grab tasks from a single channel&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I Don't Know Yet:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Honestly? I won't know what I need to look up until I hit a project with requirements beyond basic async patterns. The async_trait macro for the payment processor was a perfect example - completely new territory. That's how learning works: you don't know the gaps until you need to fill them.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Week 3-4: Type System - Bruises Build Understanding&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty Ranking (Hardest → Easiest):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HRTBs&lt;/strong&gt; - Even the official docs admit explicit HRTBs are rare (despite recent lifetime elision improvements in Rust 1.85+). I haven't had enough real-world experience with them because needing to handle multiple inputs' lifetimes (or no lifetimes at all) just doesn't come up often.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GATs&lt;/strong&gt; - Still not trivial, but the bruises from Week 5 made them stick. Applying them wrong (HTTP builder), then applying them right (async payment processor) taught me more than any documentation could.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Phantom Types&lt;/strong&gt; - Straightforward once you get the zero-cost state machine pattern. These clicked fastest for me.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The False Confidence Trap:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Week 3, first encounter with GATs: "Yeah, I get it - generic associated types with lifetime parameters."&lt;br&gt;
Week 5, after the HTTP builder struggle: "Oh... I didn't get it &lt;em&gt;at all&lt;/em&gt;."&lt;br&gt;
That gap between "I understand the syntax" and "I know when and why to use this" is where Week 5 lived. The intuition that something was off in Week 3 was right - I just hadn't done the work yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My 2-Sentence GAT Test:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;When should you use a GAT?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you need to return borrowed data tied to the implementor's lifetime and you're not sure what exact type (generic) it should return - giving that &lt;br&gt;
type flexibility to the caller of the trait method, not the trait definition itself. If you need strict type transitions (like state machines), GATs are the wrong tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Week 5: Consolidation - Concepts Meeting Reality&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The big shift in Week 5 wasn't learning &lt;em&gt;new&lt;/em&gt; concepts - it was seeing how async and GATs work &lt;em&gt;together&lt;/em&gt; in real systems. Building the async payment processor forced me to combine everything: lifetime management, async traits, borrowing semantics, all in one design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I Could Rebuild One Project:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The TCP server. Not the basic string-printing version we built in Week 1, but a more sophisticated one using the newtype pattern and GATs to &lt;br&gt;
make the connection handling more interesting. Now that I actually understand what GATs are for, I could design something that borrows connection state efficiently instead of cloning data everywhere.&lt;br&gt;
That's the clearest sign of progress: not "I finished the curriculum," but "I'd do it differently now."&lt;strong&gt;What I Can Build Now&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current Capabilities (Proven in Portfolio):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Concurrent TCP servers&lt;/strong&gt; handling hundreds of simultaneous connections with proper async runtime usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Type-safe state machines&lt;/strong&gt; using phantom types for compile-time validation (HTTP request builders, protocol state tracking)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero-allocation async APIs&lt;/strong&gt; using GATs to return borrowed data from internal state (payment processors, data access layers)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom async patterns&lt;/strong&gt; beyond just using Tokio - understanding &lt;em&gt;why&lt;/em&gt; executors work the way they do&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Realistically, Today:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anything that combines the Week 1-5 concepts - basic servers, API clients, concurrent task schedulers, type-safe builders. These are &lt;em&gt;components&lt;/em&gt; of larger systems, not full applications yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Can't Build Yet (And I'm Honest About It):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Production blockchain infrastructure&lt;/strong&gt; (RPC load balancers, validator monitoring platforms, MEV optimization - that's Phase 2, Weeks 15-38)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Embedded systems with real hardware&lt;/strong&gt; (STM32/ESP32 interfacing, RTOS integration, automotive CAN bus - that's Phase 3, Weeks 39-61)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edge AI inference systems&lt;/strong&gt; (on-device ML models, sensor fusion, industrial IoT - that's Phase 3, Weeks 46-60)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Production observability stacks&lt;/strong&gt; (Prometheus/Grafana pipelines, distributed tracing, SRE practices - that's Phase 5, Weeks 70-71)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A better answer to "what can I build?" will come after Phase 2 (blockchain infrastructure) and Phase 3 (embedded + edge AI). For now, I can build sophisticated &lt;em&gt;components&lt;/em&gt; - the building blocks that go into larger systems. That's not modest - that's realistic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Collaborators/Employers:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you need someone to build Rust libraries with strong API design, async concurrency, and compile-time safety guarantees, I can do that. &lt;br&gt;
If you need someone who's shipped production blockchain RPC infrastructure or embedded automotive systems, that's not me... yet.&lt;/p&gt;

&lt;p&gt;I know the difference. And knowing what you don't know is just as valuable as knowing what you do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open for Collaboration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is my first full technical blog post, so any feedback is genuinely appreciated - comments, corrections, questions, even just a "this resonated with me" helps me understand what's landing and what's not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who I'm hoping to connect with:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're a Rust developer (whether you're a few weeks ahead or a few years ahead), or if you're working in blockchain infrastructure, embedded systems, or edge AI and want to discuss the technical challenges in these domains, I'd love to hear from you. The best learning happens in conversation, not isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'm&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;not&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;doing (yet):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm not seeking consulting work or commercial collaborations at this stage - I'm Week 5 of an 85-week curriculum, and I'm being realistic about where I am. As I build more substantial projects and publish more posts, you'll see those calls-to-action appear naturally. For now, I'm just documenting the journey and inviting technical discussion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transparency note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My curriculum is commercially focused (blockchain infrastructure + embedded systems + edge AI), so by Month 18-24, &lt;br&gt;
you'll absolutely see me positioning for client work. But I'd rather be honest about being in the learning phase than oversell capabilities I haven't proven yet.&lt;/p&gt;

&lt;p&gt;If you want to follow along, you can find my projects on &lt;a href="https://github.com/LVC1D" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, reach me on &lt;br&gt;
&lt;a href="https://discord.com/users/hectorcryo" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;, or connect on &lt;a href="https://www.reddit.com/u/Open-Ad-5175/s/oIcNHjKBrA" rel="noopener noreferrer"&gt;Reddit&lt;/a&gt;. &lt;br&gt;
I'm documenting everything - the wins, the bruises, the "oh NOW I get it" moments.&lt;/p&gt;

&lt;p&gt;Because understanding GATs isn't about memorizing syntax - it's about that moment when you finally see &lt;em&gt;why&lt;/em&gt; they exist.&lt;/p&gt;

&lt;p&gt;And I'm still collecting those moments.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>async</category>
      <category>gat</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
