<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>rust on krtffl.dev</title>
    <link>https://krtffl.dev/categories/rust/</link>
    <description>Recent content in rust on krtffl.dev</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <copyright>© [krtffl](https://krtffl.dev)</copyright>
    <lastBuildDate>Mon, 15 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://krtffl.dev/categories/rust/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>one schema crate, two analyzers: bilingual full-text search with embedded tantivy</title>
      <link>https://krtffl.dev/posts/tantivy-bilingual-search-rust/</link>
      <pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/tantivy-bilingual-search-rust/</guid>
      <description>&lt;p&gt;you can see the word. it&amp;rsquo;s right there on the page — &lt;code&gt;filósofos&lt;/code&gt;, third line of a plutarch translation i typed out by hand. you paste it into the search box, hit enter, and the archive tells you it has never heard of it. no error, no red box, no &amp;ldquo;did you mean.&amp;rdquo; a clean, confident, empty result set — byte-for-byte the same empty set you&amp;rsquo;d get for a word that genuinely isn&amp;rsquo;t in the corpus.&lt;/p&gt;
&lt;p&gt;the index knew the word. i had watched it index the word. the reader just asked for it in a dialect the writer never spoke.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>two databases, one binary: duckdb for reads, sqlite for state</title>
      <link>https://krtffl.dev/posts/duckdb-sqlite-one-binary/</link>
      <pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/duckdb-sqlite-one-binary/</guid>
      <description>&lt;p&gt;the tracking endpoint has exactly one job: say &lt;code&gt;202&lt;/code&gt; and get out of the way. a beacon fires on someone else&amp;rsquo;s website, my server catches it, and the visitor is already three paragraphs into an article before my response even lands. nobody is waiting on that request — the whole point of a &lt;code&gt;sendBeacon&lt;/code&gt; is that the page has already moved on. so the afternoon i read a flame graph and found the ingest handler parked &lt;em&gt;inside&lt;/em&gt; a &lt;code&gt;duckdb&lt;/code&gt; &lt;code&gt;INSERT&lt;/code&gt;, holding a request open while the disk did its thing, i felt a very specific kind of stupid. i&amp;rsquo;d taken a fire-and-forget pixel and taught it to block on fsync.&lt;/p&gt;
&lt;p&gt;fixing that is most of this post. but the fix only makes sense on top of a decision most tutorials would have talked me out of: i put two different databases in one small rust binary, on purpose.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>brute-forcing the pit window: when &#39;just try them all&#39; is the right call</title>
      <link>https://krtffl.dev/posts/pit-window-brute-force-search/</link>
      <pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/pit-window-brute-force-search/</guid>
      <description>&lt;p&gt;lights out. and this time the car has to stop.&lt;/p&gt;
&lt;p&gt;two posts ago i drew stintlab&amp;rsquo;s charts in rust on canvas2d and swore a chart was just rectangles and lines. one post ago i fit the &lt;a href=&#34;https://krtffl.dev/posts/ols-regression-collinearity-rust&#34;&gt;tire-degradation model&lt;/a&gt;
 by hand — ordinary least squares, cramer&amp;rsquo;s rule, and a singular-matrix guard that turned a collinearity bug into an honest &lt;code&gt;None&lt;/code&gt;. both of those were setup. this is the payoff: the thing that takes that model and actually makes a decision.&lt;/p&gt;
&lt;p&gt;because a degradation model, on its own, just draws a number — how much slower a tire gets, lap after lap. lovely. now what? a real race is decided in one call from the pit wall: &lt;em&gt;when do you box, and onto what rubber?&lt;/em&gt; stop too early and you throw away tire life; too late and you bleed seconds to a fresher car behind. that call wins races. i wanted to make it automatically, and the algorithm i reached for is the single dumbest one in the book: try every option and keep the best.&lt;/p&gt;
&lt;p&gt;and i&amp;rsquo;m going to argue that&amp;rsquo;s exactly right.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>a query language in ~1000 lines: lexer, parser, evaluator</title>
      <link>https://krtffl.dev/posts/query-dsl-lexer-parser-rust/</link>
      <pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/query-dsl-lexer-parser-rust/</guid>
      <description>&lt;p&gt;here&amp;rsquo;s a question that sounds trivial and isn&amp;rsquo;t: &lt;em&gt;show me every 5xx response that also mentions a timeout.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;in &lt;code&gt;grep&lt;/code&gt; that&amp;rsquo;s a fragile little dance — &lt;code&gt;grep -E &#39;status[=:] ?5[0-9]{2}&#39;&lt;/code&gt; piped into another &lt;code&gt;grep timeout&lt;/code&gt;, and now you&amp;rsquo;re matching &lt;code&gt;500&lt;/code&gt; inside a request id, &lt;code&gt;timeout&lt;/code&gt; inside a url, and you&amp;rsquo;ve quietly stopped trusting the output. what you &lt;em&gt;wanted&lt;/em&gt; was this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34;&gt;
    &lt;div class=&#34;codeblock__bar&#34;&gt;
        &lt;span class=&#34;codeblock__lang&#34;&gt;text&lt;/span&gt;
        &lt;button class=&#34;codeblock__copy&#34; type=&#34;button&#34; data-copy aria-label=&#34;copy code&#34;&gt;copy ⧉&lt;/button&gt;
    &lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;status &amp;gt;= 500 AND message contains &amp;#34;timeout&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;and have the machine read &lt;code&gt;&amp;gt;=&lt;/code&gt; as &lt;em&gt;numeric&lt;/em&gt; greater-or-equal, &lt;code&gt;AND&lt;/code&gt; as boolean conjunction, and &lt;code&gt;&amp;quot;timeout&amp;quot;&lt;/code&gt; as a substring on one named field. that&amp;rsquo;s not grep. that&amp;rsquo;s a little language, and little languages need a lexer, a parser, and something to run them.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>clean and verified are different claims: a two-axis trust state machine in postgres</title>
      <link>https://krtffl.dev/posts/two-axis-trust-state-machine/</link>
      <pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/two-axis-trust-state-machine/</guid>
      <description>&lt;p&gt;ask a language model for a line of tacitus in spanish and it will hand you a sentence. fluent, confident, correctly punctuated. and sometimes the sentence contains a word that no translator ever wrote — a word a scanner invented, when it read the &lt;code&gt;á&lt;/code&gt; in a 1919 printing as the digit &lt;code&gt;4&lt;/code&gt;, or turned &lt;em&gt;violento&lt;/em&gt; into &lt;code&gt;violen10&lt;/code&gt;, and then a spell-checker came along afterward and smoothed the wreckage into something that &lt;em&gt;looks&lt;/em&gt; like spanish.&lt;/p&gt;
&lt;p&gt;the text is clean. it is not correct. and nothing about the way it reads tells you which.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>cookieless analytics: how a rotating daily salt kills the consent banner</title>
      <link>https://krtffl.dev/posts/cookieless-analytics-rotating-salt/</link>
      <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/cookieless-analytics-rotating-salt/</guid>
      <description>&lt;p&gt;every week i click &amp;ldquo;reject all&amp;rdquo; maybe forty times. cookie wall, reject all. cookie wall, manage preferences, uncheck the eight hundred &amp;ldquo;legitimate interest&amp;rdquo; partners, confirm. the banner that buries its reject button two clicks deep because the law says both choices must be equally easy and everyone pretends theirs is. it&amp;rsquo;s the loading screen of the modern web, and almost nobody &lt;em&gt;consents&lt;/em&gt; to anything — they just want the thing gone.&lt;/p&gt;
&lt;p&gt;here&amp;rsquo;s the part that took me embarrassingly long to internalise: most of those banners exist to prop up analytics that never needed your personal data in the first place.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>hand-rolling ols regression in rust, and the collinearity trap that ate my coefficients</title>
      <link>https://krtffl.dev/posts/ols-regression-collinearity-rust/</link>
      <pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/ols-regression-collinearity-rust/</guid>
      <description>&lt;p&gt;lights out, again.&lt;/p&gt;
&lt;p&gt;last time out i argued that a chart is just rectangles and lines, drew stintlab&amp;rsquo;s entire ui in rust on canvas2d to prove it, and promised — right at the bottom of &lt;a href=&#34;https://krtffl.dev/posts/rust-wasm-canvas-charts&#34;&gt;that post&lt;/a&gt;
 — to come back for &lt;em&gt;the degradation model itself&lt;/em&gt;: the math the pictures are pictures of. this is that post.&lt;/p&gt;
&lt;p&gt;the pictures draw a number. how much slower a tire gets, lap after lap, as it wears out. pin that number down and you can start to guess when a car should box. so i tried to fit it with a multiple linear regression i wrote by hand — no nalgebra, no ndarray, just the normal equations and a determinant — and the thing handed me back confident, well-formed garbage. it took a comment in one of my own tests to explain why.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>rust &#43; wasm canvas2d charts with zero javascript libraries</title>
      <link>https://krtffl.dev/posts/rust-wasm-canvas-charts/</link>
      <pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/rust-wasm-canvas-charts/</guid>
      <description>&lt;p&gt;lights out. one car jumps another off the line, and forty laps later a strategist on the pit wall wins the whole race with a single number: &lt;em&gt;when to box&lt;/em&gt;. i don&amp;rsquo;t have a pit wall. i have a laptop, a &lt;a href=&#34;https://openf1.org&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;free public api ↗&lt;/a&gt;
, and an unreasonable urge to see tire strategy as a &lt;em&gt;picture&lt;/em&gt; instead of a table of lap times.&lt;/p&gt;
&lt;p&gt;so i wanted a chart. the boring way to get one is &lt;code&gt;npm install&lt;/code&gt; something with a d3 buried in its dependency tree, feed it an array, and move on with my life. i did not do the boring way. i drew the whole thing in rust, compiled it to webassembly, and painted it onto a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; pixel by pixel with zero javascript charting libraries in the bundle.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>welford&#39;s algorithm and why i never store the samples</title>
      <link>https://krtffl.dev/posts/welford-online-variance-rust/</link>
      <pubDate>Mon, 13 Apr 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/welford-online-variance-rust/</guid>
      <description>&lt;p&gt;you want to know if the error rate just spiked. that&amp;rsquo;s a z-score: how many standard deviations is &lt;em&gt;right now&lt;/em&gt; away from what &lt;em&gt;normal&lt;/em&gt; looked like. simple formula, &lt;code&gt;(x - mean) / stddev&lt;/code&gt;, you learned it before you could legally drink.&lt;/p&gt;
&lt;p&gt;except a standard deviation needs a mean, and a mean needs all the numbers, and the numbers are a log file the size of a small moon. so where, exactly, are you keeping them?&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>building three mcp servers in rust with rmcp</title>
      <link>https://krtffl.dev/posts/mcp-servers-in-rust/</link>
      <pubDate>Mon, 30 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/mcp-servers-in-rust/</guid>
      <description>&lt;p&gt;i wanted to ask an llm &amp;ldquo;is my server on fire?&amp;rdquo; without ssh-ing into the box, running &lt;code&gt;htop&lt;/code&gt;, squinting, giving up, and running &lt;code&gt;docker ps&lt;/code&gt; instead. i wanted it to just &lt;em&gt;know&lt;/em&gt; — pull the cpu number, list the containers, hit prometheus, and tell me in a sentence.&lt;/p&gt;
&lt;p&gt;the model context protocol makes that possible. you hand an llm a set of tools, it decides when to call them. fine. so i built one server for my infra. then i wanted one for spanish government open data — the boe, the tax calendar, the property registry. then a third for formula 1 telemetry, because it&amp;rsquo;s my blog and i do what i want.&lt;/p&gt;
&lt;p&gt;three servers. and i could already see the future: three near-identical binaries, three copies of the same http-client boilerplate, three subtly different cache bugs. the copy-paste tax.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>implementing the drain algorithm in rust</title>
      <link>https://krtffl.dev/posts/drain-algorithm-in-rust/</link>
      <pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/drain-algorithm-in-rust/</guid>
      <description>&lt;p&gt;picture the scene: something is on fire in production, you ssh into the box, &lt;code&gt;tail&lt;/code&gt; a log file, and you&amp;rsquo;re greeted by forty thousand lines that all look &lt;em&gt;almost&lt;/em&gt; the same but not quite. your eyes glaze over. you &lt;code&gt;grep error&lt;/code&gt;, then &lt;code&gt;grep -v&lt;/code&gt; the noise, then pipe the whole thing into &lt;code&gt;sort | uniq -c&lt;/code&gt; and pray.&lt;/p&gt;
&lt;p&gt;there&amp;rsquo;s a better way, and it&amp;rsquo;s older than you&amp;rsquo;d think.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>i didn&#39;t leave go for rust. i split my stack.</title>
      <link>https://krtffl.dev/posts/why-i-left-go-for-rust/</link>
      <pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://krtffl.dev/posts/why-i-left-go-for-rust/</guid>
      <description>&lt;p&gt;&amp;ldquo;so are you a go guy or a rust guy now?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;somebody asked me that recently and i realised i&amp;rsquo;d been dodging it for the better part of a year. the honest answer is annoying, which is why nobody likes it: i&amp;rsquo;m both. and it&amp;rsquo;s not a fence i&amp;rsquo;m sitting on, it&amp;rsquo;s a &lt;em&gt;split&lt;/em&gt; — i write go and i write rust, on purpose, for different jobs. the moment i stopped treating &amp;ldquo;which language&amp;rdquo; as a personality question and started treating it as a tooling question, the whole tired argument just dissolved.&lt;/p&gt;</description>
    </item>
    
  </channel>
</rss>
