<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Internals on</title><link>/tags/internals/</link><description>Recent content in Internals on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Thu, 02 Oct 2025 16:55:31 +0000</lastBuildDate><atom:link href="/tags/internals/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 8: Runtime Comparison — Tokio vs async-std vs smol vs glommio</title><link>/post/rust/rust-runtime-comparison/</link><pubDate>Thu, 02 Oct 2025 16:55:31 +0000</pubDate><guid>/post/rust/rust-runtime-comparison/</guid><description>&lt;p&gt;Every few months, someone asks me &amp;ldquo;which async runtime should I use?&amp;rdquo; and every time my answer is frustrating: &amp;ldquo;it depends.&amp;rdquo; But after spending the last seven lessons understanding how runtimes work from the inside, we can finally have a real conversation about what the differences actually are, why they exist, and when each one is the right choice.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve used all four of these runtimes in production. Tokio for most things, smol for a lightweight embedded project, glommio for a storage engine prototype, and async-std briefly before migrating away from it. Let me share what I&amp;rsquo;ve learned — not from reading documentation, but from debugging real problems at 2 AM.&lt;/p&gt;</description></item><item><title>Lesson 7: Designing a Custom Async Runtime — When Tokio isn't enough</title><link>/post/rust/rust-runtime-custom-runtime/</link><pubDate>Tue, 30 Sep 2025 11:20:06 +0000</pubDate><guid>/post/rust/rust-runtime-custom-runtime/</guid><description>&lt;p&gt;A colleague once asked me, &amp;ldquo;Why would anyone build a custom async runtime when Tokio exists?&amp;rdquo; Fair question. Tokio is battle-tested, well-maintained, and fast. For 95% of use cases, it&amp;rsquo;s the right answer. But I&amp;rsquo;ve now been in three situations where it wasn&amp;rsquo;t — and each one taught me something about what a runtime actually does.&lt;/p&gt;
&lt;p&gt;The first was a latency-sensitive trading system where work-stealing&amp;rsquo;s cache invalidation was unacceptable. The second was an embedded system with no allocator. The third was a specialized database engine where we needed precise control over I/O scheduling. Each time, understanding how to build a runtime from the ground up saved the project.&lt;/p&gt;</description></item><item><title>Lesson 6: Work-Stealing Schedulers — Balancing load across cores</title><link>/post/rust/rust-runtime-work-stealing/</link><pubDate>Sat, 27 Sep 2025 13:45:50 +0000</pubDate><guid>/post/rust/rust-runtime-work-stealing/</guid><description>&lt;p&gt;I once spent two days debugging a performance issue where our Tokio application was using four cores but only one was doing any work. Three threads were idle, one was pegged at 100%. The problem wasn&amp;rsquo;t Tokio&amp;rsquo;s scheduler — it was ours. We&amp;rsquo;d accidentally created a pattern where all tasks were spawned from and waking on the same thread, and nothing triggered work-stealing because the tasks completed too quickly.&lt;/p&gt;
&lt;p&gt;That experience taught me that you can&amp;rsquo;t treat the scheduler as a black box. If you understand how work-stealing works, you can design your task topology to take advantage of it. If you don&amp;rsquo;t, you&amp;rsquo;ll write code that accidentally defeats it.&lt;/p&gt;</description></item><item><title>Lesson 5: The Reactor Pattern — How Tokio actually works</title><link>/post/rust/rust-runtime-reactor-pattern/</link><pubDate>Wed, 24 Sep 2025 09:27:13 +0000</pubDate><guid>/post/rust/rust-runtime-reactor-pattern/</guid><description>&lt;p&gt;I read the Tokio source code on a Sunday afternoon, expecting to find something inscrutable. Layers of unsafe code, impenetrable abstractions, the kind of thing that makes you question your career choices. What I actually found was a clean, well-documented reactor implementation that I could follow. Not easily — but followably. The architecture is elegant once you see how the pieces connect.&lt;/p&gt;
&lt;p&gt;This lesson is about that architecture. Not Tokio&amp;rsquo;s API — you already know how to use &lt;code&gt;tokio::spawn&lt;/code&gt; and &lt;code&gt;TcpStream&lt;/code&gt;. This is about what happens &lt;em&gt;underneath&lt;/em&gt; when you call those functions. The reactor pattern, the I/O driver, the timer wheel, and how they all feed into the executor.&lt;/p&gt;</description></item><item><title>Lesson 4: epoll/kqueue — Platform event loops</title><link>/post/rust/rust-runtime-epoll-kqueue/</link><pubDate>Sun, 21 Sep 2025 17:08:45 +0000</pubDate><guid>/post/rust/rust-runtime-epoll-kqueue/</guid><description>&lt;p&gt;Before I understood event loops, I thought async I/O was some kind of kernel magic. You register interest in a socket, and somehow the OS tells you when data arrives — without blocking a thread. How? I imagined complex kernel subsystems doing heavy lifting behind the scenes.&lt;/p&gt;
&lt;p&gt;Turns out, the mechanism is almost embarrassingly simple. The kernel maintains a list of file descriptors you care about. When something happens on one of them, it flips a bit. You ask &amp;ldquo;what happened?&amp;rdquo;, it tells you. That&amp;rsquo;s it. The entire async I/O ecosystem — Tokio, Node.js, nginx, everything — is built on this one primitive.&lt;/p&gt;</description></item><item><title>Lesson 3: io_uring — Zero-copy async I/O on Linux</title><link>/post/rust/rust-runtime-io-uring/</link><pubDate>Fri, 19 Sep 2025 10:33:08 +0000</pubDate><guid>/post/rust/rust-runtime-io-uring/</guid><description>&lt;p&gt;I ran a benchmark last year that genuinely surprised me. A simple TCP echo server using &lt;code&gt;io_uring&lt;/code&gt; was handling 40% more connections per second than the same server on &lt;code&gt;epoll&lt;/code&gt;, with measurably lower tail latencies. Not 5% — forty percent. On the same hardware, same kernel version, same application logic. That&amp;rsquo;s when I stopped treating &lt;code&gt;io_uring&lt;/code&gt; as a curiosity and started treating it as the future of Linux I/O.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve been building async runtimes on &lt;code&gt;epoll&lt;/code&gt; (or even &lt;code&gt;kqueue&lt;/code&gt; on macOS), &lt;code&gt;io_uring&lt;/code&gt; changes the game completely. Let me show you why.&lt;/p&gt;</description></item><item><title>Lesson 2: Building a Minimal Executor — Your own async runtime</title><link>/post/rust/rust-runtime-executor/</link><pubDate>Wed, 17 Sep 2025 14:12:37 +0000</pubDate><guid>/post/rust/rust-runtime-executor/</guid><description>&lt;p&gt;The moment I built my first executor from scratch, async Rust stopped being scary. Not because executors are simple — they&amp;rsquo;re not — but because once you see the machinery, every &amp;ldquo;mysterious&amp;rdquo; behavior has an obvious explanation. Futures hanging? The waker isn&amp;rsquo;t being called. Tasks not making progress? The executor&amp;rsquo;s run loop has a bug. Performance terrible? You&amp;rsquo;re probably polling too aggressively or not enough.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s build one. A real, working executor. Not a production-quality one (that&amp;rsquo;s Tokio&amp;rsquo;s job), but one that actually runs futures to completion, handles multiple tasks, and demonstrates every concept from the previous lesson.&lt;/p&gt;</description></item><item><title>Lesson 1: Future Internals — Poll, Waker, Context</title><link>/post/rust/rust-runtime-future-internals/</link><pubDate>Mon, 15 Sep 2025 08:45:22 +0000</pubDate><guid>/post/rust/rust-runtime-future-internals/</guid><description>&lt;p&gt;I thought I understood Rust futures until I tried to implement one without &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;. Not a toy future that immediately returns &lt;code&gt;Ready&lt;/code&gt;. A real future — one that yields, gets woken up, and resumes where it left off. That exercise broke every mental model I had and rebuilt it from scratch.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; syntax is one of Rust&amp;rsquo;s great lies. It looks simple. It &lt;em&gt;feels&lt;/em&gt; like you&amp;rsquo;re writing sequential code. Under the hood, the compiler is generating state machines, threading waker references through call graphs, and constructing self-referential structs that would make most C++ programmers nervous. Let&amp;rsquo;s rip the lid off.&lt;/p&gt;</description></item><item><title>Lesson 8: String Internals — Immutable, backed by bytes, cheaper than you think</title><link>/post/go/go-internals-strings/</link><pubDate>Tue, 15 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-internals-strings/</guid><description>&lt;p&gt;I used to reach for &lt;code&gt;[]byte&lt;/code&gt; over &lt;code&gt;string&lt;/code&gt; in performance-sensitive code, assuming strings were somehow more expensive because &amp;ldquo;immutability must cost something.&amp;rdquo; That intuition was wrong. Understanding what a string actually is — a read-only slice header — corrected my assumptions and simplified a lot of code I had unnecessarily complicated.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Strings in Go are everywhere, and most developers use them without thinking about what they are at the memory level. This leads to a few common mistakes:&lt;/p&gt;</description></item><item><title>Lesson 10: How rustc Works — From source code to binary</title><link>/post/rust/rust-internals-compiler-pipeline/</link><pubDate>Sat, 22 Mar 2025 11:30:00 +0000</pubDate><guid>/post/rust/rust-internals-compiler-pipeline/</guid><description>&lt;p&gt;A junior on my team once asked why Rust compiles so slowly compared to Go. I gave the standard answer about monomorphization and LLVM, but realized I couldn&amp;rsquo;t actually explain the full pipeline. So I spent a weekend reading the rustc dev guide and poking at compiler internals. What I found was a surprisingly elegant six-stage pipeline, and understanding it changed how I think about Rust&amp;rsquo;s design tradeoffs.&lt;/p&gt;
&lt;h2 id="the-big-picture"&gt;The Big Picture&lt;/h2&gt;
&lt;p&gt;When you run &lt;code&gt;cargo build&lt;/code&gt;, your source code goes through six major stages before becoming a binary:&lt;/p&gt;</description></item><item><title>Lesson 9: Miri — Your safety net for unsafe Rust</title><link>/post/rust/rust-internals-miri/</link><pubDate>Wed, 19 Mar 2025 08:45:00 +0000</pubDate><guid>/post/rust/rust-internals-miri/</guid><description>&lt;p&gt;I once shipped a Rust library with an unsafe block that worked perfectly on x86, passed all tests, and ran flawlessly in production for months. Then someone compiled it on ARM and it segfaulted immediately. The undefined behavior had been lurking the whole time — x86 just happened to tolerate the misaligned read that ARM wouldn&amp;rsquo;t. If I&amp;rsquo;d run Miri before releasing, I would have caught it in thirty seconds. Lesson learned the hard way.&lt;/p&gt;</description></item><item><title>Lesson 8: Custom Allocators — GlobalAlloc, arena allocation, and beyond</title><link>/post/rust/rust-internals-allocator/</link><pubDate>Sun, 16 Mar 2025 13:10:00 +0000</pubDate><guid>/post/rust/rust-internals-allocator/</guid><description>&lt;p&gt;I was building a JSON parser that processed millions of small documents per second. Profiling showed that 40% of the time was spent in &lt;code&gt;malloc&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt; — not parsing, not I/O, just allocating and deallocating tiny strings and vectors. Switched to an arena allocator that bulk-freed everything after each document, and throughput doubled. That experience taught me that the allocator isn&amp;rsquo;t just plumbing — it&amp;rsquo;s a performance lever.&lt;/p&gt;
&lt;h2 id="how-rust-allocates-memory"&gt;How Rust Allocates Memory&lt;/h2&gt;
&lt;p&gt;When you write &lt;code&gt;Box::new(42)&lt;/code&gt; or &lt;code&gt;Vec::with_capacity(100)&lt;/code&gt;, Rust asks the &lt;strong&gt;global allocator&lt;/strong&gt; for memory. By default, this is the system allocator — &lt;code&gt;malloc&lt;/code&gt;/&lt;code&gt;free&lt;/code&gt; on Unix, &lt;code&gt;HeapAlloc&lt;/code&gt;/&lt;code&gt;HeapFree&lt;/code&gt; on Windows. It&amp;rsquo;s a general-purpose allocator designed to handle any allocation pattern reasonably well, but it&amp;rsquo;s not optimized for any specific pattern.&lt;/p&gt;</description></item><item><title>Lesson 7: Drop Order — Deterministic destruction and why it matters</title><link>/post/rust/rust-internals-drop-order/</link><pubDate>Thu, 13 Mar 2025 07:20:00 +0000</pubDate><guid>/post/rust/rust-internals-drop-order/</guid><description>&lt;p&gt;I once had a deadlock in a Rust program that only manifested during shutdown. A mutex guard and a database connection were being dropped in the wrong order — the connection&amp;rsquo;s destructor tried to acquire the mutex that was already locked by the guard that hadn&amp;rsquo;t been dropped yet. Rust&amp;rsquo;s drop order is deterministic, but &amp;ldquo;deterministic&amp;rdquo; doesn&amp;rsquo;t mean &amp;ldquo;obvious.&amp;rdquo; Knowing the rules saved me hours of debugging.&lt;/p&gt;
&lt;h2 id="the-drop-trait"&gt;The Drop Trait&lt;/h2&gt;
&lt;p&gt;In Rust, cleanup logic is implemented through the &lt;code&gt;Drop&lt;/code&gt; trait. When a value goes out of scope, the compiler calls &lt;code&gt;drop()&lt;/code&gt; on it automatically. This is RAII — Resource Acquisition Is Initialization — borrowed from C++ but made more reliable by the ownership system.&lt;/p&gt;</description></item><item><title>Lesson 6: repr(C), repr(transparent), repr(packed) — Taking control of memory layout</title><link>/post/rust/rust-internals-repr/</link><pubDate>Tue, 11 Mar 2025 09:55:00 +0000</pubDate><guid>/post/rust/rust-internals-repr/</guid><description>&lt;p&gt;The first time I wrote Rust FFI bindings to a C library, I defined a struct, passed it across the boundary, and got garbage data back. The C side was reading fields at fixed offsets, but Rust had silently reordered my fields for padding efficiency. Took me twenty minutes of staring at hex dumps to realize the layouts didn&amp;rsquo;t match. That&amp;rsquo;s the day I learned about &lt;code&gt;#[repr(C)]&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="default-rust-layout-reprrust"&gt;Default Rust Layout: repr(Rust)&lt;/h2&gt;
&lt;p&gt;By default, Rust structs use &lt;code&gt;repr(Rust)&lt;/code&gt; — the compiler is free to reorder fields, add padding wherever it wants, and generally optimize the layout however it sees fit. The only guarantees are:&lt;/p&gt;</description></item><item><title>Lesson 5: Fat Pointers — &amp;dyn Trait, &amp;[T], and &amp;str under the hood</title><link>/post/rust/rust-internals-fat-pointers/</link><pubDate>Sun, 09 Mar 2025 16:40:00 +0000</pubDate><guid>/post/rust/rust-internals-fat-pointers/</guid><description>&lt;p&gt;I remember being confused why &lt;code&gt;&amp;amp;str&lt;/code&gt; was 16 bytes on a 64-bit system. A pointer is 8 bytes — what&amp;rsquo;s the other 8? That question sent me down a rabbit hole that fundamentally changed how I think about Rust&amp;rsquo;s type system. Turns out, some references carry extra baggage, and that baggage is the entire reason dynamically sized types work.&lt;/p&gt;
&lt;h2 id="thin-pointers-vs-fat-pointers"&gt;Thin Pointers vs Fat Pointers&lt;/h2&gt;
&lt;p&gt;Most references in Rust are &amp;ldquo;thin&amp;rdquo; — a single machine word pointing to the data:&lt;/p&gt;</description></item><item><title>Lesson 4: vtables — How dyn Trait actually works under the hood</title><link>/post/rust/rust-internals-vtable/</link><pubDate>Fri, 07 Mar 2025 11:15:00 +0000</pubDate><guid>/post/rust/rust-internals-vtable/</guid><description>&lt;p&gt;I was profiling a parser once and found that a hot path using &lt;code&gt;dyn Iterator&lt;/code&gt; was 3x slower than the equivalent code using generics. The algorithm was identical. The difference? Dynamic dispatch — every method call went through a vtable lookup instead of being inlined. That day I learned to respect what &lt;code&gt;dyn&lt;/code&gt; really costs. Let&amp;rsquo;s open the hood.&lt;/p&gt;
&lt;h2 id="static-vs-dynamic-dispatch"&gt;Static vs Dynamic Dispatch&lt;/h2&gt;
&lt;p&gt;Rust gives you two ways to call methods on trait objects: static dispatch (generics) and dynamic dispatch (&lt;code&gt;dyn Trait&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>Lesson 3: Box — Heap allocation by choice</title><link>/post/rust/rust-internals-box/</link><pubDate>Wed, 05 Mar 2025 08:30:00 +0000</pubDate><guid>/post/rust/rust-internals-box/</guid><description>&lt;p&gt;When I first started writing Rust, I used &lt;code&gt;Box&lt;/code&gt; everywhere. Came from a Java background where everything lives on the heap, and wrapping things in &lt;code&gt;Box&lt;/code&gt; felt natural. Took me a while to realize I was fighting the language — most of the time, you don&amp;rsquo;t need it. But when you do need &lt;code&gt;Box&lt;/code&gt;, nothing else will do.&lt;/p&gt;
&lt;h2 id="what-box-actually-is"&gt;What Box Actually Is&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; is the simplest smart pointer in Rust. It allocates a value of type &lt;code&gt;T&lt;/code&gt; on the heap and gives you ownership of that allocation through a pointer on the stack. When the &lt;code&gt;Box&lt;/code&gt; goes out of scope, it frees the heap memory. That&amp;rsquo;s it. No reference counting, no garbage collection, no magic.&lt;/p&gt;</description></item><item><title>Lesson 2: Stack vs Heap — Where your data actually lives</title><link>/post/rust/rust-internals-stack-heap/</link><pubDate>Mon, 03 Mar 2025 14:45:00 +0000</pubDate><guid>/post/rust/rust-internals-stack-heap/</guid><description>&lt;p&gt;A colleague once asked me why their Rust program was ten times slower than expected. They were allocating a &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt; inside a tight loop — millions of heap allocations per second. Moved the vec outside the loop, pre-allocated with &lt;code&gt;with_capacity&lt;/code&gt;, and the function went from 2 seconds to 80 milliseconds. Stack vs heap isn&amp;rsquo;t an academic distinction. It&amp;rsquo;s the difference between fast code and slow code.&lt;/p&gt;
&lt;h2 id="two-memory-regions-two-personalities"&gt;Two Memory Regions, Two Personalities&lt;/h2&gt;
&lt;p&gt;Your program has (at minimum) two regions of memory to work with: the &lt;strong&gt;stack&lt;/strong&gt; and the &lt;strong&gt;heap&lt;/strong&gt;. They behave differently, perform differently, and serve different purposes. Rust makes the choice between them more explicit than most languages, which is one of the reasons it&amp;rsquo;s fast by default.&lt;/p&gt;</description></item><item><title>Lesson 1: Memory Layout — Size, alignment, and the padding you never asked for</title><link>/post/rust/rust-internals-memory-layout/</link><pubDate>Sat, 01 Mar 2025 10:22:00 +0000</pubDate><guid>/post/rust/rust-internals-memory-layout/</guid><description>&lt;p&gt;I spent an embarrassing amount of time debugging a networking project where my hand-crafted packet structs were mysteriously three bytes too large. Turns out the compiler was inserting padding I didn&amp;rsquo;t know about. That&amp;rsquo;s when I realized most Rust developers — myself included — treat memory layout as a black box. Let&amp;rsquo;s crack it open.&lt;/p&gt;
&lt;h2 id="why-layout-matters"&gt;Why Layout Matters&lt;/h2&gt;
&lt;p&gt;Every type in Rust has two fundamental properties: &lt;strong&gt;size&lt;/strong&gt; and &lt;strong&gt;alignment&lt;/strong&gt;. The size is how many bytes the value occupies. The alignment is which memory addresses the value is allowed to start at. These two numbers determine everything about how your data sits in memory, how much RAM your program uses, and whether your structs can talk to C code or hardware registers.&lt;/p&gt;</description></item><item><title>Lesson 7: Values Copy vs Share — When Go copies and when it doesn't</title><link>/post/go/go-internals-copy-share/</link><pubDate>Sat, 01 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-internals-copy-share/</guid><description>&lt;p&gt;Early in my Go career I had a bug where I modified a slice inside a function expecting the caller&amp;rsquo;s slice to remain unchanged — and it did. Then I had a different bug where I expected the opposite and the caller&amp;rsquo;s slice &lt;em&gt;was&lt;/em&gt; modified. I had no mental model for predicting which would happen. Building that model is what this lesson is about.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Go passes everything by value. That&amp;rsquo;s the official line, and it&amp;rsquo;s true. But &amp;ldquo;by value&amp;rdquo; means different things for different types:&lt;/p&gt;</description></item><item><title>Lesson 6: Memory Alignment and Struct Padding — Field order affects struct size</title><link>/post/go/go-internals-alignment/</link><pubDate>Wed, 08 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-internals-alignment/</guid><description>&lt;p&gt;I once reviewed a PR where someone had defined a struct with a bool field between two int64 fields, resulting in a 24-byte struct instead of the 17 bytes you might naively calculate. When I pointed it out, the response was &amp;ldquo;the compiler should handle that.&amp;rdquo; It doesn&amp;rsquo;t. Go respects the field order you give it, and it inserts padding to satisfy alignment requirements. Knowing the rules means you write efficient structs by default.&lt;/p&gt;</description></item><item><title>Lesson 5: GC Behavior and Tuning — GOGC and GOMEMLIMIT changed the game</title><link>/post/go/go-internals-gc/</link><pubDate>Fri, 15 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-internals-gc/</guid><description>&lt;p&gt;For years, tuning Go&amp;rsquo;s GC meant tweaking &lt;code&gt;GOGC&lt;/code&gt; and hoping for the best. I operated on vibes and guesswork. Then Go 1.19 introduced &lt;code&gt;GOMEMLIMIT&lt;/code&gt; — a hard memory limit that fundamentally changed how I reason about GC tuning. Suddenly I had a second axis of control that actually matched how production memory is constrained. This lesson is the mental model I wish I had from day one.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Go uses a concurrent, tri-color mark-and-sweep garbage collector. &amp;ldquo;Concurrent&amp;rdquo; means most of the GC work happens while your program is running, not in stop-the-world pauses. This is generally excellent, but the GC needs to be triggered somehow, and the default trigger can be surprising.&lt;/p&gt;</description></item><item><title>Lesson 4: Escape Analysis Deep Dive — The compiler decides where your data lives</title><link>/post/go/go-internals-escape-analysis/</link><pubDate>Sat, 05 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-internals-escape-analysis/</guid><description>&lt;p&gt;I used to assume that every time I wrote &lt;code&gt;&amp;amp;someStruct{}&lt;/code&gt; or returned a pointer from a function, I was creating a heap allocation. I was wrong — and the wrongness mattered for how I was designing APIs. After learning about escape analysis, I stopped guessing and started &lt;em&gt;asking the compiler&lt;/em&gt; directly. This changed how I write Go.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Go has two places to put data: the &lt;strong&gt;stack&lt;/strong&gt; and the &lt;strong&gt;heap&lt;/strong&gt;. Stack allocations are cheap — they&amp;rsquo;re just a pointer bump, and the stack frame is reclaimed automatically when the function returns. Heap allocations go through the memory allocator, require garbage collection, and have non-trivial overhead.&lt;/p&gt;</description></item><item><title>Lesson 3: Method Sets and Addressability — Why your value can't satisfy that interface</title><link>/post/go/go-internals-method-sets/</link><pubDate>Sun, 25 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-internals-method-sets/</guid><description>&lt;p&gt;I spent a solid twenty minutes staring at a compilation error that read &amp;ldquo;does not implement interface (Write method has pointer receiver)&amp;rdquo; and thinking: I &lt;em&gt;can see&lt;/em&gt; the Write method right there. What is Go complaining about? Once I understood method sets and why addressability matters, the rule became obvious — and I have never needed to look it up since.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Go lets you define methods with either a value receiver or a pointer receiver:&lt;/p&gt;</description></item><item><title>Lesson 2: Nil Interface Gotchas — nil is not nil when it has a type</title><link>/post/go/go-internals-nil-interface/</link><pubDate>Thu, 18 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-internals-nil-interface/</guid><description>&lt;p&gt;A few months into my first production Go service, I had a bug that cost me two hours. A function returned an &lt;code&gt;error&lt;/code&gt;, I checked &lt;code&gt;if err != nil&lt;/code&gt; and it passed — but then further down the stack the program panicked trying to call &lt;code&gt;.Error()&lt;/code&gt; on a nil pointer. The function had returned a nil &lt;code&gt;*MyError&lt;/code&gt;, not a nil &lt;code&gt;error&lt;/code&gt;. These are not the same thing, and until you internalize why, you will hit this bug.&lt;/p&gt;</description></item><item><title>Lesson 1: Interface Representation — Two words that carry your abstractions</title><link>/post/go/go-internals-interfaces/</link><pubDate>Mon, 10 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-internals-interfaces/</guid><description>&lt;p&gt;I remember the first time I hit a bug where &lt;code&gt;err != nil&lt;/code&gt; returned &lt;code&gt;true&lt;/code&gt; even though the function clearly returned &lt;code&gt;nil&lt;/code&gt;. I spent forty minutes staring at perfectly reasonable-looking code before I realized I had no idea what an interface actually &lt;em&gt;is&lt;/em&gt; at the memory level. Once I understood the two-word representation, that class of bug never confused me again.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Go interfaces are the backbone of polymorphism in the language. You use them constantly — &lt;code&gt;io.Reader&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;fmt.Stringer&lt;/code&gt;. But most developers treat them as magic. You assign a concrete value, you call methods, and somehow Go figures out which implementation to dispatch to at runtime.&lt;/p&gt;</description></item></channel></rss>