<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Generics on</title><link>/tags/generics/</link><description>Recent content in Generics on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sun, 17 Nov 2024 00:00:00 +0000</lastBuildDate><atom:link href="/tags/generics/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 8: When Duplication Is Better Than Abstraction — Bad abstraction costs more than repeated code</title><link>/post/go/go-generics-duplication-vs-abstraction/</link><pubDate>Sun, 17 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-duplication-vs-abstraction/</guid><description>&lt;p&gt;I want to end this course with the idea I wish I&amp;rsquo;d understood at the start — not just intellectually, but in my gut. It&amp;rsquo;s this: &lt;strong&gt;duplication is a problem you can see. Bad abstraction is a problem you can&amp;rsquo;t see until it&amp;rsquo;s too late.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Duplicated code is visible. You grep for it, you find it, you fix it. It&amp;rsquo;s annoying, but it&amp;rsquo;s honest. Bad abstraction hides behind clean-looking code. It&amp;rsquo;s the function that requires fifteen minutes of context to understand, the type parameter that nobody knows how to satisfy, the constraint that&amp;rsquo;s technically correct but practically useless. It costs you in onboarding, in debugging, in every change that touches it for the rest of the codebase&amp;rsquo;s life.&lt;/p&gt;</description></item><item><title>Lesson 7: Refactoring Concrete to Generic — Start concrete, extract when proven</title><link>/post/go/go-generics-refactoring/</link><pubDate>Fri, 25 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-refactoring/</guid><description>&lt;p&gt;The advice &amp;ldquo;start concrete, extract when proven&amp;rdquo; is easy to say and surprisingly hard to follow when you&amp;rsquo;re in the middle of writing the third version of the same function. The temptation to generalize early is real. I&amp;rsquo;ve felt it. Most engineers who care about clean code have felt it.&lt;/p&gt;
&lt;p&gt;But the cost of premature abstraction is higher than the cost of temporary duplication. A duplicated function can be removed with a search-and-replace. A bad abstraction gets built around, extended in wrong directions, and defended by sunk-cost thinking. This lesson is about how to do the refactoring correctly — waiting until the pattern is proven, then extracting it cleanly.&lt;/p&gt;</description></item><item><title>Lesson 6: Real-World Examples — Generics that survived code review</title><link>/post/go/go-generics-real-world/</link><pubDate>Wed, 02 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-real-world/</guid><description>&lt;p&gt;Lessons 1 through 5 were mostly about principles. This one is about code. Specifically, the five generic implementations I&amp;rsquo;ve used in real production systems that my teammates didn&amp;rsquo;t complain about. Each one passed code review, made it into production, and held up over time.&lt;/p&gt;
&lt;p&gt;The pattern across all of them is consistent: the algorithm is identical regardless of the type, the duplication without generics would have been mechanical and ongoing, and the generic version is genuinely easier to read than the alternative.&lt;/p&gt;</description></item><item><title>Lesson 5: Anti-Patterns — Just because you can doesn't mean you should</title><link>/post/go/go-generics-anti-patterns/</link><pubDate>Sun, 08 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-anti-patterns/</guid><description>&lt;p&gt;Every powerful tool has a failure mode that looks like success. With generics, the failure mode is this: you write something that compiles, works correctly, and is impressively abstract — but your teammates can&amp;rsquo;t read it, can&amp;rsquo;t debug it, and quietly work around it. I&amp;rsquo;ve written code like that. It felt clever in the moment. It was a problem in practice.&lt;/p&gt;
&lt;p&gt;This lesson is about the patterns that sound good and turn out badly. I&amp;rsquo;m calling them out explicitly because they&amp;rsquo;re seductive — especially if you&amp;rsquo;ve spent time in Haskell or Scala and you know what generic abstractions &lt;em&gt;can&lt;/em&gt; look like.&lt;/p&gt;</description></item><item><title>Lesson 4: Interfaces vs Generics — Behavior or data shape? That's your answer</title><link>/post/go/go-generics-interfaces-vs-generics/</link><pubDate>Wed, 14 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-interfaces-vs-generics/</guid><description>&lt;p&gt;One of the most common mistakes I see in Go codebases post-1.18 is people reaching for generics when interfaces were already the right tool — and vice versa. The two features look superficially similar. Both let you write code that works with multiple types. But they&amp;rsquo;re solving different problems, and using the wrong one makes code harder to read, test, and extend.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the question I ask myself every time: &lt;strong&gt;Is what varies the behavior, or the data shape?&lt;/strong&gt; That single question gets me to the right answer ninety percent of the time.&lt;/p&gt;</description></item><item><title>Lesson 3: Good Patterns — Generic code should be boring</title><link>/post/go/go-generics-good-patterns/</link><pubDate>Mon, 22 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-good-patterns/</guid><description>&lt;p&gt;The best generic code I&amp;rsquo;ve ever written looks like the worst. No clever type wizardry, no five-level constraint hierarchies — just a function that clearly does one thing, works for any type that makes sense, and disappears into the background of a codebase. When someone reads it two years later and immediately understands it, that&amp;rsquo;s a win.&lt;/p&gt;
&lt;p&gt;This lesson is about the patterns where generics genuinely shine. These are the ones that survived code review, that made the team&amp;rsquo;s lives measurably better, and that I&amp;rsquo;d reach for again without hesitation.&lt;/p&gt;</description></item><item><title>Lesson 15: GATs — Generic associated types explained</title><link>/post/rust/rust-generics-gats/</link><pubDate>Fri, 12 Jul 2024 18:00:00 +0000</pubDate><guid>/post/rust/rust-generics-gats/</guid><description>&lt;p&gt;GATs (Generic Associated Types) took &lt;em&gt;seven years&lt;/em&gt; from proposal to stabilization. That&amp;rsquo;s not because Rust&amp;rsquo;s team is slow — it&amp;rsquo;s because GATs are genuinely hard to get right, and they unlock patterns that were previously impossible without unsafe code or painful workarounds. When they finally landed in Rust 1.65, I immediately rewrote a chunk of a database abstraction layer that had been haunting me for months.&lt;/p&gt;
&lt;p&gt;The one-liner: GATs let associated types have their own generic parameters, including lifetimes.&lt;/p&gt;</description></item><item><title>Lesson 14: Const Generics — Types parameterized by values</title><link>/post/rust/rust-generics-const-generics/</link><pubDate>Wed, 10 Jul 2024 15:30:00 +0000</pubDate><guid>/post/rust/rust-generics-const-generics/</guid><description>&lt;p&gt;Before const generics landed (Rust 1.51), working with arrays was painful. You couldn&amp;rsquo;t write a function that accepted &lt;code&gt;[T; N]&lt;/code&gt; for any &lt;code&gt;N&lt;/code&gt;. The standard library had trait implementations for arrays up to size 32 — manually written, one per size. Need &lt;code&gt;[u8; 33]&lt;/code&gt; to implement &lt;code&gt;Debug&lt;/code&gt;? Too bad. That era is over, and honestly, const generics are one of the most underappreciated features in modern Rust.&lt;/p&gt;
&lt;p&gt;Instead of parameterizing types by &lt;em&gt;other types&lt;/em&gt;, you parameterize them by &lt;em&gt;values&lt;/em&gt;. An array&amp;rsquo;s size, a buffer&amp;rsquo;s capacity, a matrix&amp;rsquo;s dimensions — baked into the type system at compile time.&lt;/p&gt;</description></item><item><title>Lesson 13: Monomorphization — How generics become fast</title><link>/post/rust/rust-generics-monomorphization/</link><pubDate>Sun, 07 Jul 2024 11:45:00 +0000</pubDate><guid>/post/rust/rust-generics-monomorphization/</guid><description>&lt;p&gt;&amp;ldquo;Zero-cost abstractions&amp;rdquo; is Rust&amp;rsquo;s battle cry. But what does that actually mean for generics? How does &lt;code&gt;Vec&amp;lt;i32&amp;gt;&lt;/code&gt; and &lt;code&gt;Vec&amp;lt;String&amp;gt;&lt;/code&gt; both exist without runtime overhead? The answer is monomorphization — the compiler generates a separate, specialized copy of your generic code for each concrete type used. You write it once, the compiler duplicates it for each type, and the result runs as fast as hand-written specialized code.&lt;/p&gt;
&lt;p&gt;This is brilliant. It&amp;rsquo;s also a double-edged sword. And understanding the mechanism changes how you design generic APIs.&lt;/p&gt;</description></item><item><title>Lesson 12: Operator Overloading with Traits — Making your types feel native</title><link>/post/rust/rust-traits-operator-overloading/</link><pubDate>Thu, 04 Jul 2024 20:15:00 +0000</pubDate><guid>/post/rust/rust-traits-operator-overloading/</guid><description>&lt;p&gt;I was building a linear algebra library and got tired of writing &lt;code&gt;vector_a.add(&amp;amp;vector_b)&lt;/code&gt; everywhere. It looked ugly. It read poorly. Math should look like math — &lt;code&gt;a + b&lt;/code&gt;, not &lt;code&gt;a.add(&amp;amp;b)&lt;/code&gt;. In Rust, operator overloading isn&amp;rsquo;t some dark magic — it&amp;rsquo;s just trait implementation. Every operator maps to a trait in &lt;code&gt;std::ops&lt;/code&gt;, and implementing that trait makes the operator work on your type.&lt;/p&gt;
&lt;h2 id="the-add-trait"&gt;The &lt;code&gt;Add&lt;/code&gt; Trait&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;+&lt;/code&gt; operator desugars to a call to &lt;code&gt;Add::add&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Lesson 11: Essential Std Traits — Iterator, Display, From, Default</title><link>/post/rust/rust-traits-std-traits/</link><pubDate>Tue, 02 Jul 2024 08:30:00 +0000</pubDate><guid>/post/rust/rust-traits-std-traits/</guid><description>&lt;p&gt;There&amp;rsquo;s a tier list of Rust traits. Some you&amp;rsquo;ll implement once in your career. Some you&amp;rsquo;ll implement weekly. And then there are the ones you&amp;rsquo;ll implement so often they become muscle memory — &lt;code&gt;Display&lt;/code&gt;, &lt;code&gt;From&lt;/code&gt;, &lt;code&gt;Default&lt;/code&gt;, &lt;code&gt;Iterator&lt;/code&gt;. These four (plus a few friends) are the backbone of idiomatic Rust. If you internalize them, your types will feel native. If you skip them, your types will feel like second-class citizens in the ecosystem.&lt;/p&gt;</description></item><item><title>Lesson 2: Type Parameters and Constraints — Teaching the compiler what you mean</title><link>/post/go/go-generics-type-parameters/</link><pubDate>Mon, 01 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-type-parameters/</guid><description>&lt;p&gt;The hardest part of learning Go generics isn&amp;rsquo;t the concept — it&amp;rsquo;s the syntax. The first time I read a function signature like &lt;code&gt;func Keys[K comparable, V any](m map[K]V) []K&lt;/code&gt;, I did a double-take. It looks like someone snuck type annotations from another language into Go. But once it clicks, it&amp;rsquo;s actually pretty elegant — every piece is there for a reason.&lt;/p&gt;
&lt;p&gt;This lesson is about understanding type parameters and constraints deeply enough that you can write them yourself without copying from examples. That&amp;rsquo;s the threshold that separates &amp;ldquo;I&amp;rsquo;ve used generics&amp;rdquo; from &amp;ldquo;I understand generics.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 10: Orphan Rules and the Newtype Workaround — Coherence in practice</title><link>/post/rust/rust-traits-orphan-rules/</link><pubDate>Sun, 30 Jun 2024 17:55:00 +0000</pubDate><guid>/post/rust/rust-traits-orphan-rules/</guid><description>&lt;p&gt;Picture this: you&amp;rsquo;re using two crates — &lt;code&gt;serde&lt;/code&gt; and some &lt;code&gt;db_client&lt;/code&gt; crate. You want to implement &lt;code&gt;serde::Serialize&lt;/code&gt; for &lt;code&gt;db_client::Row&lt;/code&gt;. Sounds reasonable. You write the &lt;code&gt;impl&lt;/code&gt;, and the compiler slaps you: &amp;ldquo;only traits defined in the current crate can be implemented for types defined outside of the current crate.&amp;rdquo; Welcome to the orphan rules.&lt;/p&gt;
&lt;p&gt;My first reaction was frustration. My second reaction, after dealing with diamond dependency problems in C++ for years, was &amp;ldquo;oh, this is actually protecting me.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 9: Blanket Implementations — Implementing for all T</title><link>/post/rust/rust-traits-blanket-impls/</link><pubDate>Fri, 28 Jun 2024 10:20:00 +0000</pubDate><guid>/post/rust/rust-traits-blanket-impls/</guid><description>&lt;p&gt;The moment blanket implementations clicked for me, I felt like I&amp;rsquo;d been handed a cheat code. You write &lt;code&gt;impl&amp;lt;T: Display&amp;gt; ToString for T&lt;/code&gt; and suddenly &lt;em&gt;every single type&lt;/em&gt; that implements &lt;code&gt;Display&lt;/code&gt; automatically gets &lt;code&gt;ToString&lt;/code&gt;. One line of implementation logic, infinite types covered. This is how the standard library builds massive capability trees from small pieces.&lt;/p&gt;
&lt;h2 id="whats-a-blanket-implementation"&gt;What&amp;rsquo;s a Blanket Implementation?&lt;/h2&gt;
&lt;p&gt;A blanket implementation implements a trait for all types matching a bound, rather than for a specific type:&lt;/p&gt;</description></item><item><title>Lesson 8: Object Safety — Why some traits can't be dyn</title><link>/post/rust/rust-traits-object-safety/</link><pubDate>Tue, 25 Jun 2024 13:40:00 +0000</pubDate><guid>/post/rust/rust-traits-object-safety/</guid><description>&lt;p&gt;The first time I got the error &amp;ldquo;the trait &lt;code&gt;Clone&lt;/code&gt; cannot be made into an object,&amp;rdquo; I stared at it for a solid minute. Clone is one of the most fundamental traits in Rust. How can it not work with &lt;code&gt;dyn&lt;/code&gt;? The answer is &lt;em&gt;object safety&lt;/em&gt; — a set of rules that determine which traits can be used as trait objects. It&amp;rsquo;s one of those things that seems arbitrary until you understand &lt;em&gt;why&lt;/em&gt; the rules exist.&lt;/p&gt;</description></item><item><title>Lesson 7: dyn Trait — Runtime polymorphism and its cost</title><link>/post/rust/rust-traits-dynamic-dispatch/</link><pubDate>Sun, 23 Jun 2024 19:15:00 +0000</pubDate><guid>/post/rust/rust-traits-dynamic-dispatch/</guid><description>&lt;p&gt;Every Rust programmer hits this wall eventually. You have a &lt;code&gt;Vec&lt;/code&gt; and you want to put different types in it — all implementing the same trait, but different concrete types. You try &lt;code&gt;Vec&amp;lt;impl Trait&amp;gt;&lt;/code&gt; and the compiler says no. You try &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; with generics and realize &lt;code&gt;T&lt;/code&gt; can only be one type at a time. That&amp;rsquo;s when you discover &lt;code&gt;dyn Trait&lt;/code&gt;, and the first real tradeoff in Rust&amp;rsquo;s type system: static dispatch vs dynamic dispatch.&lt;/p&gt;</description></item><item><title>Lesson 6: Supertraits — Building trait hierarchies</title><link>/post/rust/rust-traits-supertraits/</link><pubDate>Fri, 21 Jun 2024 08:50:00 +0000</pubDate><guid>/post/rust/rust-traits-supertraits/</guid><description>&lt;p&gt;I had a trait called &lt;code&gt;Serialize&lt;/code&gt; (homebrew, pre-serde days) that needed to format things as strings. I kept calling &lt;code&gt;.to_string()&lt;/code&gt; inside default methods and wondering why the compiler complained. The type implementing my trait didn&amp;rsquo;t necessarily implement &lt;code&gt;Display&lt;/code&gt;. The fix was obvious in hindsight — make &lt;code&gt;Display&lt;/code&gt; a &lt;em&gt;supertrait&lt;/em&gt; of &lt;code&gt;Serialize&lt;/code&gt;. If you want to serialize, you must be displayable. Period.&lt;/p&gt;
&lt;p&gt;Supertraits let you build trait hierarchies where implementing one trait &lt;em&gt;requires&lt;/em&gt; implementing another.&lt;/p&gt;</description></item><item><title>Lesson 5: Associated Types vs Generic Parameters — Choosing the right tool</title><link>/post/rust/rust-traits-associated-types/</link><pubDate>Wed, 19 Jun 2024 16:05:00 +0000</pubDate><guid>/post/rust/rust-traits-associated-types/</guid><description>&lt;p&gt;Here&amp;rsquo;s a question that confused me for months: why does &lt;code&gt;Iterator&lt;/code&gt; use an associated type (&lt;code&gt;type Item&lt;/code&gt;) instead of a generic parameter (&lt;code&gt;Iterator&amp;lt;T&amp;gt;&lt;/code&gt;)? They look like they do the same thing. They kinda do. But the choice between them changes your entire API&amp;rsquo;s ergonomics, and picking wrong leads to annoying code downstream.&lt;/p&gt;
&lt;p&gt;The short answer: associated types mean &amp;ldquo;one implementation per type,&amp;rdquo; generic parameters mean &amp;ldquo;many implementations per type.&amp;rdquo; But the implications are deeper than that.&lt;/p&gt;</description></item><item><title>Lesson 4: where Clauses — When bounds get complex</title><link>/post/rust/rust-traits-where-clauses/</link><pubDate>Mon, 17 Jun 2024 11:30:00 +0000</pubDate><guid>/post/rust/rust-traits-where-clauses/</guid><description>&lt;p&gt;You know that moment when a function signature gets so long it wraps three times in your editor and you can&amp;rsquo;t even find the return type? I hit that wall writing a generic cache layer that needed &lt;code&gt;Hash + Eq + Clone + Debug&lt;/code&gt; on the key, &lt;code&gt;Serialize + DeserializeOwned + Clone&lt;/code&gt; on the value, and &lt;code&gt;Display&lt;/code&gt; on both. The inline bounds turned my function signature into an unreadable mess.&lt;/p&gt;</description></item><item><title>Lesson 3: Trait Bounds — Constraining generic types</title><link>/post/rust/rust-traits-trait-bounds/</link><pubDate>Fri, 14 Jun 2024 21:10:00 +0000</pubDate><guid>/post/rust/rust-traits-trait-bounds/</guid><description>&lt;p&gt;I once wrote a generic function that looked perfectly reasonable — accepted any &lt;code&gt;T&lt;/code&gt;, did some work, returned a result. It compiled. Then I tried to actually &lt;em&gt;use&lt;/em&gt; it with a type that didn&amp;rsquo;t have &lt;code&gt;Clone&lt;/code&gt;, and suddenly the compiler was screaming at me with errors pointing at the function internals rather than the call site. That&amp;rsquo;s backwards. The fix? Trait bounds. Declare what you need upfront so the errors land where they belong.&lt;/p&gt;</description></item><item><title>Lesson 2: Default Implementations and Selective Overrides — Don't repeat yourself</title><link>/post/rust/rust-traits-default-impl/</link><pubDate>Wed, 12 Jun 2024 14:45:00 +0000</pubDate><guid>/post/rust/rust-traits-default-impl/</guid><description>&lt;p&gt;Here&amp;rsquo;s something that bugged me when I first started with traits: I had six different types all implementing the same trait, and five of them had &lt;em&gt;identical&lt;/em&gt; method bodies. I was copying the same three lines into five &lt;code&gt;impl&lt;/code&gt; blocks like a human xerox machine. There had to be a better way.&lt;/p&gt;
&lt;p&gt;There is. Default implementations.&lt;/p&gt;
&lt;h2 id="the-basics"&gt;The Basics&lt;/h2&gt;
&lt;p&gt;A trait can provide a default body for any of its methods. Implementors can then choose to override it — or just accept the default:&lt;/p&gt;</description></item><item><title>Lesson 1: Trait Fundamentals — Defining shared behavior</title><link>/post/rust/rust-traits-fundamentals/</link><pubDate>Mon, 10 Jun 2024 09:22:00 +0000</pubDate><guid>/post/rust/rust-traits-fundamentals/</guid><description>&lt;p&gt;I spent my first month in Rust writing &lt;code&gt;impl&lt;/code&gt; blocks that looked suspiciously like Java interfaces. Copy-paste, copy-paste, tweak one method, ship it. Then I hit a wall — a refactor where I needed to swap out a storage backend, and every single call site had hardcoded the concrete type. That&amp;rsquo;s when traits stopped being &amp;ldquo;a feature I should learn&amp;rdquo; and became &amp;ldquo;the thing saving me from rewriting 4,000 lines.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 1: What Generics Solve in Go — Remove duplication without hiding intent</title><link>/post/go/go-generics-what-they-solve/</link><pubDate>Fri, 07 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-what-they-solve/</guid><description>&lt;p&gt;I spent two years writing Go before generics shipped in 1.18, and I&amp;rsquo;ll be honest — I didn&amp;rsquo;t miss them at first. Go&amp;rsquo;s simplicity felt like a feature. Then I found myself maintaining a codebase with six nearly-identical sort functions, a &lt;code&gt;MinInt&lt;/code&gt;, &lt;code&gt;MinFloat64&lt;/code&gt;, &lt;code&gt;MinInt64&lt;/code&gt;, and a comment that said &amp;ldquo;do not touch, generated.&amp;rdquo; That&amp;rsquo;s when I started caring.&lt;/p&gt;
&lt;p&gt;Generics in Go aren&amp;rsquo;t a revolution. They&amp;rsquo;re a targeted fix for a specific, real pain point: you had to either use &lt;code&gt;interface{}&lt;/code&gt; and lose type safety, or repeat yourself across every concrete type you cared about. Neither option aged well.&lt;/p&gt;</description></item></channel></rss>