<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Anti-Patterns on</title><link>/tags/anti-patterns/</link><description>Recent content in Anti-Patterns on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Thu, 24 Apr 2025 09:30:00 +0000</lastBuildDate><atom:link href="/tags/anti-patterns/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 10: Using unsafe to Escape the Borrow Checker — The wrong reason</title><link>/post/rust/rust-anti-unsafe-escape-hatch/</link><pubDate>Thu, 24 Apr 2025 09:30:00 +0000</pubDate><guid>/post/rust/rust-anti-unsafe-escape-hatch/</guid><description>&lt;p&gt;I found this in a production codebase:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_or_insert&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self, key: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;mut&lt;/span&gt; Value {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;self.map.contains_key(key) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self.map.insert(key.to_string(), Value::default());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// &amp;#34;The borrow checker is being stupid, we know the key exists&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;unsafe&lt;/span&gt; { &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;(self.map.get_mut(key).unwrap() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; Value) }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The comment says it all. The developer hit a borrow checker error — a legitimate one about borrowing &lt;code&gt;self.map&lt;/code&gt; mutably twice — and instead of restructuring the code, they cast through a raw pointer to silence the compiler. This is undefined behavior. The compiler is allowed to assume the mutable references don&amp;rsquo;t alias, and it &lt;em&gt;will&lt;/em&gt; optimize based on that assumption. When it does, your program does something you didn&amp;rsquo;t write.&lt;/p&gt;</description></item><item><title>Lesson 9: Macro Abuse — When a function would do</title><link>/post/rust/rust-anti-macro-abuse/</link><pubDate>Mon, 21 Apr 2025 17:08:00 +0000</pubDate><guid>/post/rust/rust-anti-macro-abuse/</guid><description>&lt;p&gt;I spent an entire afternoon debugging a test failure that turned out to be caused by a macro expanding variable names in a way I didn&amp;rsquo;t expect. The macro was called &lt;code&gt;make_handler!&lt;/code&gt; and it generated HTTP handler functions from a declarative DSL that someone on the team had invented. The &amp;ldquo;DSL&amp;rdquo; saved maybe ten lines of boilerplate per handler. The macro definition was 200 lines of nested &lt;code&gt;macro_rules!&lt;/code&gt; with five recursion levels, three &lt;code&gt;tt&lt;/code&gt; munchers, and hygiene workarounds that I&amp;rsquo;m still not convinced were correct. When a new developer asked how to add a query parameter to a handler, nobody could explain it without first teaching them how the macro worked.&lt;/p&gt;</description></item><item><title>Lesson 8: Premature Optimization — Profile before you optimize</title><link>/post/rust/rust-anti-premature-optimization/</link><pubDate>Sat, 19 Apr 2025 13:42:00 +0000</pubDate><guid>/post/rust/rust-anti-premature-optimization/</guid><description>&lt;p&gt;A teammate once spent three days replacing every &lt;code&gt;String&lt;/code&gt; in our data model with a custom arena-allocated string type. The rationale: &amp;ldquo;String allocations are slow, and we&amp;rsquo;re processing a lot of data.&amp;rdquo; Sounds reasonable, right? After the rewrite, I ran the benchmarks. The improvement was within noise — less than 1%. The actual bottleneck was network I/O to an external API, which accounted for 94% of the request latency. Those three days of intricate unsafe string manipulation? Completely wasted. And the code was now harder to read, harder to maintain, and had a subtle use-after-free bug that we found six weeks later.&lt;/p&gt;</description></item><item><title>Lesson 7: Arc&lt;Mutex&lt;T&gt;&gt; as Default — Reach for channels first</title><link>/post/rust/rust-anti-arc-mutex-default/</link><pubDate>Wed, 16 Apr 2025 20:15:00 +0000</pubDate><guid>/post/rust/rust-anti-arc-mutex-default/</guid><description>&lt;p&gt;There&amp;rsquo;s a specific moment in every Rust developer&amp;rsquo;s journey where they discover &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; and start putting everything in it. I&amp;rsquo;ve been that developer. I had a web service that needed to share a cache between request handlers, and my first instinct was &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;HashMap&amp;lt;String, CachedItem&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;. It worked. Then traffic went up, contention went up, tail latency went up, and I spent a weekend profiling lock contention that shouldn&amp;rsquo;t have existed in the first place — because most of my &amp;ldquo;shared mutable state&amp;rdquo; could have been restructured as message passing.&lt;/p&gt;</description></item><item><title>Lesson 6: Trait Bloat — Interface segregation in Rust</title><link>/post/rust/rust-anti-trait-bloat/</link><pubDate>Tue, 15 Apr 2025 09:55:00 +0000</pubDate><guid>/post/rust/rust-anti-trait-bloat/</guid><description>&lt;p&gt;I worked on a project that had a &lt;code&gt;Storage&lt;/code&gt; trait with twenty-three methods. Twenty-three. It handled reading, writing, deleting, listing, searching, watching for changes, managing permissions, computing checksums, and streaming large files. Every backend — S3, local filesystem, in-memory for tests — had to implement all twenty-three methods. The in-memory test backend had fourteen methods that just returned &lt;code&gt;unimplemented!()&lt;/code&gt;. The local filesystem backend panicked on the permissions methods because POSIX permissions don&amp;rsquo;t map to the trait&amp;rsquo;s model. And every time someone added a new method to the trait, every backend had to be updated — even the ones where the new method made no sense.&lt;/p&gt;</description></item><item><title>Lesson 5: Over-Genericizing — Not everything needs &lt;T&gt;</title><link>/post/rust/rust-anti-over-generic/</link><pubDate>Sun, 13 Apr 2025 11:28:00 +0000</pubDate><guid>/post/rust/rust-anti-over-generic/</guid><description>&lt;p&gt;I reviewed a library last year where the author had made literally everything generic. The HTTP client was generic over the transport, the serializer, the deserializer, the error type, the retry policy, the timeout strategy, and the logger. Using it required spelling out a type signature that looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; client: &lt;span style="color:#a6e22e"&gt;HttpClient&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TcpTransport&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;TlsConfig&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; JsonSerializer&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;PrettyPrint&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; JsonDeserializer&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;StrictMode&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AppError,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ExponentialBackoff&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;SystemClock&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FixedTimeout,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; SlogLogger&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;JsonFormat&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; HttpClient::new(&lt;span style="color:#75715e"&gt;/* ... */&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The kicker? The library was an internal tool used by exactly one team. There was one transport, one serializer, one error type. Every type parameter had exactly one implementation. The author had written an extensible framework for a problem that didn&amp;rsquo;t need extending.&lt;/p&gt;</description></item><item><title>Lesson 4: God Structs — When types do too much</title><link>/post/rust/rust-anti-god-struct/</link><pubDate>Fri, 11 Apr 2025 16:10:00 +0000</pubDate><guid>/post/rust/rust-anti-god-struct/</guid><description>&lt;p&gt;I once opened a file called &lt;code&gt;app.rs&lt;/code&gt; and found a struct with forty-two fields. Forty-two. It held the database connection, the HTTP client, the cache handle, the logger, the config, the metrics collector, the rate limiter, the auth provider, the feature flags, the email sender, the template engine, and about thirty other things I&amp;rsquo;ve blocked from memory. Every function in the codebase took &lt;code&gt;&amp;amp;self&lt;/code&gt; on this monster. Need to send an email? You need the god struct. Parse a config value? God struct. Log a message? Believe it or not, god struct.&lt;/p&gt;</description></item><item><title>Lesson 3: Stringly Typed APIs — Use enums, not strings</title><link>/post/rust/rust-anti-stringly-typed/</link><pubDate>Wed, 09 Apr 2025 08:47:00 +0000</pubDate><guid>/post/rust/rust-anti-stringly-typed/</guid><description>&lt;p&gt;I inherited a Rust codebase once where the entire state machine was driven by string comparisons. The order status could be &lt;code&gt;&amp;quot;pending&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;processing&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;shipped&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;delivered&amp;quot;&lt;/code&gt;, or &lt;code&gt;&amp;quot;cancelled&amp;quot;&lt;/code&gt;. Except sometimes it was &lt;code&gt;&amp;quot;Pending&amp;quot;&lt;/code&gt; with a capital P. And there was one code path that set it to &lt;code&gt;&amp;quot;canceled&amp;quot;&lt;/code&gt; — one L, American spelling. And another that used &lt;code&gt;&amp;quot;CANCELLED&amp;quot;&lt;/code&gt;. The bug lived in production for weeks because nobody could figure out why some orders were getting stuck in a phantom state that didn&amp;rsquo;t match any of the &lt;code&gt;if status == &amp;quot;cancelled&amp;quot;&lt;/code&gt; checks scattered across thirty files.&lt;/p&gt;</description></item><item><title>Lesson 2: unwrap() in Production — Time bombs waiting to explode</title><link>/post/rust/rust-anti-unwrap-abuse/</link><pubDate>Mon, 07 Apr 2025 14:35:00 +0000</pubDate><guid>/post/rust/rust-anti-unwrap-abuse/</guid><description>&lt;p&gt;A service I was responsible for went down at 2 AM on a Saturday because of a single &lt;code&gt;.unwrap()&lt;/code&gt; call on line 847 of a file nobody had touched in months. The function parsed a config value that was supposed to always be present. Somebody changed the config format in a different repo, the value became optional, and that &lt;code&gt;.unwrap()&lt;/code&gt; detonated like a landmine — &lt;code&gt;thread 'main' panicked at 'called Option::unwrap() on a None value'&lt;/code&gt;. Down. Dead. Pager screaming.&lt;/p&gt;</description></item><item><title>Lesson 1: .clone() Everywhere — Hiding ownership problems</title><link>/post/rust/rust-anti-clone-everywhere/</link><pubDate>Sat, 05 Apr 2025 10:22:00 +0000</pubDate><guid>/post/rust/rust-anti-clone-everywhere/</guid><description>&lt;p&gt;I was reviewing a pull request last year from a developer who&amp;rsquo;d been writing Rust for about three months. The code compiled. The tests passed. Everything looked fine — until I ran &lt;code&gt;grep -c '\.clone()' src/&lt;/code&gt; and got back a number that made me physically uncomfortable. Forty-seven clones across six files. The codebase was a data pipeline processing millions of events per hour, and this person had turned every ownership error into a &lt;code&gt;.clone()&lt;/code&gt; call until the compiler stopped yelling.&lt;/p&gt;</description></item></channel></rss>