RSS/Atom Feed Analyzer
Analysis of https://steveklabnik.com/feed.xml
Feed fetched in 140 ms.
Content type is application/xml.
Feed is 449,341 characters long.
Feed has an ETag of W/"441ec6928030c1e88166f9c4f820de53-ssl-df".
Warning Feed is missing the Last-Modified HTTP header.
Feed is well-formed XML.
Warning Feed has no styling.
This is an RSS feed.
Feed title: Steve Klabnik
Warning Feed is missing a self link.
Warning Feed is missing an image.
Feed has 20 items.
First item published on 2026-09-21T00:00:00.000Z
Last item published on 2024-10-16T00:00:00.000Z
All items have published dates.
Newest item was published on 2026-09-21T00:00:00.000Z.
Home page URL: https://steveklabnik.com/
Home page has feed discovery link in <head>.
Home page has a link to the feed in the <body>
Formatted XML
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Steve Klabnik</title>
<description>Steve Klabnik's blog</description>
<link>https://steveklabnik.com/</link>
<ttl>15</ttl>
<item>
<title>Arguing about arguments</title>
<link>https://steveklabnik.com/writing/arguing-about-arguments/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/arguing-about-arguments/</guid>
<description>Here’s some Rust code: // define a function fn foo (x : i32 , y : i32 ) -> i32 { // body elided } // call it let z = foo ( 5 , 6 ); In programming language jargon, we call x and y parameters and 5 and 6 arguments . Rust does not have many fancy features related to parameters and…</description>
<pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>Here’s some Rust code:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// define a function</span></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(x</span><span style="color:#F97583">:</span><span style="color:#B392F0"> i32</span><span style="color:#E1E4E8">, y</span><span style="color:#F97583">:</span><span style="color:#B392F0"> i32</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> i32</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// call it</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> z </span><span style="color:#F97583">=</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">6</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>In programming language jargon, we call <code>x</code> and <code>y</code> <em>parameters</em> and <code>5</code> and <code>6</code>
<em>arguments</em>.</p>
<p>Rust does not have many fancy features related to parameters and arguments. But
other languages do. For example, <a href="https://api.rubyonrails.org/classes/ActionController/Redirecting.html#method-i-redirect_to">here’s a function in Ruby</a>,
another language I’ve written a ton of:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#6A737D"># its definition in Rails</span></span>
<span class="line"><span style="color:#F97583">def</span><span style="color:#B392F0"> redirect_to</span><span style="color:#E1E4E8">(options </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {}, response_options </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {})</span></span>
<span class="line"><span style="color:#6A737D"> # body elided</span></span>
<span class="line"><span style="color:#F97583">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># you can call it in all of these ways:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># pass a string with a URL</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to </span><span style="color:#9ECBFF">"http://www.rubyonrails.org"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># pass an instance of a model to go to it, if post.id = 5 then this might go to</span></span>
<span class="line"><span style="color:#6A737D"># "/posts/5"</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to @post</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># invoke an action directly, might be equivalent to the above in a Posts controller</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to </span><span style="color:#79B8FF">action:</span><span style="color:#9ECBFF"> "show"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">id:</span><span style="color:#79B8FF"> 5</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># redirect to a model's URL, passing a flash message</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to </span><span style="color:#B392F0">post_url</span><span style="color:#E1E4E8">(@post), </span><span style="color:#79B8FF">alert:</span><span style="color:#9ECBFF"> "Watch it, mister!"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># do the same but also set a specific HTTP code</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to </span><span style="color:#B392F0">post_url</span><span style="color:#E1E4E8">(@post), </span><span style="color:#79B8FF">status:</span><span style="color:#79B8FF"> :found</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># do both</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to </span><span style="color:#B392F0">post_url</span><span style="color:#E1E4E8">(@post), </span><span style="color:#79B8FF">status:</span><span style="color:#79B8FF"> 301</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">flash:</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">updated_post_id:</span><span style="color:#E1E4E8"> @post.</span><span style="color:#B392F0">id</span><span style="color:#E1E4E8"> }</span></span></code></pre>
<p>I used to love Ruby, and now I love Rust. And to be honest, <code>redirect_to</code> and
friends are a huge reason why I am so against Rust gaining these sorts of fancy
features: they can be really concise, and beautiful to a certain kind of eye,
but it also makes it really, really hard to know what all you can do with a
function. You pretty much have to hope that someone has written good documentation,
and while Rails’s documentation is pretty good, not every library is going to have
it.</p>
<p>To be clear about it, there are several things bundled together here: flexible
argument types, options hashes, defaults, and keyword-looking syntax. It’s that
whole style of API that shaped my skepticism, not argument labels by themselves.</p>
<p>So, what exactly are all of these features?</p>
<h2 id="named-parameters">Named parameters</h2>
<p>The simplest one is <em>named parameters</em>. As you might guess, this means that we can
explicitly use parameter names at the call site:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// without named parameters</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> z </span><span style="color:#F97583">=</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">6</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// if Rust had them</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> z </span><span style="color:#F97583">=</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(x</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 5</span><span style="color:#E1E4E8">, y</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 6</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>Named parameters are nice because they can give you more information at the call
site, but also they can be more flexible. Some languages also let you supply
named arguments in a different order:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// since we have names, we can write this:</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> z </span><span style="color:#F97583">=</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(x</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 5</span><span style="color:#E1E4E8">, y</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 6</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// but we can also write this:</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> z </span><span style="color:#F97583">=</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(y</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 6</span><span style="color:#E1E4E8">, x</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 5</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>The downside of named parameters is that they can get quite verbose:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="python"><code><span class="line"><span style="color:#6A737D"># FastAPI in Python, UserOut class not shown</span></span>
<span class="line"><span style="color:#B392F0">@app.get</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#9ECBFF"> "/me"</span><span style="color:#E1E4E8">, </span></span>
<span class="line"><span style="color:#FFAB70"> response_model</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">UserOut, </span></span>
<span class="line"><span style="color:#FFAB70"> status_code</span><span style="color:#F97583">=</span><span style="color:#79B8FF">200</span><span style="color:#E1E4E8">, </span></span>
<span class="line"><span style="color:#FFAB70"> tags</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">[</span><span style="color:#9ECBFF">"Users"</span><span style="color:#E1E4E8">],</span></span>
<span class="line"><span style="color:#FFAB70"> summary</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"Get current user profile"</span></span>
<span class="line"><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583">def</span><span style="color:#B392F0"> get_current_user</span><span style="color:#E1E4E8">():</span></span></code></pre>
<p>I tried to not strawman this by showing an extreme example, but <code>get</code> can take
23 different keyword arguments, and so this could get quite, quite long.</p>
<p>Named arguments can be good when passing expressions, so we can understand
something more at the call site. In the above, <code>status_code=200</code> is much nicer
than a bare <code>200</code>, if you know HTTP well it’s kind of obvious what it is, but
the additional clarity is nice. But it can be common to break truly complex
examples down into variables that we end up forwarding to the function, and then
it becomes verbose <em>and</em> redundant. As an example from inside FastAPI where
<code>get</code> is invoked directly with all of those options:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="python"><code><span class="line"><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">.router.get(</span></span>
<span class="line"><span style="color:#E1E4E8"> path,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model,</span></span>
<span class="line"><span style="color:#FFAB70"> status_code</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">status_code,</span></span>
<span class="line"><span style="color:#FFAB70"> tags</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">tags,</span></span>
<span class="line"><span style="color:#FFAB70"> dependencies</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">dependencies,</span></span>
<span class="line"><span style="color:#FFAB70"> summary</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">summary,</span></span>
<span class="line"><span style="color:#FFAB70"> description</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">description,</span></span>
<span class="line"><span style="color:#FFAB70"> response_description</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_description,</span></span>
<span class="line"><span style="color:#FFAB70"> responses</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">responses,</span></span>
<span class="line"><span style="color:#FFAB70"> deprecated</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">deprecated,</span></span>
<span class="line"><span style="color:#FFAB70"> operation_id</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">operation_id,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_include</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_include,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_exclude</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_exclude,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_by_alias</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_by_alias,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_exclude_unset</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_exclude_unset,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_exclude_defaults</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_exclude_defaults,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_exclude_none</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_exclude_none,</span></span>
<span class="line"><span style="color:#FFAB70"> include_in_schema</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">include_in_schema,</span></span>
<span class="line"><span style="color:#FFAB70"> response_class</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_class,</span></span>
<span class="line"><span style="color:#FFAB70"> name</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">name,</span></span>
<span class="line"><span style="color:#FFAB70"> callbacks</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">callbacks,</span></span>
<span class="line"><span style="color:#FFAB70"> openapi_extra</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">openapi_extra,</span></span>
<span class="line"><span style="color:#FFAB70"> generate_unique_id_function</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">generate_unique_id_function,</span></span>
<span class="line"><span style="color:#E1E4E8">)</span></span></code></pre>
<p>We actually snuck in another two features into this example: how did we only
pass five arguments into a function that has 23 parameters?</p>
<h2 id="optional-andor-default-arguments">Optional and/or Default arguments</h2>
<p>As the name implies, optional arguments is a feature where you can choose to not pass in an argument,
and default arguments lets you set a default when an argument is not passed.</p>
<p>To go back to that example:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="python"><code><span class="line"><span style="color:#B392F0">@app.get</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#9ECBFF"> "/me"</span><span style="color:#E1E4E8">, </span></span>
<span class="line"><span style="color:#FFAB70"> response_model</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">UserOut, </span></span>
<span class="line"><span style="color:#FFAB70"> status_code</span><span style="color:#F97583">=</span><span style="color:#79B8FF">200</span><span style="color:#E1E4E8">, </span></span>
<span class="line"><span style="color:#FFAB70"> tags</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">[</span><span style="color:#9ECBFF">"Users"</span><span style="color:#E1E4E8">],</span></span>
<span class="line"><span style="color:#FFAB70"> summary</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"Get current user profile"</span></span>
<span class="line"><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583">def</span><span style="color:#B392F0"> get_current_user</span><span style="color:#E1E4E8">():</span></span></code></pre>
<p>We only passed in five of the 22 possible arguments. If we had to pass all 22 every time, that would
be extremely verbose. So by declaring defaults for some parameters, we can leave them off of the call
site, and not pass arguments for them, and get the defaults instead. We saw this in the Ruby:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#6A737D"># its definition in Rails</span></span>
<span class="line"><span style="color:#F97583">def</span><span style="color:#B392F0"> redirect_to</span><span style="color:#E1E4E8">(options </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {}, response_options </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {})</span></span></code></pre>
<p>The <code>= {}</code> are defaults for <code>options</code> and <code>response_options</code>: they’re an empty hash map.</p>
<p>You’ll often find optional and default arguments come together: after all, if you don’t pass
an argument in, what should the value of that parameter be? If your language has some sort of
null value, that can be one solution, and it can feel like optional arguments without default
arguments.</p>
<p>But you can truly get one without the other with our next feature, function overloading:</p>
<h2 id="function-overloading">Function Overloading</h2>
<p>Languages that support function overloading allow you to define multiple
functions with the same name, but different signatures. Which function gets
invoked depends on which arguments you pass. This lets us truly have optional
arguments without default arguments. In Java:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="java"><code><span class="line"><span style="color:#6A737D">// Version 1: Requires both arguments</span></span>
<span class="line"><span style="color:#F97583">void</span><span style="color:#B392F0"> connect</span><span style="color:#E1E4E8">(String url, </span><span style="color:#F97583">int</span><span style="color:#E1E4E8"> timeout) { ... }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// Version 2: Timeout is optional to the caller, and handled internally</span></span>
<span class="line"><span style="color:#F97583">void</span><span style="color:#B392F0"> connect</span><span style="color:#E1E4E8">(String url) { ... }</span></span></code></pre>
<p>Here, if you pass in a timeout, you get the first function, and if you don’t,
you get the second. Instead of null, the parameter just doesn’t exist at all.</p>
<p>This is a specific form of something called “function dispatch,” which basically
means “hey when a function is invoked, what <em>exactly</em> gets called?” There are a
<em>lot</em> of different ways to do this, and I don’t want this post to get into all
of that right now, but there is a multitude of possibilities here: static vs
dynamic, single vs multiple, predicate dispatch, pattern matching dispatch,
prototype based dispatch… maybe I’ll write a post about that someday too.</p>
<p>So, what are the pros and cons here?</p>
<h2 id="these-features-make-me-uneasy">These features make me uneasy</h2>
<p>As I said above, Rust doesn’t support any of these three features right now. And
they have been a persistent request from the community for years. <a href="https://github.com/rust-lang/rfcs/issues/323">Here</a> is
a 12 year old GitHub issue about this support, and it even includes something we
didn’t talk about, “variable arity argument lists.”</p>
<p>That gets me to the first thing that I don’t like about these features: there
are a <em>lot</em> of them. And they are all intertwined. As we mentioned before, if
you have optional arguments, you probably want default arguments. If you have
named parameters, do you want to support <em>only</em> named parameters, or do you want
to support both? It’s so tempting to just support all of them, and then you’ve
added a huge mountain of complexity.</p>
<p>In Rust right now, you have to write multiple functions with slightly different
names:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// a new empty vector</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// one with a set capacity</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">with_capacity</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>For the simple cost of “write two different names for your functions,” you get
to keep the rules very simple: there is one function definition, and you invoke
it by name. Done. Would it be nice to be able to say all of these:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">(capacity</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 5</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>Sure, maybe. But the cost feels very, very high to me. If you’re used to these
features, maybe it doesn’t seem that high, but I have a Ruby tattoo on my body.
I’ve seen some shit. And so I’ve grown to enjoy Rust’s simplicity in this area.
Yes, if you have a function with tons of parameters, you may need to write a
builder instead, and that comes with its own form of verbosity:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// if Vec had a builder</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">with_capacity</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">build</span><span style="color:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// all of the code you have to write to make the builder elided</span></span></code></pre>
<p>But the language itself stays simple, and the rules are easy. For a language
that’s already perceived as complex, this has always felt right to me. And that’s
why I’ve pushed back against the various proposals to extend Rust in this way all
of these years.</p>
<p>But recently, I changed my opinion on one of these features, and one alone, and
I would consider it okay if Rust gained them. Maybe. Also, it’s important to note
that I haven’t worked on Rust in years, and so my opinions are kind of irrelevant,
but whatever: it’s my blog, this is what opinions are for.</p>
<h2 id="im-okay-with-named-parameters-now">I’m okay with named parameters now</h2>
<p>I think Rust could be okay with named parameters, but <em>not</em> optional or default
ones. And what changed my opinion is coding agents, actually. I’m sorry, maybe
you’re sick of me talking about AI, but hear me out.</p>
<p>One of my beefs with named parameters, as mentioned above, is verbosity. But
what you gain for that verbosity is clarity. I don’t think that verbosity and
clarity are always the same thing; I like <code>{}</code> rather than <code>do</code>/<code>end</code>, and find
it more readable, personally. But let’s take this function from the <code>image</code> crate:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// definition</span></span>
<span class="line"><span style="color:#F97583">pub</span><span style="color:#F97583"> fn</span><span style="color:#B392F0"> crop_imm</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">I</span><span style="color:#F97583">:</span><span style="color:#B392F0"> GenericImageView</span><span style="color:#E1E4E8">>(</span></span>
<span class="line"><span style="color:#E1E4E8"> image</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">I</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> x</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> y</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> width</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> height</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> SubImage</span><span style="color:#E1E4E8"><</span><span style="color:#F97583">&</span><span style="color:#B392F0">I</span><span style="color:#E1E4E8">> { </span><span style="color:#F97583">...</span><span style="color:#E1E4E8"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// calling it</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> cropped </span><span style="color:#F97583">=</span><span style="color:#B392F0"> image</span><span style="color:#F97583">::</span><span style="color:#B392F0">imageops</span><span style="color:#F97583">::</span><span style="color:#B392F0">crop_imm</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">img, </span><span style="color:#79B8FF">10</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">20</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">200</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">100</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// if we had named arguments</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> cropped </span><span style="color:#F97583">=</span><span style="color:#B392F0"> image</span><span style="color:#F97583">::</span><span style="color:#B392F0">imageops</span><span style="color:#F97583">::</span><span style="color:#B392F0">crop_imm</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E1E4E8"> image</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#E1E4E8">img,</span></span>
<span class="line"><span style="color:#E1E4E8"> x</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 10</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> y</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 20</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> width</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 200</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> height</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 100</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">);</span></span></code></pre>
<p>This does really increase readability, no question about it. But for me, typing
all of that out just isn’t really super worth the squeeze. But when Claude is
gonna write it? I care a lot less. And the readability advantage for humans is
even stronger for agents: the extra clarity of the text inline seems to (I
haven’t run real evals on this yet…) be more helpful and use less tokens and
calls when looking at the actual call site. If an agent were reading that named
example above, it could tell that <code>10</code> is the <code>x</code> value without having to go
look up the function signature itself. “What’s good for humans is true for
agents” strikes again.</p>
<p>This is also why I’m still against optional arguments: they deliberately
<em>hide</em> what is being passed in to the function, and obscure clarity at the call
site. What’s nice about them is that you have to type less. But I’m not typing
myself anymore. So the benefit just isn’t there, but the drawbacks are. This
also doesn’t solve the verbosity side of passing <code>foo=foo</code> in our FastAPI
example, it is still redundant. But one part of the downsides have been
ameliorated.</p>
<p>Notably, all of this reasoning also applies to the builder pattern, and it’s why
I try to use builders sparingly in Rust. It can emulate named arguments, which
is good, but it can also emulate optional/default/variable arguments, which is
bad. The juice is worth the squeeze sometimes, but not all the time, and
probably not even the majority of the time.</p>
<p>So we’ve ameliorated the largest downside, but get to keep the largest upside.
That seems good. But even if we like the feature, there are still lots of
practical concerns with Rust specifically that make it not a slam-dunk to add
them to the language.</p>
<h2 id="theres-tons-of-practical-issues-though">There’s tons of practical issues though</h2>
<p>Now, “is this feature good in the abstract” is very different from “should this
feature” be implemented? There are a <em>lot</em> of open questions and drawbacks to
implementing named parameters. A simple one is that you don’t get rid of all of
those previous functions, <code>with_capacity</code> will exist forever. That’s a
relatively minor downside to some of the harder problems.</p>
<p>First, strictly speaking Rust parameters aren’t names, they’re patterns. A
name is a simple pattern, but you can get more complex:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">((x, y)</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> (</span><span style="color:#B392F0">i32</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">i32</span><span style="color:#E1E4E8">)) {</span></span></code></pre>
<p>This parameter doesn’t have a name, it’s a pattern that introduces two bindings.
You could come up with some syntax that gives things external names vs internal
names, but there’s that complexity revealing its head again.</p>
<p>What happens when functions become values? I can write this today:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> resize</span><span style="color:#E1E4E8">(width</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">, height</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> offset</span><span style="color:#E1E4E8">(dx</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">, dy</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> f</span><span style="color:#F97583">:</span><span style="color:#B392F0"> fn</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">u32</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">u32</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=</span><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> resizing { resize } </span><span style="color:#F97583">else</span><span style="color:#E1E4E8"> { offset };</span></span></code></pre>
<p>What name do we use for <code>f</code>’s parameters? They can be named different things.
Does this become an error? What names can you use when invoking <code>f</code>? Making this
more complicated, Rust actually will accept this code right now:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">type</span><span style="color:#B392F0"> Callback</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> fn</span><span style="color:#E1E4E8">(width</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">, height</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> f</span><span style="color:#E1E4E8">(g</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Callback</span><span style="color:#E1E4E8">) {}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> bar</span><span style="color:#E1E4E8">(x</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">, y</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">) {}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0"> f</span><span style="color:#E1E4E8">(bar) ;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>The names in <code>Callback</code> are just documentation, as you can see they’re not
required. Does this become an error? Do we require <code>bar</code> to change its names?
The same goes for trait definitions, actually:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">trait</span><span style="color:#B392F0"> Writer</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> write</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">, data</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#E1E4E8">[</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">]);</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Sink</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Writer</span><span style="color:#F97583"> for</span><span style="color:#B392F0"> Sink</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> write</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">, bytes</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#E1E4E8">[</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">]) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Is this an error? Is it okay?</p>
<p>Here’s another problem: evaluation order.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> consume</span><span style="color:#E1E4E8">(data</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">>, length</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Right now, Rust always evaluates parameters from left to right at the call site.
So if we use the named version:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> consume</span><span style="color:#E1E4E8">(data</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">>, length</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> consume2</span><span style="color:#E1E4E8">(length</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">, data</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">>) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> data </span><span style="color:#F97583">=</span><span style="color:#B392F0"> vec!</span><span style="color:#E1E4E8">[</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">2</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">3</span><span style="color:#E1E4E8">];</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> // this is an error, borrow after move</span></span>
<span class="line"><span style="color:#B392F0"> consume</span><span style="color:#E1E4E8">(data, data</span><span style="color:#F97583">.</span><span style="color:#B392F0">len</span><span style="color:#E1E4E8">());</span></span>
<span class="line"><span style="color:#6A737D"> // this is okay</span></span>
<span class="line"><span style="color:#B392F0"> consume2</span><span style="color:#E1E4E8">(data</span><span style="color:#F97583">.</span><span style="color:#B392F0">len</span><span style="color:#E1E4E8">(), data);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> // does this compile or not?</span></span>
<span class="line"><span style="color:#B392F0"> consume</span><span style="color:#E1E4E8">(length</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> data</span><span style="color:#F97583">.</span><span style="color:#B392F0">len</span><span style="color:#E1E4E8">(), data</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> data);</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>If we keep left to right evaluation order according to the definition, but let
you use named values in any order, suddenly some orders at the call site compile
and some orders do not. Do we change evaluation order to do whatever compiles?
That feels very dangerous and confusing.</p>
<p>Rust does let you do this with struct fields:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Args</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> data</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">>,</span></span>
<span class="line"><span style="color:#E1E4E8"> length</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> data </span><span style="color:#F97583">=</span><span style="color:#B392F0"> vec!</span><span style="color:#E1E4E8">[</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">2</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">3</span><span style="color:#E1E4E8">];</span></span>
<span class="line"><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D"> // works</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> args </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Args</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> length</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> data</span><span style="color:#F97583">.</span><span style="color:#B392F0">len</span><span style="color:#E1E4E8">(),</span></span>
<span class="line"><span style="color:#E1E4E8"> data,</span></span>
<span class="line"><span style="color:#E1E4E8"> };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> // doesn't</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> args </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Args</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> data,</span></span>
<span class="line"><span style="color:#E1E4E8"> length</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> data</span><span style="color:#F97583">.</span><span style="color:#B392F0">len</span><span style="color:#E1E4E8">(),</span></span>
<span class="line"><span style="color:#E1E4E8"> };</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This is one reason why a lot of proposals for named arguments try to unify them
with structs. As I said before, not insurmountable, just things that need to be
sorted.</p>
<p>Another small thing is a compatibility hazard: if we start naming parameters,
renaming them breaks callers. Is that okay? Could you introduce aliases to help
with this? That’s more complexity…</p>
<p>These are just some of the problems that come up in the design space, and this
post is getting long, so I’ll leave it at this for now. But proposals for doing
this have to engage with all of this and more, which is why language design is
difficult.</p>
<h2 id="in-conclusion">In conclusion</h2>
<p>I like to think that I change my opinions over time as the world or my
understanding of it changes. So I thought it might be interesting to share a way
in which an opinion that I’ve held for over a decade has changed a bit lately.
I’m still not sure if named parameters are right for Rust, but I’m at least
somewhat open to the idea these days. We’ll see what the language team decides
if anyone does decide to keep trying to push this forward. And people do, for
example, I am writing this in part because I saw <a href="https://botahamec.dev/named-optional-args">this post</a> go by today.
This post isn’t intended to be support or a rebuttal of it, I actually didn’t
even read it yet, it’s just that I’ve been thinking about this a lot lately
(while working on <a href="https://rue-lang.dev/">Rue</a>) and seeing that go by pushed me to write some of
the thoughts down. You should probably go check it out, as it’s an actual
proposal in this space and this post is more of an exploration of the general
idea and my own thoughts on it.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mw2ahu3pv22q" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Arguing about arguments
<a target="_blank" href="https://steveklabnik.com/writing/arguing-about-arguments/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/argu...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/arguing-about-arguments/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreigpeqitvd6az3jx55koev3nbbllzxb6moswv6erkvptevqphh553e" alt="" class="thumbnail s-rtbqd8"/> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Arguing about arguments</p> <p class="description s-rtbqd8">Here’s some Rust code: // define a function fn foo (x : i32 , y : i32 ) -> i32 { // body elided } // call it let z = foo ( 5 , 6 ); In programming language jargon, we call x and y parameters and 5 and 6 arguments . Rust does not have many fancy features related to parameters and…</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-09-21T17:30:35.997Z" class="date s-hik11q">September 21, 2026 at 5:30 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="92 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>92</span></span> <span class="stat s-hik11q" title="11 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>11</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mw2ahu3pv22q" target="_blank" class="permalink s-hik11q"><span>Read 22 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>So, what happened to my Claude Code series?</title>
<link>https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/</guid>
<description>I started off this year writing a lot, and wanted to keep up the pace. I did good at first! And then… I didn’t manage to. In some ways, this is because I have just been super busy! I’ve never had less free time, and I’m okay with that. There’s a tremendous amount of good things…</description>
<pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>I started off this year writing a lot, and wanted to keep up the pace. I did
good at first! And then… I didn’t manage to.</p>
<p>In some ways, this is because I have just been super busy! I’ve never had less
free time, and I’m okay with that. There’s a tremendous amount of good things
going on in my life.</p>
<p>But it’s also put this whole Claude Code series in an awkward spot, because
things have been changing so rapidly, that it’s been hard to keep up. And I also
need to figure out what it is I actually want to say, and my practice has
changed tremendously since January!</p>
<p>I’m thinking about this a lot today in part because one of the things that happened
since we last talked about this stuff was the release of Claude Fable. And now,
a new Fable has just been put out into the world, and OpenAI’s Astra is coming very
soon. New models don’t always mean that practice inherently changes, but like, it’s
just feeling like this is getting further and further behind, and so I think it’s
probably good for me to just officially close this series. I still really like what
I wrote, and think it’s still valuable today for getting started, but I just don’t
think I can commit to keeping up with documenting everything I do at the pace that
stuff is happening at. So being honest with myself about that is very useful.</p>
<p>To those that found this series helpful, I’m glad, and I’m sorry I’m not writing
more about it. I’ll still write things about this topic in general, but the core
idea of a “how to get started doing this stuff” series just isn’t in the cards
for me right now.</p>
<p>AI is here to stay, and it is dramatically changing how people work. I believe
that more strongly than ever. So stopping isn’t about me somehow getting bearish
on this stuff. It’s just that I’m too darn busy for this specific project.</p>
<p>This is also why the “notes” section on my website still has zero entries. I was
hoping that something to put stuff shorter form than a blog post would make it
easier to write, but in the end, I can’t even find much time for that.</p>
<p>Okay well I’m having trouble wrapping this up so, I’ll just leave it here for
now.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mukmbg4ugs2e" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">So, what happened to my Claude Code series? <a target="_blank" href="https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/so-w...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">So, what happened to my Claude Code series?</p> <p class="description s-rtbqd8">I started off this year writing a lot, and wanted to keep up the pace. I did good at first! And then… I didn’t manage to. In some ways, this is because I have just been super busy! I’ve never had less free time, and I’m okay with that. There’s a tremendous amount of good things…</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-09-02T18:53:57.487Z" class="date s-hik11q">September 2, 2026 at 6:53 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="53 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>53</span></span> <span class="stat s-hik11q" title="1 repost"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>1</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mukmbg4ugs2e" target="_blank" class="permalink s-hik11q"><span>Read 8 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Too many words about DIDs</title>
<link>https://steveklabnik.com/writing/too-many-words-about-dids/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/too-many-words-about-dids/</guid>
<description>Your “Bluesky account” is not just a Bluesky account: it is an account that can be used with a variety of other applications. This post is going to be an exploration of part of what that means from a technical perspective, so if you’re not a software developer, this post isn’t…</description>
<pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>Your “Bluesky account” is not <em>just</em> a Bluesky account: it is an account that
can be used with a variety of other applications. This post is going to be an
exploration of part of what that means from a technical perspective, so if you’re
not a software developer, this post isn’t for you. But what I’m going to explain
is the technical mechanism for how your account works separate from Bluesky, and
in fact, separate from any particular app.</p>
<p>Let’s talk about identity: who are you, anyway? Users of a system need some sort
of way to describe who they are to use it. If you want to log in, you need to
present who you are. If you want to make a post, well, we need to know who the
author of that post is. For atproto, the protocol that underlies Bluesky and
other apps in the ATmosphere, they use the “Decentralized Identity” standard,
also known as DID. The <a href="https://www.w3.org/TR/did-1.0/">W3C standardized DIDs in
2022</a>. As you might guess from the name, DIDs
are, an identifier that you can use as the basis of identity for building
applications. And the idea is that these identifiers are decentralized. However,
a lot of people have a lot of feelings about that specific word, and often
accuse atproto of not being properly decentralized. We’re going to go over the
details so you can understand how this works, and you can decide for yourself if
this approach suits you or not.</p>
<h2 id="dids-and-did-documents">DIDs and DID Documents</h2>
<p>Here is my DID, we’ll use this as an example: <code>did:plc:3danwc67lo7obz2fmdg6jxcr</code></p>
<p>There are three parts, separated by colons: The scheme (<code>did</code>), the method (<code>plc</code>),
and the DID method-specific identifier (<code>3danwc67lo7obz2fmdg6jxcr</code>).</p>
<p>To use a DID, such as <code>did:plc:3danwc67lo7obz2fmdg6jxcr</code>, you resolve it into a
<a href="https://www.w3.org/TR/did-1.0/#dfn-did-documents">DID Document</a></p>
<blockquote>
<p>A set of data describing the DID subject, including mechanisms, such as
cryptographic public keys, that the DID subject or a DID delegate can use to
authenticate itself and prove its association with the DID.</p>
</blockquote>
<p>That document contains <a href="https://www.w3.org/TR/did-1.0/#core-properties">various
properties</a> that describe the
identity. Here’s my DID Document, at the time of writing:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>
<span class="line"><span style="color:#79B8FF"> "@context"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://www.w3.org/ns/did/v1"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://w3id.org/security/multikey/v1"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://w3id.org/security/suites/secp256k1-2019/v1"</span></span>
<span class="line"><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:plc:3danwc67lo7obz2fmdg6jxcr"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "alsoKnownAs"</span><span style="color:#E1E4E8">: [</span><span style="color:#9ECBFF">"at://steveklabnik.com"</span><span style="color:#E1E4E8">],</span></span>
<span class="line"><span style="color:#79B8FF"> "verificationMethod"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:plc:3danwc67lo7obz2fmdg6jxcr#atproto"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Multikey"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "controller"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:plc:3danwc67lo7obz2fmdg6jxcr"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "publicKeyMultibase"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"zQ3shfNec2kPEb8cL77gSRMbCwbWE27p9nxKkcc4E82xtW8RJ"</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#79B8FF"> "service"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"#atproto_pds"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"AtprotoPersonalDataServer"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "serviceEndpoint"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"https://morel.us-east.host.bsky.network"</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8"> ]</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This document gives you everything you need to know to determine who I am, that is,
given an arbitrary post that claims it’s written by me, this document describes how
you’d verify that claim.</p>
<p>We’ll get into how to do that in a moment, but first, how do you resolve
that DID into that DID document? Well, it’s pretty easy: each method is a
standard that describes how you do that. So when you see <code>did:plc</code>, that means
we use the PLC standard, which we’ll be going over in a moment. Another method
supported by Bluesky is <code>did:web</code>. In that case, you wouldn’t use the PLC
standard, you’d use the Web one.</p>
<p>This is the sense in which DIDs are decentralized: when you present your
identity, you get to decide what method validates that that is a real identity.
There’s no centralized authority that determines which DID types are valid. Now,
of course, that doesn’t mean that every application supports every DID method,
because while this specification is <em>very</em> generic, you’re still going to have
to write some code to implement that particular method. I could say “Hey I’m
<code>did:foo:1243</code>” and unless your app supports the <code>foo</code> method, it’s not gonna
inherently just know what to do. So that is one important caveat.</p>
<h2 id="didweb"><code>did:web</code></h2>
<p>Let’s explain this resolution process for the <code>web</code> method. While supported by
Bluesky, a very small number of users actually use <code>did:web</code>, but it’s a simpler
method and so I think it’s illustrative to go over first. I’ll be using <a href="https://bsky.app/profile/web.lizthegrey.com">Liz
Fong-Jones</a>’ <code>did:web</code> account as an
example here. Her identity for that account is <code>did:web:lizthegrey.com</code>.</p>
<p>So how do we resolve this DID into a DID Document? We take the method-specific
identifier, which in this case is <code>lizthegrey.com</code>, and put it into this URL template:</p>
<p><code>https://<id>/.well-known/did.json</code></p>
<p>You can then go fetch this URL to resolve it into the DID Document, which at the
time of writing, looks like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>
<span class="line"><span style="color:#79B8FF"> "@context"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://www.w3.org/ns/did/v1"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://w3id.org/security/multikey/v1"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://w3id.org/security/suites/secp256k1-2019/v1"</span></span>
<span class="line"><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:web:lizthegrey.com"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "alsoKnownAs"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#9ECBFF"> "at://web.lizthegrey.com"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#9ECBFF"> "did:plc:i4tfenpfog244rxry5uz4vtk"</span></span>
<span class="line"><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#79B8FF"> "verificationMethod"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:web:lizthegrey.com#atproto"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Multikey"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "controller"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:web:lizthegrey.com"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "publicKeyMultibase"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"zQ3shnEqBNR5cuTePW8FyvnrRQaFf6Y7sCi5NBmDpteVXFjb6"</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#79B8FF"> "service"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"#atproto_pds"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"AtprotoPersonalDataServer"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "serviceEndpoint"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"https://pds.lizthegrey.com"</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8"> ]</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This is very simple! So why might we not want to use <code>did:web</code>? Why bother with
any other system? Well, this relies on the DNS system. One could make the
argument that ultimately, this is still centralized in some form. If Liz’s
domain registrar were to take away her domain, she would also lose control of
this DID. In a more generic sense, if Liz decides she wants to not use that
domain anymore, she will lose control of that identity to whoever does. That
could be through non-malicious means, like letting it expire and someone else
purchases it, or through malicious ones, like a hack which would compromise her
registrar account and take the domain over.</p>
<p>Also, you need to have a web server running on that domain with infinite uptime;
if the server goes down, so does your ability to get the document.</p>
<p>When this DID document changes, there’s no mechanism for clients to know that
it’s changed, which means applications may use one that’s out of date, or that
there is lag between updating the document and updating the application built on
it, which may cause temporary problems until the latest document is fetched.</p>
<h2 id="didplc"><code>did:plc</code></h2>
<p>All of these drawbacks led Bluesky to develop their own DID method, which
attempts to fix these problems and others. This method is called
<a href="https://web.plc.directory/spec/v0.1/did-plc"><code>did:plc</code></a>. To resolve a <code>did:plc</code>,
you take the entire DID, and put it in this template:</p>
<p><code>https://plc.directory/<did></code></p>
<p>You can then fetch that URL and get the DID document.</p>
<p>So… what’s the difference? Well, in this case, both nothing and something. In
a very literal sense, both are resolved in the same way: you fetch a URL.
However, the details matter. There is already two ways in which this is different
than DID:Web:</p>
<ol>
<li>Your DID is no longer tied to a specific domain name. I can let
<code>steveklabnik.com</code> expire and move to <code>steve.klabnik.com</code> and my <code>did:plc</code>
stays the same.</li>
<li>While a web server still needs to be running, that’s the job of plc.directory, not
my own job. This is operationally much simpler.</li>
</ol>
<p>I’ve presented the above as pros, but there are also cons. Before, I had to
trust the DNS system and domain registrars, now I have to trust plc.directory.
All of the same caveats apply in that sense, I have to trust that they don’t
take my ID away from me, or that it doesn’t get stolen, etc. However, there are
also some important details that mitigate this, which we’ll get to. But for some
people, neither trusting DNS nor trusting plc.directory is acceptable, and there
are other DID methods that use, for example, a blockchain to resolve the name.
Bluesky does not support using any of those DID methods, so for this
application, it’s not really relevant, but it’s important to know that they
exist.</p>
<p>Why do it this way? Well, the simplest way to put it is this: setting up a
<code>did:web</code> involves a lot of “nerd stuff.” You have to register a domain, and
that’s also an ongoing monetary cost. You have to know how to set up a web
server, and author some JSON to put on that server. You have to keep it running.
You have to know how to store your private keys, and keep them safe. It’s a
non-starter compared to “sign up for this web app.” And Bluesky’s goals involves
making this platform accessible to non-nerds. By having plc.directory manage
all of this, we eliminate all of those steps.</p>
<p>While drafting this post, I have also been made aware of
<a href="https://identity.foundation/didwebvh/v1.0/"><code>did:webvh</code></a>, which expands on <code>did:web</code> and attempts to rectify
some of its shortcomings. I have not read the spec yet, but it has reached 1.0,
so it is probably worth checking out. I wanted to get this post shipped last
week, and didn’t want to delay it further by adding another section, but if
I were writing this post in the future, I’d probably want to talk about it as
well, so just a little heads-up there.</p>
<h2 id="does-bluesky-own-your-identity">Does Bluesky own your identity?</h2>
<p>But it does also mean that, in some sense, Bluesky still owns your identity.
They’ve generated a keypair for you, and they have access to the secret key.
That’s unacceptable for some people. So how do you fix that?</p>
<p>Well, <code>did:plc</code> has some additional features that <code>did:web</code> does not. For
example, <code>did:plc</code> will allow you to register additional keypairs with your ID
and use them to rotate your signing keys. This allows you to remove the Bluesky
generated keys and insert your own.</p>
<p>While that is true, it’s also the case that your PDS needs to use your keys to
sign your posts. As such, most people are likely to store their keys in their PDS,
and so if you are using a Bluesky managed PDS, well, you’ve uploaded your keys to
their infrastructure, and that’s probably not acceptable if you’re trying to keep
your identity away from Bluesky. Of course, the solution there is to run your own
PDS and then rotate your keys. At that point, your key is living on infrastructure
you own, and Bluesky has no say over it any more.</p>
<p>I think that this possibility is an important design property, and allows
motivated users to meaningfully own their identity. A criticism of this boils
down to “well, most users won’t do that,” and while that’s true, I also think
that’s okay for most people, and that having the choice is more important than
forcing every user to deal with their own key management.</p>
<h2 id="in-summary">In summary</h2>
<p>This is kind of an abrupt end to this post, but I just wanted to get some things
down ‘on paper’ as it were. I hope you’ve learned a bit about identity and how
it works with atproto.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mqmjx73a2s2w" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Too many words about DIDs: <a target="_blank" href="https://steveklabnik.com/writing/too-many-words-about-dids/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/too-...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/too-many-words-about-dids/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Too many words about DIDs</p> <p class="description s-rtbqd8">Blog post: Too many words about DIDs by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-07-14T15:37:00.032Z" class="date s-hik11q">July 14, 2026 at 3:37 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="132 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>132</span></span> <span class="stat s-hik11q" title="29 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>29</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mqmjx73a2s2w" target="_blank" class="permalink s-hik11q"><span>Read 12 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Compilers and AI 'cyber' defense</title>
<link>https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/</guid>
<description>I haven’t had time to blog in a while, I have a lot to say, but have been doing a lot of things and so haven’t gotten words down in a while. One of those things in the past few weeks has been picking back up my Rue project. I’m really excited for what it’s turning into, even…</description>
<pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>I haven’t had time to blog in a while, I have a lot to say, but have been doing
a lot of things and so haven’t gotten words down in a while.</p>
<p>One of those things in the past few weeks has been picking back up my <a href="https://rue-lang.dev/">Rue</a>
project. I’m really excited for what it’s turning into, even though it’s not
really ready for anyone else to use just yet. So go take a look if you want, but
don’t expect anything amazing, it’s very much a work in progress.</p>
<p>Anyway, one of the things that got me back into working on it is the releases of
Fable and Sol. I hadn’t really touched Rue since January, and a lot of things have
changed since then. It’s fun to have new models take a look at older projects and
see what their new perspectives are.</p>
<p>Unfortunately, due to the intelligence of these new models, the US government
has asked for and imposed restrictions. These are largely around things like
cybersecurity. Since I don’t do cybersecurity work, I didn’t think they would
affect me.</p>
<p>(Aside: I put ‘cyber’ in quotes in the title because I hate that apparently
that’s the term folks are using now. Sigh.)</p>
<p>Well:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>ⓘ This content can't be shown</span></span>
<span class="line"><span> We take extra caution with cybersecurity requests. If you're a security professional, you may be able to apply for</span></span>
<span class="line"><span> Trusted Access.</span></span>
<span class="line"><span> Trusted Access: https://openai.com/form/enterprise-trusted-access-for-cyber/</span></span>
<span class="line"><span> Learn more: https://help.openai.com/en/articles/20001326</span></span>
<span class="line"><span></span></span>
<span class="line"><span></span></span>
<span class="line"><span>› please continue</span></span>
<span class="line"><span></span></span>
<span class="line"><span></span></span>
<span class="line"><span>ⓘ This content can't be shown</span></span>
<span class="line"><span> We take extra caution with cybersecurity requests. If you're a security professional, you may be able to apply for</span></span>
<span class="line"><span> Trusted Access.</span></span>
<span class="line"><span> Trusted Access: https://openai.com/form/enterprise-trusted-access-for-cyber/</span></span>
<span class="line"><span> Learn more: https://help.openai.com/en/articles/20001326</span></span></code></pre>
<p>This is the output from Sol, but the same thing happens with Fable too. I asked Sol
about it, and this is what it says:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>• It is a false positive from the safety layer, not a judgment that you asked for anything malicious. Rue contains</span></span>
<span class="line"><span> fuzzers, crash reproducers, sanitizer hooks, child processes, deliberate out-of-bounds writes, and compiler-panic</span></span>
<span class="line"><span> tests. Those patterns look similar to exploit-development activity when a classifier sees commands or snippets without</span></span>
<span class="line"><span> fully trusting the repository-ownership context.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> RUE-560 is an especially likely trigger because the correct regression test deliberately performs a one-byte overflow</span></span>
<span class="line"><span> in an ASan-instrumented child and requires that child to crash. That is defensive testing, but the surface pattern is</span></span>
<span class="line"><span> still “construct an overflow and observe the failure.”</span></span>
<span class="line"><span></span></span>
<span class="line"><span> I cannot disable or bypass that layer. I can reduce interruptions by keeping work tightly scoped to this repository,</span></span>
<span class="line"><span> putting negative behavior inside the existing test harness, describing the defensive purpose explicitly, avoiding</span></span>
<span class="line"><span> reusable attack tooling, and checkpointing progress before risky-looking verification. The persistent goal means that</span></span>
<span class="line"><span> if a turn is interrupted, the next continuation can inspect the workspace and resume without needing you. Nothing I’ve</span></span>
<span class="line"><span> found suggests the project or your request is dangerous.</span></span></code></pre>
<p>It is really unfortunate that, in order to make sure my compiler isn’t going to
produce incorrect codegen, this trips the safety guards. I guess that simply
saying “hey this is a toy language nobody uses and so it’s fine I’m not hacking
anyone” and that not allowing me around the guards is a “good” thing, but at the
same time, it is unfortunate if these policies end up making it so that individuals
like me can’t make my software as robust as products from large organizations.</p>
<p>I don’t know how to end this post really, just wanted to put that out there. I’m
still thinking about the implications, and I hope you do as well.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mqckcblr5c2k" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Compilers and AI 'cyber' defense
<a target="_blank" href="https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/comp...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Compilers and AI 'cyber' defense</p> <p class="description s-rtbqd8">Blog post: Compilers and AI 'cyber' defense by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-07-10T16:16:34.386Z" class="date s-hik11q">July 10, 2026 at 4:16 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="27 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>27</span></span> <span class="stat s-hik11q" title="7 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>7</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mqckcblr5c2k" target="_blank" class="permalink s-hik11q"><span>Read 5 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>The most important thing when working with LLMs</title>
<link>https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/</guid>
<description>Okay, so you’ve got the basics of working with Claude going. But you’ve probably run into some problems: Claude doesn’t do what you want it to do, it gets confused about what’s happening and goes off the rails, all sorts of things can go wrong. Let’s talk about how to improve…</description>
<pubDate>Tue, 27 Jan 2026 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>Okay, so you’ve got the basics of working with Claude going. But you’ve probably
run into some problems: Claude doesn’t do what you want it to do, it gets
confused about what’s happening and goes off the rails, all sorts of things can
go wrong. Let’s talk about how to improve upon that.</p>
<p>The most important thing that you can do when working with an LLM is give it a
way to quickly evaluate if it’s doing the right thing, and if it isn’t, point it
in the right direction.</p>
<p>This is incredibly simple, yet, like many simple things, also wildly complex.
But if you can keep this idea in mind, you’ll be well equipped to become
effective when working with agents.</p>
<h2 id="two-lessons-from-teaching-children">Two lessons from teaching children</h2>
<p>A long time ago, I used to teach programming classes. Many of these were to
adults, but some of them were to children. Teenaged children, but children
nonetheless. We used to do an exercise to try and help them understand the
difference between talking in English and talking in Ruby, or JavaScript, or
whatever kind of programming language rather than human language. The exercise
went like this:</p>
<p>I would have a jar of peanut butter, a jar of jelly, a loaf of bread, a spoon,
and a knife. I would ask the class to take a piece of paper and write down a
series of steps to make a peanut butter and jelly sandwich. They’d all then give
me their algorithms, and the fun part for me began: find one that’s innocently
written that I could hilariously misinterpret. For example, I might find one
like:</p>
<ol>
<li>Put the peanut butter on the bread</li>
<li>Put the jelly on the bread</li>
<li>Put the bread together</li>
</ol>
<p>I’d read this aloud to the class, you all understand this is a recipe for a
peanut butter and jelly sandwich, right? I’d take the jar of peanut butter and
place it upon the unopened bag of bread. I’d do the same with the jar of jelly.
This would of course, squish the bread, which feels slightly transgressive given
that you’re messing up the bread, so the kids would love that. I’d then say
something like “the bread is already together, I do not understand this
instruction.”</p>
<p>After the inevitable laughter died down, I’d make my point: the computer will do
<em>exactly</em> what you say, but not what you mean. So you have to get good at
figuring out when you said something different than what you mean. Sort of
ironically, LLMs are kind of the inverse of this: they’ll sometimes try to
figure out what you mean, and then do that, rather than simply doing what you
say. But the core thing here is the same: semantic drift from what we intended
our program to do, and what it actually does.</p>
<p>The second lesson is something I came up with sometime, I don’t even remember
how exactly. But it’s something I told my students a lot. And that’s this:</p>
<p>If your program did everything you wanted without problems, you wouldn’t be
programming: you’d be using your program. The act of programming is itself
perpetually to be in a state where something is either inadequate or broken,
and the job is to fix that.</p>
<p>I also think this is a bit simplistic but also getting at something. I had
originally come up with this in the context of trying to explain how you
need to manage your frustration when programming; if you get easily upset by
something not working, doing computer programming might not be for you. But I do
think these two things combine into something that gets to the heart of what we
do: we need to understand what it is we want our software to do, and then make
it do that. Sometimes, our software doesn’t do something yet. Sometimes, it does
something, but incorrectly. Both of these cases result in a divergence from the
program’s intended behavior.</p>
<p>So, how do we know if our program does what it should do?</p>
<h2 id="gotta-go-fast">Gotta go fast</h2>
<p>Well, what we’ve been doing so far is:</p>
<ol>
<li>Asking the LLM to do something by typing up what we want it to do</li>
<li>Closely observing its behavior and course correcting it when it goes off of
the rails</li>
<li>Eventually, after it says that it’s finished, reviewing its output</li>
</ol>
<p>This is our little mini software development lifecycle, or “SDLC.” This process
works, but is slow. That’s great for getting the feel of things, but programmers
are process optimizers by trade. One of my favorite tools for optimization is
called <a href="https://www3.cs.stonybrook.edu/~rezaul/Spring-2012/CSE613/reading/Amdahl-1967.pdf">Amdahl’s law</a>. The core idea is this, formulated in my own
words:</p>
<blockquote>
<p>If you have a process that takes multiple steps, and you want to speed it up,
if you optimize only one step, the maximum amount of speedup you’ll get is
determined by the portion of the process that step takes.</p>
</blockquote>
<p>In other words, imagine we have a three step process:</p>
<ol>
<li>One minute</li>
<li>Ten minutes</li>
<li>Two minutes</li>
</ol>
<p>This process takes a total of 13 minutes to complete. If we speed up step 3 by
double, it goes from two minutes to one minute, and now our process takes 12
minutes. However, if we were able to speed up step 2 by double, we’d cut off
five minutes, and our process would now take 8 minutes.</p>
<p>We can use this style of analysis to guide our thinking in many ways, but the
most common way, for me, is to decide where to put my effort. Given the process
above, I’m going to look at step 2 first to try and figure out how to make it
faster. That doesn’t mean we can achieve the 2x speedup, but heck, if we get a
10% decrease in time, that’s the same time as if we did get a 2x on step 3. So
it’s at least the place where we should start.</p>
<p>I chose the above because, well, I think it properly models the proportion of
time we’re taking when doing things with LLMs: we spend some time asking it to
do something, and we spend a bit more time reviewing its output. But we spend a
<em>lot</em> of time clicking “accept edit,” and a lot of time allowing Claude to
execute tools. This will be our next step forward, as this will increase our
velocity when working with the tools significantly. However, like with many
optimization tasks, this is easier said than done.</p>
<p>The actual mechanics of improving the speed of this step are simple at first:
hit <code>shift-tab</code> to auto-accept edits, and “Yes, and don’t ask again for <code><cmd></code>
commands” when you think the <code><cmd></code> is safe for Claude to run. By doing this,
once you have enough commands allowed, your input for step 2 of our development
loop can drop to zero. Of course, it takes time for Claude to actually implement
what you’ve asked, so it’s not like our 13 minute process drops to three, but
still, this is a major efficiency step.</p>
<p>But we were actively monitoring Claude for a reason. Claude will sometimes do
incorrect things, and we need to correct it. At some point, Claude will say
“Hey I’ve finished doing what you asked of me!” and it doesn’t matter how fast
it does step 2 if we get to step 3 and it’s just incorrect, and we need to
throw everything out and try again.</p>
<p>So, how do we get Claude to guide itself in the right direction?</p>
<h2 id="lets-start-at-the-end">Let’s start at the end</h2>
<p>A useful technique for figuring out what you should do is to consider the
ending: where do we want to go? That will inform what we need to do to get
there. Well, the ending of step 2 is knowing when to transition to step 3. And
that transition is gated by “does the software do what it is supposed to do?”
That’s a huge question! But in practice, we can do what we always do: start
simple, and iterate from there. Right now, the transition from step 2 to step 3
is left up to Claude. Claude will use its own judgement to decide when it thinks
that the software is working. And it’ll be right. But why leave that up to
chance?</p>
<p>I expect that some of you are thinking that maybe I’m belaboring this point.
“Why not just skip to <code>cargo test</code>? That’s the idea, right? We need tests.” Well
on some level: yes. But on another level, no. I’m trying to teach you how to
think here, not give you the answer. Because it might be broader than just “run
the tests.” Maybe you are working on a project where the tests aren’t very good
yet. Maybe you’re working on a behavior that’s hard to automatically test.
Maybe the test suite takes a very long time, and so isn’t appropriate to be
running over and over and over.</p>
<p>Remember our plan from the last post? Where Claude finished the plan with this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span> Verification</span></span>
<span class="line"><span></span></span>
<span class="line"><span> 1. Run cargo build to ensure it compiles</span></span>
<span class="line"><span> 2. Run `cargo run -- version` to verify it prints "task 0.1.0"</span></span>
<span class="line"><span> 3. Run `cargo run -- --help` to verify help output works</span></span></code></pre>
<p>These aren’t “tests” in the traditional sense of a test suite, but they <em>are</em>
objective measures that Claude can invoke itself to understand if it’s finished
the task. Claude could run <code>cargo run -- version</code> after every file edit if it
wanted to, and as soon as it sees <code>task 0.1.0</code>, it knows that it’s finished.
You don’t <em>need</em> a comprehensive test suite. You just need <em>some</em> sort of way
for Claude to detect if it’s done in some sort of objective fashion.</p>
<p>Of course, we can do better.</p>
<h2 id="improving-from-there">Improving from there</h2>
<p>While giving Claude a way to know if it’s done working is important, there’s a
second thing we need to pay attention to: when Claude isn’t done working, can we
guide it towards doing the right thing, rather than the wrong thing?</p>
<p>For example, those of you who are of a similar vintage as myself may remember the
output of early compilers. It was often… not very helpful. Imagine that we told
Claude that it should run <code>make test</code> to know if things are working, and the only
output from it was the exit code: 0 if we succeeded, 1 if we failed. That would
accomplish our objective of letting Claude know when things are done, but it
wouldn’t help Claude know what went wrong when it returns 1.</p>
<p>This is one reason why I think Rust works well with LLMs. Take this incorrect Rust
program:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> y</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>The Rust compiler won’t just say “yeah this program is incorrect,” it’ll give you
this (as of Rust 1.93.0):</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>error: expected `;`, found `}`</span></span>
<span class="line"><span> --> src/main.rs:2:10</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>2 | let y</span></span>
<span class="line"><span> | ^ help: add `;` here</span></span>
<span class="line"><span>3 | }</span></span>
<span class="line"><span> | - unexpected token</span></span>
<span class="line"><span></span></span>
<span class="line"><span>error[E0282]: type annotations needed</span></span>
<span class="line"><span> --> src/main.rs:2:9</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>2 | let y</span></span>
<span class="line"><span> | ^</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>help: consider giving `y` an explicit type</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>2 | let y: /* Type */</span></span>
<span class="line"><span> | ++++++++++++</span></span>
<span class="line"><span></span></span>
<span class="line"><span>For more information about this error, try `rustc --explain E0282`.</span></span></code></pre>
<p>The compiler will point out the exact place in the code itself of where there’s
an issue, and even make suggestions as to how to fix it. This goes beyond just
simply saying “it doesn’t work” and instead nudges you to what might fix the
problem. Of course, this isn’t perfect, but if it’s helpful more than not,
that’s a win.</p>
<p>Of course, too much verbosity isn’t helpful either. A lot of tooling has
gotten much more verbose lately. Often times, this is really nice as a human.
Pleasant terminal output is, well… pleasant. But that doesn’t mean that it’s
always good or useful. For example, here’s the default output for <code>cargo test</code>:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>running 3 tests</span></span>
<span class="line"><span>test bar ... ok</span></span>
<span class="line"><span>test baz ... ok</span></span>
<span class="line"><span>test foo ... ok</span></span>
<span class="line"><span></span></span>
<span class="line"><span>test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span></span></code></pre>
<p>This is not bad output. It’s nice. But it’s also not useful for an LLM. We don’t
need to read all of the tests that are passing, we really just want to see some
sort of minimal output, and then what failed if something failed. In Cargo’s case,
that’s <code>cargo test -q</code> for “quiet”:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>running 3 tests</span></span>
<span class="line"><span>...</span></span>
<span class="line"><span>test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span></span></code></pre>
<p>There is no point in giving a ton of verbose input to an LLM that it isn’t even
going to need to use. If you’re feeding a tools’ output to an LLM, you should
consider both what the tool does in the failure case, but also the success case.
Maybe configure things to be a bit simpler for Claude. You’ll save some tokens
and get better results.</p>
<p>All of this has various implications for all sorts of things. For example, types
are a great way to get quick feedback on what you’re doing. A comprehensive test
suite that completes quickly is useful for giving feedback to the LLM. But that
also doesn’t inherently mean that types must be better or that you need to be doing
TDD; whatever gives you that underlying principle of “objective feedback for the
success case and guidance for the failure case” will be golden, no matter what
tech stack you use.</p>
<h2 id="whats-good-for-the-goose-human-is-good-for-the-gander-llm">What’s good for the <del>goose</del> human is good for the <del>gander</del> LLM</h2>
<p>This brings me to something that may be counter-intuitive, but I think is also
true, and worth keeping in the back of your mind: what’s good for Claude is also
probably good for humans working on your system. A good test suite was
considered golden before LLMs. That it’s great for them is just a nice
coincidence.</p>
<p>At the end of the day, Claude is not a person, but it tackles programming
problems in a similar fashion to how we do: take in the problem, attempt a
solution, run the compiler/linter/tests, and then see what feedback it gets,
then iterate. That core loop is the same, even if humans can exercise better
judgement and can have more skill. And so even though I pitched fancy terminal
output as an example of how humans and LLMs need different things, that’s really
just a superficial kind of thing. Good error messages are still critical for
both. We’re just better at having terminal spinners not take up space in our
heads while we’re solving a problem, and can appreciate the aesthetics in a way
that Claude does not.</p>
<p>Incidentally, this is one of the things that makes me hopeful about the future
of software development under agentic influence. Engineers always complain that
management doesn’t give us time to do refactorings, to improve the test suite,
to clean our code. Part of the reason for this is that we often didn’t do a good
job of pitching how it would actually help accomplish business goals. But even
if you’re on the fence about AI, and upset that management is all about AI:
explain to management that this stuff is a force multiplier for your agents.
Use the time you’ve saved by doing things the agentic way towards improving
your test suite, or your documentation, or whatever else. I think there’s a chance
that all of this stuff leads to higher quality codebases than ones filled with
slop. But it also requires us to make the decisions that will lead is in that
direction.</p>
<h2 id="in-conclusion">In conclusion</h2>
<p>That’s what I have for you today: consider how you can help Claude evaluate its
own work. Give it explicit success criteria, and make evaluating that criteria
as simple and objective as possible.</p>
<p>In the next post, we’re gonna finally talk about <code>Claude.md</code>. Can you believe
that I’ve talked this much about how to use Claude and we haven’t talked about
<code>Claude.md</code>? There’s good reason for that, as it turns out. We’re going to talk
a bit more about understanding how interacting with LLMs work, and how it can
help us both improve step 1 in our process, but also continue to make step 2
better and better.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mdg6nftkm22k" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Steve Klabnik</span></bdi> <span class="handle s-12mzi62">@steveklabnik.com</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcznckciyk2e" title="January 22, 2026 at 4:48 PM" class="date s-12mzi62"><time datetime="2026-01-22T16:48:55.938Z">Jan 22</time></a></div> <p class="context s-12mzi62">Replying to <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" dir="auto" class="s-12mzi62">Steve Klabnik</a></p> <p class="rich-text is-small s-1lecfnd">Agentic development basics: <a target="_blank" href="https://steveklabnik.com/writing/agentic-development-basics/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/agen...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/agentic-development-basics/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Agentic development basics</p> <p class="description s-rtbqd8">Blog post: Agentic development basics by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div></div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> </div> <p class="rich-text is-large s-1lecfnd">The most important thing when working with LLMs
<a target="_blank" href="https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/the-...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">The most important thing when working with LLMs</p> <p class="description s-rtbqd8">Blog post: The most important thing when working with LLMs by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-01-27T16:31:10.824Z" class="date s-hik11q">January 27, 2026 at 4:31 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="35 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>35</span></span> <span class="stat s-hik11q" title="4 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>4</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mdg6nftkm22k" target="_blank" class="permalink s-hik11q"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Agentic development basics</title>
<link>https://steveklabnik.com/writing/agentic-development-basics/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/agentic-development-basics/</guid>
<description>In my last post, I suggested that you should start using Claude in your software development process via read-only means at first. The idea is just to get used to interacting with the AI, seeing what it can do, and seeing what it struggles with. Once you’ve got a handle on that…</description>
<pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>In my last post, I suggested that you should start using Claude in your software
development process via read-only means at first. The idea is just to get used
to interacting with the AI, seeing what it can do, and seeing what it struggles
with.</p>
<p>Once you’ve got a handle on that part, it’s time to graduate to writing code.
However, I’m going to warn you about this post: I hope that by the end of it,
you’re a little frustrated. This is because I don’t think it’s productive to
skip to the tools and techniques that experienced users use yet. We have to
walk before we run. And more importantly, we have to understand how and why we
run. That is, I hope that this step will let you start producing code with
Claude, but it will also show you some of the initial pitfalls when doing so, in
order to motivate the techniques you’re going to learn about in part 3.</p>
<p>So with that in mind, let’s begin.</p>
<h2 id="step-0-version-control">Step 0: version control</h2>
<p>Okay I lied. Before we actually begin: you are using version control, right? If
not, you may want to go learn a bit about it. Version control, like git (or my
beloved jj) is pretty critical for software development, but it’s in my opinion
even <em>more</em> critical for this sort of development. You really want to be able to
restore to previous versions of the code, branch off and try things, and recover
from mistakes. If you already use version control systems religiously, you might
use this as an excuse to learn even more features of them. I never bothered with
<code>git worktree</code>s in the past, but I use <code>jj workspace</code>s with agents all the time
now.</p>
<h2 id="step-1-mindset-shift-stop-writing-code">Step 1: Mindset shift: stop writing code</h2>
<p>Okay, here’s my first bit of advice: commit yourself to not writing code any
more. I don’t mean forever, I don’t mean all the time, I mean, while you’re
trying to learn agentic software development, on the project that you’re
learning it with, just don’t write any code manually.</p>
<p>This might be a bit controversial! However, I think it is essential. Let me tell
you a short story. Many years ago, I was in Brazil. I wanted to try scuba diving
for the first time. Seemed like a good opportunity. Now, I don’t remember the
exact setup, but I do remember the hardest part for me. Our instructor
told us to put the mask on, and then lean forward and put our faces in the water
and breathe through the regulator. I simply could not do it. I got too in my
head, it was like those “you are now breathing manually” memes. I forget if it
was my idea or the instructor’s idea, but what happened in practice: I just
jumped in. My brain <em>very quickly</em> went from “but how do I do this properly” to
“oh God, if you don’t figure this out right the fuck now you’re gonna fuckin die
idiot” and that’s exactly what I needed to do it. A few seconds later, I was
breathing just fine. I just needed the shock to my system, I needed to commit.
And I figured it out.</p>
<p>Now, I’m turning 40 and this happened long ago, and it’s reached more of a
personal myth status in my head, so maybe I got the details wrong, maybe this is
horribly irresponsible and someone who knows about diving can tell me if that
experience was wrong, but the point has always stuck with me: sometimes, you
just gotta go for it.</p>
<p>This dovetails into another part I snuck into there: “on the project that you’re
learning it with.” I really think that you should undergo a new project for this
endeavor. There’s a few reasons for this:</p>
<ol>
<li>You can be less precious about the code. This isn’t messing up one of your
projects, this is a throwaway scratch thing that doesn’t matter.</li>
<li>“AI does better on greenfield projects” is not exactly true, but there’s
enough truth to it that I think you should do a new project. It’s really more
about secondary factors than it is actual greenfield vs brownfield
development but whatever, doesn’t matter: start new.</li>
</ol>
<p>Pick something to get started with, and create a new repo. I suggest something
you’ve implemented before, or maybe something you know how to do but have never
bothered to take the time to actually build. A small CLI tool might be a good
idea. Doesn’t super matter what it is.</p>
<p>For the purposes of this example, I’m going to build a task tracking CLI
application. Because there aren’t enough of those in the world.</p>
<h2 id="step-2-your-first-few-steps-with-claude">Step 2: Your first few steps with Claude</h2>
<p>I recommend making a new fresh directory, and initializing the project of your
choice. I’m using Rust, of course, so I’ll <code>cargo new tasks && cd tasks</code>. You
can make Claude do it, but I don’t think that starting from an initialized
project is a bad idea either. I’m more likely to go the <code>cargo new</code> route if I
know I’m building something small, and more of the “make Claude do it” route if
I’m doing like, a web app with a frontend, backend, and several services. Anyway
point is: get your project to exist, and then just run Claude Code. I guess you
should <a href="https://code.claude.com/docs/en/quickstart">install it first</a>, but anyway, you can just</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>$ claude</span></span></code></pre>
<p>to get started. At the time of writing, Claude will ask you if you trust the
files in this folder, you want to say yes, because you just created it. You’ll
also get some screens asking if you want to do light or dark mode, to log in
with your Anthropic account, stuff like that.</p>
<p>And then you’ll be in.</p>
<p>Claude will ask you to run <code>/init</code> to create a CLAUDE.md, but we’re not gonna do
that at the start. We need to talk about even more basic things than that first.</p>
<p>You’ll be at a prompt, and it’ll even suggest something for you to get started
with. Mine says “Try “fix typecheck errors"" at the moment. We’re gonna try to get
Claude to modify a few lines of our program. The <code>cargo new</code> in Rust produces
this program:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"Hello, world!"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>so I’ll ask Claude this:</p>
<blockquote>
<p>Hi claude! right now my program prints “Hello, world”, can we have it print
“Goodbye, world” instead?</p>
</blockquote>
<p>Claude does this for me:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● I'll make that change for you.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Read(src/main.rs)</span></span>
<span class="line"><span> ⎿ Read 4 lines</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Update(src/main.rs)</span></span></code></pre>
<p>And then asks this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span> Edit file src/main.rs</span></span>
<span class="line"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>
<span class="line"><span> 1 fn main() {</span></span>
<span class="line"><span> 2 - println!("Hello, world!");</span></span>
<span class="line"><span> 2 + println!("Goodbye, world!");</span></span>
<span class="line"><span> 3 }</span></span>
<span class="line"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>
<span class="line"><span> Do you want to make this edit to main.rs?</span></span>
<span class="line"><span> ❯ 1. Yes</span></span>
<span class="line"><span> 2. Yes, allow all edits during this session (shift+tab)</span></span>
<span class="line"><span> 3. No</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Esc to cancel · Tab to add additional instructions</span></span></code></pre>
<p>Claude wants to edit a file, and so by default, it has to ask us permission to
do so. This is a terrible place to end up, but a great place to get started! We
want to use these prompts at first to understand what Claude is doing, and sort
of “code review” it as we go. More on that in a bit. Anyway, this is why you
should answer “Yes” to this question, and not “Yes, allow all edits during this
session.” We want to keep reviewing the code for now. You want to be paying
close attention to what Claude is doing, so you can build up some intuition
about it.</p>
<p>Before clicking yes, I want to talk about what Claude did in my case here. Note
that my prompt said</p>
<blockquote>
<p>right now my program prints “Hello, world”, can we have it print “Goodbye,
world”</p>
</blockquote>
<p>Astute observers will have noticed that it actually says <code>Hello, world!</code> and not
<code>Hello, world</code>. We also asked it to have it say <code>Goodbye, world</code> and it is
showing a diff that will make it say <code>Goodbye, world!</code>. This is a tiny
different, but it is also important to understand: Claude is going to try and
figure out what you mean and do that. This is both the source of these tools’
power and also the very thing that makes it hard to trust them. In this case,
Claude was right, I didn’t type the exact string when describing the current
behavior, and I didn’t mean to remove the <code>!</code>.</p>
<p>In the previous post, I said that you shouldn’t be mean to Claude. I think it
makes the LLM perform worse. So now it’s time to talk about your own reaction to
the above: did you go “yeah Claude fucked up it didn’t do exactly what I asked?”
or did you go “yeah Claude did exactly what I asked?” I think it’s important to
try and let go of preconceived notions here, especially if your reaction here
was negative. I know this is kind of woo, just like “be nice to Claude,” but you
have to approach this as “this is a technology that works a little differently
than I’m used to, and that’s why I’m learning how to meet it on its own terms”
rather than “it didn’t work the way I expected it to, so it is wrong.” A non-woo
way of putting it is this: the right way to approach “it didn’t work” in this
context is “that’s a skill issue on my part, and I’m here to sharpen my skills.”
Yes, there are limits to this technology and it’s not perfect. That’s not the
point. You’re not doing that kind of work right now, you’re doing <code>sed</code>.</p>
<p>Now, I should also say that like, if you don’t want to learn a new tool? 100%
okay with me. Learned some things about a tool, and didn’t like it? Sure! Some
of you won’t like agentic development. That’s okay. No worries, thanks for
reading, have a nice day. I mean that honestly. But for those folks who do want
to learn this, I’m trying to communicate that I think you’ll have a better time
learning it if you try to get into the headspace of “how do I get the results I
want” rather than getting upset and giving up when it doesn’t work out.</p>
<p>Okay, with that out of the way, if you asked a small enough question, Claude
probably did the right thing. Let’s accept.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Done. Your program will now print "Goodbye, world!" instead.</span></span></code></pre>
<h2 id="planning">Planning</h2>
<p>This might be a good time to commit & save your progress. You can use <code>!</code> to put
Claude Code into “bash mode” and run commands, so I just <code>!jj commit -m "goodbye world"</code> and I’m good. You can also use another terminal, I just figured I’d let
you know. It’s good for short commands.</p>
<p>Let’s try something bigger. To do that, we’re gonna invoke something called
“plan mode.” Claude Code has three (shhhh, we don’t talk about the fourth yet)
modes. The first one is the “ask to accept edits” mode. But if you hit
<code>shift+tab</code>, you’ll see <code>⏵⏵ accept edits on (shift+tab to cycle)</code> at the bottom
left. We don’t want to automatically accept edits. Hit <code>shift+tab</code> again, and
you’ll see this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>⏸ plan mode on (shift+tab to cycle)</span></span></code></pre>
<p>This is what we want. Plan mode. Plan mode is useful any time you’re doing work
that’s on the larger side, or just when you want to think through something
before you begin. In plan mode, Claude cannot modify your files until you accept
the plan. With plan mode, you talk to Claude about what you want to do, and you
collaborate on a plan together. A nice thing about it is that you can
communicate the things you are sure of, and also ask about the things you’re not
sure of. So let’s kick off some sort of plan to build the most baby parts of our
app. In my case, I’m prompting it with this:</p>
<blockquote>
<p>hi claude! i want this application to grow into a task tracking app. right
now, it’s just hello world. I’d like to set up some command line argument
parsing infrastructure, with a <code>task version</code> command that prints the version.
can we talk about that?</p>
</blockquote>
<p>Yes, I almost always type <code>hi claude!</code>, feel free to not. And I always feel like
a “can we talk about that” on the end is nice too. I try to talk to Claude the
way I’d talk to a co-worker. Obviously this would be too minor of a thing to
bother to talk to a co-worker about, but like, you know, baby steps. Note that
what I’m asking is basically a slightly more complex “hello world”, just getting
some argument parsing up. You want something this sized: you know how it should
be done, it should be pretty simple, but it’s not a fancy <code>sed</code> command.</p>
<p>With plan mode, Claude will end up responding to this by taking a look at your
project, considering what you might need, and then coming up with a plan. Here’s
the first part of Claude’s answer to me:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Sure! This is a great foundation to build on. Since the codebase is minimal right now (just the hello world in src/main.rs), I</span></span>
<span class="line"><span> can skip extensive exploration and go straight to planning.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> For CLI argument parsing in Rust, the standard choice is clap - it's the most popular library with excellent subcommand</span></span>
<span class="line"><span> support, derive macros for clean code, and built-in version/help handling. This will scale well as you add more commands like</span></span>
<span class="line"><span> task add, task list, etc.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Let me write up a plan for you.</span></span></code></pre>
<p>It’ll then come up with a plan, and it usually write it out to a file somewhere:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Updated plan</span></span>
<span class="line"><span> ⎿ Wrote 56 lines to ../../.claude/plans/shiny-dancing-cray.md</span></span>
<span class="line"><span> # Plan: CLI Argument Parsing Infrastructure</span></span>
<span class="line"><span> ## Goal</span></span>
<span class="line"><span> Set up command line argument parsing for the task tracking app with a `task version` subcommand.</span></span>
<span class="line"><span> ## Approach</span></span>
<span class="line"><span> Use **clap** with derive macros - the standard Rust CLI library.</span></span>
<span class="line"><span> ## Changes</span></span>
<span class="line"><span> … +46 lines (ctrl+o to expand)</span></span></code></pre>
<p>You can go read the file if you want to, but it’s not needed. Claude is going to
eventually present the plan to you directly, and you’ll review it before moving on.
Claude will also probably ask you a question or maybe even a couple, depending.
There’s a neat little TUI for responding to its questions, it can even handle
multiple questions at once:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● I've drafted a plan using clap with derive macros. Before finalizing, I have one question:</span></span>
<span class="line"><span></span></span>
<span class="line"><span>────────────────────────────────────────────────────────────────────────────────────────────</span></span>
<span class="line"><span> ☐ CLI Library</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Which CLI parsing library would you prefer?</span></span>
<span class="line"><span></span></span>
<span class="line"><span>❯ 1. clap (Recommended)</span></span>
<span class="line"><span> Most popular, feature-rich, excellent subcommand support, built-in help/version</span></span>
<span class="line"><span> 2. argh</span></span>
<span class="line"><span> Simpler and lighter, derive-based, from Google</span></span>
<span class="line"><span> 3. Manual parsing</span></span>
<span class="line"><span> Use std::env::args directly, no dependencies, more boilerplate</span></span>
<span class="line"><span> 4. Type something.</span></span>
<span class="line"><span>────────────────────────────────────────────────────────────────────────────────────────────</span></span>
<span class="line"><span> Chat about this</span></span>
<span class="line"><span> Skip interview and plan immediately</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Enter to select · ↑/↓ to navigate · Esc to cancel</span></span></code></pre>
<p>For those of you that don’t write Rust, this is a pretty good response! Clap is
the default choice in the ecosystem, arg is a decent option too, and doing it
yourself is always possible. I’m going to choose clap, it’s great. If you’re not
sure about the question, you can arrow down to “Chat about this” and discuss it
more.</p>
<p>Here’s why you don’t need to read the file: Claude will pitch you on its plan:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● User answered Claude's questions:</span></span>
<span class="line"><span> ⎿ · Which CLI parsing library would you prefer? → clap (Recommended)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Clap it is. The plan is ready - it adds clap with derive macros and sets up the subcommand structure with task version as the</span></span>
<span class="line"><span> first command. This gives you a solid foundation to add more commands later.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span></span>
<span class="line"><span> Ready to code?</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Here is Claude's plan:</span></span>
<span class="line"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>
<span class="line"><span> Plan: CLI Argument Parsing Infrastructure</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Goal</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Set up command line argument parsing for the task tracking app with a task version subcommand.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Approach</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Use clap with derive macros - the standard Rust CLI library.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Changes</span></span>
<span class="line"><span></span></span>
<span class="line"><span> 1. Update Cargo.toml</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Add clap dependency with derive feature:</span></span>
<span class="line"><span> [dependencies]</span></span>
<span class="line"><span> clap = { version = "4", features = ["derive"] }</span></span>
<span class="line"><span></span></span>
<span class="line"><span> 2. Update src/main.rs</span></span>
<span class="line"><span></span></span>
<span class="line"><span> - Define a Cli struct with clap derive macros</span></span>
<span class="line"><span> - Define a Commands enum for subcommands (starting with Version)</span></span>
<span class="line"><span> - Implement the version subcommand to print the version from Cargo.toml</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Structure:</span></span>
<span class="line"><span> use clap::{Parser, Subcommand};</span></span>
<span class="line"><span></span></span>
<span class="line"><span> #[derive(Parser)]</span></span>
<span class="line"><span> #[command(name = "task")]</span></span>
<span class="line"><span> #[command(about = "A task tracking application")]</span></span>
<span class="line"><span> struct Cli {</span></span>
<span class="line"><span> #[command(subcommand)]</span></span>
<span class="line"><span> command: Commands,</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span></span></span>
<span class="line"><span> #[derive(Subcommand)]</span></span>
<span class="line"><span> enum Commands {</span></span>
<span class="line"><span> /// Print version information</span></span>
<span class="line"><span> Version,</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span></span></span>
<span class="line"><span> fn main() {</span></span>
<span class="line"><span> let cli = Cli::parse();</span></span>
<span class="line"><span></span></span>
<span class="line"><span> match cli.command {</span></span>
<span class="line"><span> Commands::Version => {</span></span>
<span class="line"><span> println!("task {}", env!("CARGO_PKG_VERSION"));</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Verification</span></span>
<span class="line"><span></span></span>
<span class="line"><span> 1. Run cargo build to ensure it compiles</span></span>
<span class="line"><span> 2. Run ./target/debug/tasks version to verify it prints "task 0.1.0"</span></span>
<span class="line"><span> 3. Run ./target/debug/tasks --help to verify help output works</span></span>
<span class="line"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Would you like to proceed?</span></span>
<span class="line"><span></span></span>
<span class="line"><span> ❯ 1. Yes, clear context and auto-accept edits (shift+tab)</span></span>
<span class="line"><span> 2. Yes, auto-accept edits</span></span>
<span class="line"><span> 3. Yes, manually approve edits</span></span>
<span class="line"><span> 4. Type here to tell Claude what to change</span></span>
<span class="line"><span></span></span>
<span class="line"><span> ctrl-g to edit in VS Code · ~/.claude/plans/shiny-dancing-cray.md</span></span></code></pre>
<p>This is pretty good! Now, if you like this plan, you can select #3. Remember,
we’re not auto accepting just yet! Don’t worry about the difference between 1
and 2 now, we’ll talk about it someday. But, I actually want Claude to tweak
this plan, I wouldn’t run <code>./target/debug/tasks version</code>, I would do <code>cargo run -- version</code>. So I’m going to go down to four and type literally that to Claude:</p>
<blockquote>
<p>I wouldn’t run <code>./target/debug/tasks version</code>, I would do <code>cargo run -- version</code>.</p>
</blockquote>
<p>And Claude replies:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Good point - let me update the verification section.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Updated plan</span></span>
<span class="line"><span> ⎿ /plan to preview</span></span>
<span class="line"><span> 51</span></span>
<span class="line"><span> 52 ## Verification</span></span>
<span class="line"><span> 53 1. Run `cargo build` to ensure it compiles</span></span>
<span class="line"><span> 54 -2. Run `./target/debug/tasks version` to verify it prints "task 0.1.0"</span></span>
<span class="line"><span> 55 -3. Run `./target/debug/tasks --help` to verify help output works</span></span>
<span class="line"><span> 54 +2. Run `cargo run -- version` to verify it prints "task 0.1.0"</span></span>
<span class="line"><span> 55 +3. Run `cargo run -- --help` to verify help output works</span></span></code></pre>
<p>and then presents the menu again. See, this is where the leverage of “Claude
figures out what I mean” can be helpful: I only told it about <code>version</code>, but it
also changed to <code>cargo run -- --help</code> as well. However, there’s a drawback too:
we didn’t tell Claude we wanted to have help output! However, this is also a
positive: Claude considered help output to be so basic that it’s suggesting it
for our plan. It’s up to you to decide if this is an overreach on Claude’s part.
In my case, I’m okay with it because it’s so nearly automatic with Clap and it’s
something I certainly want in my tool, so I’m going to accept this plan.</p>
<p>Iterate with Claude until you’re happy with the plan, and then do the same.</p>
<p>I’m not going to paste all of the diffs here, but for me, Claude then went and
did the plan: it added the dependency to <code>Cargo.toml</code>, it added the needed code
to <code>src/main.rs</code>, it ran <code>cargo build</code> to try and do the build. Oh yeah, here’s
a menu we haven’t seen yet:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span> Bash command</span></span>
<span class="line"><span></span></span>
<span class="line"><span> cargo build 2>&1</span></span>
<span class="line"><span> Build the Rust project</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Do you want to proceed?</span></span>
<span class="line"><span> ❯ 1. Yes</span></span>
<span class="line"><span> 2. Yes, and don't ask again for cargo build commands in /home/stat/tmp/tasks</span></span>
<span class="line"><span> 3. No</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Esc to cancel · Tab to add additional instructions</span></span></code></pre>
<p>This is a more interesting question than the auto-edit thing. Claude won’t run
commands without you signing off on them. If you’re okay with letting Claude run
this command every time without asking, you can choose 2, and if you want to
confirm every time, you can type 1. Completely up to you.</p>
<p>Now it ran <code>cargo run -- version</code>, and <code>cargo run -- --help</code>. Everything looked
good, so I see:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Done. The CLI infrastructure is set up:</span></span>
<span class="line"><span></span></span>
<span class="line"><span> - cargo run -- version outputs task 0.1.0</span></span>
<span class="line"><span> - cargo run -- --help shows the help with available commands</span></span>
<span class="line"><span></span></span>
<span class="line"><span> You now have a foundation to add more subcommands to the Commands enum as you build out the task tracking features.</span></span></code></pre>
<p>And that’s it, we’ve built our first feature! Yeah, it’s pretty small, and in
this case, we probably could have copy/pasted the documentation, but again,
that’s not the point right now: we’re just trying to take very small steps
forward to get used to working with the tool. We <em>want</em> it to be something
that’s quick for us to verify. We are spending more time here than we would if
we did it by hand, because that time isn’t wasted: it’s time learning. As we
ramp up the complexity of what we can accomplish, we’ll start seeing speed
gains. But we’re deliberately going slow and doing little right now.</p>
<h2 id="stretch-your-legs">Stretch your legs</h2>
<p>From here, that’s exactly what I’d like you to do: figure out where the limits
are. Try something slightly larger, slightly harder. Try to do stuff with just a
prompt, and then throw that commit away and try it again with plan mode. See
what stuff you need plan mode for, and what you can get away with with just a
simple prompt.</p>
<p>I’ll leave you with an example of a prompt I might try next: asking Cladue for
their opinion on what you should do. The next thing I’d do in this project is to
switch back into planning mode and ask this:</p>
<blockquote>
<p>what do you think is the best architecture for this tracking app? we haven’t
discussed any real features, design, or requirements. this is mostly a little
sample application to practice with, and so we might not need a real database,
we might get away with a simple file or files to track todos. but maybe
something like sqlite would still be appropriate. if we wanted to implement a
next step for this app, what do you think it should be?</p>
</blockquote>
<p>Here’s what Claude suggested:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Great question - this is the kind of design discussion that's valuable before diving into implementation. Let me share my</span></span>
<span class="line"><span> thoughts.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Storage: File vs SQLite</span></span>
<span class="line"><span></span></span>
<span class="line"><span> For a practice app, I'd recommend starting with a simple JSON file:</span></span>
<span class="line"><span></span></span>
<span class="line"><span> - No additional dependencies - just serde + serde_json</span></span>
<span class="line"><span> - Human-readable - you can inspect/edit ~/.tasks.json directly</span></span>
<span class="line"><span> - Focus on app logic - learn clap, error handling, and Rust patterns without database complexity</span></span>
<span class="line"><span> - Easy to migrate later - if you outgrow it, moving to SQLite is a good exercise in itself</span></span>
<span class="line"><span></span></span>
<span class="line"><span> SQLite is great but adds cognitive load (SQL, migrations, connection handling). For a practice app, that complexity might</span></span>
<span class="line"><span> distract from the core learning.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Recommended Next Step</span></span>
<span class="line"><span></span></span>
<span class="line"><span> The natural progression is the core CRUD operations. I'd suggest implementing task add next:</span></span>
<span class="line"><span></span></span>
<span class="line"><span> task add "Buy groceries"</span></span>
<span class="line"><span></span></span>
<span class="line"><span> This introduces:</span></span>
<span class="line"><span> - Taking arguments in subcommands (clap)</span></span>
<span class="line"><span> - Reading/writing a data file (serde)</span></span>
<span class="line"><span> - Defining your Task struct (what fields? id, description, created_at, completed?)</span></span>
<span class="line"><span> - Error handling patterns</span></span>
<span class="line"><span></span></span>
<span class="line"><span> After that: task list, then task done <id>, then task remove <id>.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span></span>
<span class="line"><span> ☐ Next step</span></span>
<span class="line"><span></span></span>
<span class="line"><span>What would you like to tackle next?</span></span>
<span class="line"><span></span></span>
<span class="line"><span>❯ 1. task add (Recommended)</span></span>
<span class="line"><span> Add a task with a description, stored in a JSON file</span></span>
<span class="line"><span> 2. task list first</span></span>
<span class="line"><span> Set up storage and list (empty at first), then add</span></span>
<span class="line"><span> 3. Use SQLite instead</span></span>
<span class="line"><span> Start with SQLite from the beginning for the learning experience</span></span>
<span class="line"><span> 4. Type something.</span></span>
<span class="line"><span>──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span></span>
<span class="line"><span> Chat about this</span></span>
<span class="line"><span> Skip interview and plan immediately</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Enter to select · ↑/↓ to navigate · Esc to cancel</span></span></code></pre>
<p>This plan is pretty solid. But again, it’s a demo app. The important part is,
you can always throw this away if you don’t like it. So try some things. Give
Claude some opinions and see how they react. Try small features, try something
larger. Play around. But push it until Claude fails.</p>
<h2 id="what-to-do-when-claude-fails">What to do when Claude fails</h2>
<p>At some point, you will run into problems. Maybe you already have! What you
should do depends on the failure mode.</p>
<p>The first failure you’re gonna run into is “I don’t like the code!” Maybe Claude
just does a bad job. You have two options: the first is to just tell Claude to
fix it. Claude made a mess, Claude can clean it up. In more extreme cases, you
may want to just simply <code>jj abandon</code> or <code>git reset --hard</code> and start again.
Honestly, the second approach is better in a lot of cases, but I’m not going to
always recommend it just yet. The reason why it is is that it gives you some
time to reflect on why Claude did a bad job. But we’re gonna talk about that as
the next post in this series! So for now, stick to the ‘worse’ option: just tell
Claude to fix problems you find.</p>
<p>The second kind of failure is one where Claude just really struggles to get
things right. It looks in the wrong places in your codebase, it tries to find a
bug and can’t figure it out, it misreads output and that leads it astray, etc.
This kind of failure is harder to fix with the tools you have available to you
right now. What matters is taking note of them, so that you can email them to
me, haha. I mean, feel free to do that, and I can incorporate specific
suggestions into the next posts, but also, just being able to reflect on what
Claude struggles with is a good idea generally. You’ll be able to fix them
eventually, so knowing what you need to improve matters.</p>
<h2 id="what-is-compaction">What is “compaction”?</h2>
<p>If Claude works long enough, you’ll see something about “compaction.” We haven’t
discussed things at a deep enough level to really understand this yet, so don’t
worry about it! You may want to note one thing though: Claude tends to do worse
after compaction, in my opinion. So one way to think about this is, “If I see
compaction, I’ve tried to accomplish too large a task.” Reflect on if you could
have broken this up into something smaller.</p>
<p>We’ll talk about this more in the next post.</p>
<h2 id="conclusion">Conclusion</h2>
<p>So that’s it! Let Claude write some code, in a project you don’t care about. Try
bigger and harder things until you’re a bit frustrated with its failures. You
will hit the limits, because you’re not doing any of the intermediate techniques
to help Claude do a good job. But my hope is, by running into these issues,
you’ll understand the motivation for those techniques, and will be able to
better apply them in the future.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mcznckciyk2e" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Steve Klabnik</span></bdi> <span class="handle s-12mzi62">@steveklabnik.com</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mbu3gbqrwk2y" title="January 7, 2026 at 6:20 PM" class="date s-12mzi62"><time datetime="2026-01-07T18:20:23.515Z">Jan 7</time></a></div> <p class="rich-text is-small s-1lecfnd">Getting started with Claude for software development: <a target="_blank" href="https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/gett...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Getting started with Claude for software development</p> <p class="description s-rtbqd8">Blog post: Getting started with Claude for software development by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div></div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> </div> <p class="rich-text is-large s-1lecfnd">Agentic development basics: <a target="_blank" href="https://steveklabnik.com/writing/agentic-development-basics/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/agen...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/agentic-development-basics/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Agentic development basics</p> <p class="description s-rtbqd8">Blog post: Agentic development basics by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-01-22T16:48:55.938Z" class="date s-hik11q">January 22, 2026 at 4:48 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="38 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>38</span></span> <span class="stat s-hik11q" title="1 repost"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>1</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcznckciyk2e" target="_blank" class="permalink s-hik11q"><span>Read 6 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>How to think about Gas Town</title>
<link>https://steveklabnik.com/writing/how-to-think-about-gas-town/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/how-to-think-about-gas-town/</guid>
<description>I want to continue with my series on how you can use Claude Code for software development , but I have at least two posts I need to write first. This post is one of those two. Thanks for all of the kind words you all have said about the first post in the series, I’ll absolutely…</description>
<pubDate>Thu, 15 Jan 2026 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>I want to continue with my <a href="/series/getting-started-with-claude-code">series on how you can use Claude Code for software
development</a>, but I have at least two posts I need to write
first. This post is one of those two. Thanks for all of the kind words you all
have said about the first post in the series, I’ll absolutely be continuing it,
hopefully next week.</p>
<p>With that out of the way… let’s talk about Gas Town. If you’re not familiar,
it’s a project from Steve Yegge that has a lot of people having a lot of
feelings. Steve launched Gas Town just under two weeks ago, and in general, his
posts about it have brought out a reaction that has varied from “what the
fuck?!?” to “I don’t get it.” I’ll link Bryan here, not to pick on him, but sort
of the opposite: I think this is a very normal response to all of this, and
especially from him:</p>
<bluesky-post src="at://did:plc:ucct3fwwarzd7rety6nzq4cc/app.bsky.feed.post/3mcft3omsa226" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:ucct3fwwarzd7rety6nzq4cc/bafkreibajqtfkfkb7tkctsl42q747tekfc4yuek5dhjbe7plc24c7b3rti" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Bryan Cantrill</span></bdi> <span class="handle s-hik11q">@bcantrill.bsky.social</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">WTAF
<a target="_blank" href="https://steve-yegge.medium.com/gas-town-emergency-user-manual-cf0e4556d74b" rel="noopener nofollow" class="link s-1lecfnd">steve-yegge.medium.com/gas-town-eme...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steve-yegge.medium.com/gas-town-emergency-user-manual-cf0e4556d74b" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:ucct3fwwarzd7rety6nzq4cc/bafkreick2ibnbvizxsiafszp6qkaf3gegryrxxfhu2qweaa2ddghl74zwa" alt="" class="thumbnail s-rtbqd8"/> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Gas Town Emergency User Manual</p> <p class="description s-rtbqd8">It’s been a busy 12 days since I launched Gas Town. ️I’ve merged over 100 PRs from nearly 50 contributors, adding 44k lines of code that no…</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steve-yegge.medium.com</span></div></div></a></div> <time datetime="2026-01-14T19:39:13.273Z" class="date s-hik11q">January 14, 2026 at 7:39 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="148 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>148</span></span> <span class="stat s-hik11q" title="32 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>32</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc/post/3mcft3omsa226" target="_blank" class="permalink s-hik11q"><span>Read 30 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>However, I’m also not exactly a normal person:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mcfylftvw22s" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Steve Klabnik</span></bdi> <span class="handle s-12mzi62">@steveklabnik.com</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcfyeyndzs2s" title="January 14, 2026 at 9:13 PM" class="date s-12mzi62"><time datetime="2026-01-14T21:13:54.475Z">Jan 14</time></a></div> <p class="rich-text is-small s-1lecfnd">normal people: gas town is insane and yegge is on drugs
me, problematic french philosophy enjoyer: i dunno this seems not that weird at all
almost going to grad school broke me a little bit, i think</p> </div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> </div> <p class="rich-text is-large s-1lecfnd">"You can use Polecats without the Refinery" is too out there for you?</p> <div class="embeds s-azdpbr"><div class="image-embed is-bordered is-aligned s-t3k6fc"><div class="single-item tl tr bl br is-standalone s-t3k6fc" style="aspect-ratio: 500/522"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreiakmz36gmfjffm3scdr4s3mx5k63d5xjbplfsgi4bbmyjmnki6t4q" alt="you are like a little baby, watch this" class="image s-t3k6fc"/> <div class="placeholder s-t3k6fc"></div></div></div></div> <time datetime="2026-01-14T21:17:29.649Z" class="date s-hik11q">January 14, 2026 at 9:17 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="32 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>32</span></span> <span class="stat s-hik11q" title="1 repost"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>1</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcfylftvw22s" target="_blank" class="permalink s-hik11q"><span>Read 3 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>So, today I’d like to talk about Gas Town, and what I think Yegge is doing.</p>
<h2 id="what-is-gas-town">What is Gas Town?</h2>
<p>Gas town is two things simultaneously: something that’s kind of boring and
obvious, and something that’s opaque and confusing, illegible almost.</p>
<p>If you’ve found yourself saying “what?!?” when reading about Gas Town, this post
is for you.</p>
<p>I want to start with the boring and obvious, and move into the opaque and
illegible.</p>
<h2 id="gas-town-is-inevitable">Gas Town is inevitable</h2>
<p>Okay, so I almost went with “Gas Town is Inevitable” as the title of this post,
but I think that’s a bit too provocative, and not quite accurate. I do think
<em>something like</em> Gas Town was going to be tried by someone, sometime. That’s the
inevitable part. This is because Gas Town isn’t really that novel, actually.</p>
<p>Here’s what Gas Town is: you have a workspace. In that workspace, you have
projects. Each project has a bug tracker, used like normal: feature requests and
bug reports. Gas Town is software that allows you to instruct AI agents to
autonomously knock out those bugs.</p>
<p>That’s it.</p>
<p>This isn’t a fundamentally novel idea. The novel part comes in that it’s agents
doing the work, not humans.</p>
<p>This is why I describe it as “inevitable,” it’s an idea that pretty logically
follows from the idea of “a software agent can successfully complete programming
tasks.” Humans find a big list of tasks to be a useful way to organize work, so
there’s no reason to believe that agents would not. So you follow this idea
wherever it takes you. That’s the experimental part of Gas Town: does this work?
Can it work? We don’t know! Steve is trying.</p>
<p>One level of abstraction deeper, Steve is using some pretty tried and true ideas
here from Erlang. There’s supervisor trees, mailboxes, stuff like that. This is
a pretty solid base to work from! Erlang is legendary in these spaces for a reason.</p>
<p>Anyway yeah. That’s really just it. Unless you’re particularly interested in
this topic or question, that’s really all you need to know about Gas Town to
understand it.</p>
<p>So why all of the weird terminology?</p>
<h2 id="opacity-and-community-building">Opacity and community building</h2>
<p>AI is weird. The tools are very different than we’re used to. If 2025 was any
measure, 2026 is going to be a very interesting year for this profession. I know
I’m personally re-thinking a lot of sacred cows that I’ve worshipped for my
whole career. I don’t think all of them are bad, but I do think that things are
weird enough that it’s worth trying to figure things out from first principles
again.</p>
<p>And that’s where I think Steve is at too.</p>
<p>And I’ll also be honest, I’ve been slowly working myself into thinking about all
of this stuff too. I even have my own little project I started this week,
<a href="https://github.com/steveklabnik/docket">docket</a>, which is sort of one step down a road that Gas Town is many
miles down already. I wasn’t going to talk about Docket for a while, if ever,
but I think that maybe if Gas Town is too much, two thousand lines of Rust and a
little CLI may make more sense to you.</p>
<p>But that’s sort of the problem here: while Steve and I are very different,
there’s one way in which we’re the same: people read our blogs. I know that
projects I do get attention, heck, <a href="https://rue-lang.dev/">Rue</a> is not really ready for public
consumption, really. But because people find the stuff I mess around with to be
interesting, it got a lot of attention, far disproportionate for what it is. You
can put strings into an array, but you can’t even get them out yet! It’s just
too early!</p>
<p>So, what do you do if you:</p>
<ol>
<li>Know your projects get attention.</li>
<li>You also want like-minded people to come on this journey with you.</li>
<li>You don’t want to waste time explaining things to people who don’t get it.</li>
</ol>
<p>You reach for a tool that many people have used over the years: you get weird
with it.</p>
<p>There’s many examples of this happening in art and political movements. A pretty
famous one you might have heard of is <a href="https://en.wikipedia.org/wiki/Surrealism">Surrealism</a>. One of the core
ideas of surrealism is that they’re trying to unlock the power of the
subconscious mind, bypassing the conscious to (hopefully) reveal deeper, more
profound truths.</p>
<p>One of my favorite surrealist works is <a href="https://en.wikipedia.org/wiki/The_Treachery_of_Images">“La Trahison des images”</a> by
René Magritte. It is a painting of a pipe, with some words below: “Ceci n’est
pas une pipe” or “This is not a pipe.” At first, most people’s reaction to the
work is “uhhhh but that is a pipe?” But what the work is asking you to do is
think a little bit harder: this isn’t a pipe. It’s a painting of a pipe. That we
tend to conflate the image of something with the thing is a bit weird, if you
think about it a bit more. Why do we do that, anyway? What is the connection
between language, reality, images, and thoughts?</p>
<p>The painting was made in 1929, so these ideas are a bit more mainstream by now,
so you may not find this particular line of inquiry to be illuminating, but you
know, that’s what happens 97 years later.</p>
<p>Anyway. So if you’re someone who is going to attract attention, and want to
engage with a subset of those people, but also push away others, you could do
worse than trying to go surreal. It’s something that would be helpful both for
challenging assumptions, as well as for finding fellow travelers while repelling
those who aren’t interested in the same goals.</p>
<p>Maybe he is on metaphorical drugs. But that’s deliberate, is all I’m saying. And
maybe drugs aren’t your thing, and that’s fine too.</p>
<h2 id="is-this-a-good-thing">Is this a good thing?</h2>
<p>I also want to to make a few other things clear: I haven’t talked to Steve about
any of this, this is me just talking about what I see. Also, I haven’t tried Gas
Town myself, and I’m not sure I will. This is because while I’m somewhat aligned
with Steve’s train of thought here, I’m also not entirely.</p>
<p>I’m more interested in watching from afar. And that’s because, well, I have some
concerns with the way that Gas Town is doing things. But I’m also glad that he’s
trying it. It’s a rocket ship, and a lot of those exploded in the early days.
Tons of people died trying to invent the airplane. Some of their ideas were bad,
but eventually, enough of the conceptual space was explored to figure out the
good ideas.</p>
<h2 id="one-example-question">One example question</h2>
<p>What I want to leave you with is an example of the kind of experimentation I’m
seeing in Gas Town.</p>
<p>At Oxide, <a href="https://oxide.computer/principles">they talk a lot about their values</a>. It was a thing I really
appreciated about working there. On Monday, there was an episode of Oxide and
Friends on the topic of one of Oxide’s values as it applies to LLMs: Rigor.</p>
<bluesky-post src="at://did:plc:6235fybsui2cjt4fcspdbwpd/app.bsky.feed.post/3mcfgktcrbs2n" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:6235fybsui2cjt4fcspdbwpd" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:6235fybsui2cjt4fcspdbwpd/bafkreidqaixw7yj2vj64nd7ixiqncujwo6trchymloh2bycaorqg7bjkhy" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:6235fybsui2cjt4fcspdbwpd" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Adam Leventhal</span></bdi> <span class="handle s-hik11q">@ahl.bsky.social</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">On Monday, <a target="_blank" href="https://bsky.app/profile/did:plc:sl3g3nhnad34sfhjnqdivbzd" class="mention s-1lecfnd">@sunshowers.io</a> and <a target="_blank" href="https://bsky.app/profile/did:plc:ctphcgyhnllfluywokibtrgb" class="mention s-1lecfnd">@davidcrespo.bsky.social</a> joined me and <a target="_blank" href="https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc" class="mention s-1lecfnd">@bcantrill.bsky.social</a> to talk about their use of LLMs for building software--not to make vibe-coded one-offs, but to build much *more* robust systems software.</p> <div class="embeds s-azdpbr"><a target="_blank" href="https://youtu.be/fmFt4-jjEc0" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:6235fybsui2cjt4fcspdbwpd/bafkreiahoa3iyfh5cgirajxzhq5wafzqinzzqeh3piwzscs2mkt2ureu2u" alt="" class="thumbnail s-rtbqd8"/> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Oxide and Friends 1/12/2026 -- Engineering Rigor in the LLM Age</p> <p class="description s-rtbqd8">YouTube video by Oxide Computer Company</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">youtu.be</span></div></div></a></div> <time datetime="2026-01-14T15:55:02.860Z" class="date s-hik11q">January 14, 2026 at 3:55 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="56 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>56</span></span> <span class="stat s-hik11q" title="17 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>17</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:6235fybsui2cjt4fcspdbwpd/post/3mcfgktcrbs2n" target="_blank" class="permalink s-hik11q"><span>Read 2 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>Gas Town is <em>aggressively</em> not rigorous. And while rigor is a great value to
have, when you’re in an experimental mindset, relaxing rigor can be a valid
strategy to move forward.</p>
<p>But I think there’s a really interesting question to explore there: in the same
way that we can use LLMs in a rigorous way, is there a form of rigor that works
with AI agents merging code into trunk? What do we <em>really</em> mean by “rigor”? Is
it synonymous with “tests pass”? Does it mean that the state of the system must
be fully rigorous up front? Or is rigor an end state we work towards?</p>
<p>I am skeptical, but that’s also exactly why I’m thinking about such things. For
folks who are trying to reckon seriously with AI and the impact it will have on
software development, I think 2026 is a year to open your mind, challenge
assumptions, and re-consider what actually matters. The outcome of that might be
“yeah, we were right before and these ideas are bad,” but I have a sneaking
suspicion that the world has changed, and some old assumptions do not hold any
longer.</p>
<p>We’ll find out.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mcia32tae22j" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">How to think about Gas Town: <a target="_blank" href="https://steveklabnik.com/writing/how-to-think-about-gas-town/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/how-...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/how-to-think-about-gas-town/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">How to think about Gas Town</p> <p class="description s-rtbqd8">Blog post: How to think about Gas Town by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-01-15T18:36:50.633Z" class="date s-hik11q">January 15, 2026 at 6:36 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="138 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>138</span></span> <span class="stat s-hik11q" title="18 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>18</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcia32tae22j" target="_blank" class="permalink s-hik11q"><span>Read 21 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Getting started with Claude for software development</title>
<link>https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/</guid>
<description>2025 was an interesting year in many ways. One way in which it was interesting for me is that I went from an AI hater to a pretty big user. And so I’ve had a few requests for a “using Claude” guide, so I figure new year, why not give it a shot? The lack of this kind of content…</description>
<pubDate>Wed, 07 Jan 2026 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>2025 was an interesting year in many ways. One way in which it was interesting
for me is that I went from an AI hater to a pretty big user. And so I’ve had a
few requests for a “using Claude” guide, so I figure new year, why not give it a
shot? The lack of this kind of content was something that really frustrated me
starting out, so feels like a good thing to contribute to the world.</p>
<p>This post is going to be for software developers that are interested in learning
about using these tools as of early 2026. I’m going to spend this post talking
about some background, and then the first steps towards getting your feet wet.
If folks like it, I’ll follow up with more. There’s a lot here. I’m going to be
speaking about Claude directly, because it’s the tool I use the most, but a lot
of this should apply to other platforms as well.</p>
<h2 id="theres-a-lot-here">There’s a lot here</h2>
<p>The first thing I want to say on this topic is that there’s a reason that this
is the first post of possibly many: there’s a lot here, as I just said above.
This matters more than you might think at first. Becoming productive with LLMs
is not actually easy, no matter what other people tell you. A lot of advice in
this space is given by people who don’t have a teaching background, and have
forgotten how much work they’ve put in to get to where they are.</p>
<p>I liken it to vim: everyone acknowledges that modal editing is a different way
of working. We joke about how hard it is to learn how to quit vim if you’ve
accidentally started it up. But many people also acknowledge that the learning
curve is worth it, due to the power you get. I think of LLMs like vim: they’re
not super easy to get real results from, but the time invested can be worth it.</p>
<p>It’s also worth saying up front: maybe it’s not worth it, for you. I don’t fault
anyone for not wanting to spend time learning a new tool, especially in a space
that’s moving as fast as this one. Effectively everything I’m going to talk
about in this post has really only come into its own in the last 12-14 months.
Maybe in another 12 months this post will be useless. I don’t know. But just
like we might not find the time to learn vim to be worth it over just using a
more normal editor, that doesn’t mean that deciding that all of this isn’t worth
your time isn’t a rational, reasonable decision to make. You’re not going to be
“left behind” or whatever some of the boosters say, in the same way that you
don’t need to learn vim to do software dev. We aren’t doing vim vs emacs wars
here. We’re saying “hey if you want to learn vim this is how I think you can,
and if not, that’s fine.”</p>
<p>Furthermore, because there’s so much to cover, this post is going to be
background and step 1. Because otherwise it would be too dang long. You can’t
just read ten thousand words on this stuff and be an expert, you have to go
actually use the things. So you should be taking time between each post to go
and do that, and so not putting it all in one place should give you natural
breakpoints to go and actually try this stuff out.</p>
<h2 id="intentionality">Intentionality</h2>
<p>The very first thing I want to say on this entire topic is something that I
think about a lot. I have generally had better outcomes with LLMs than a lot of
people I know. And that’s bothered me. And I’m not sure exactly why that is. But
I do have one idea.</p>
<p>I like to approach this space in a … maybe “scientific” way is too strong, but
at least a rational one. I try things out, discard what doesn’t seem to work,
and keep what seems to work. I try and think critically about this space.</p>
<p>However.</p>
<p>I do think that the whole “vibe” term, while complicated in this space, is also
important. Vibes do matter, actually. I have some more science-y and some more
folks-y reasons that I believe this. But I do think that the attitude you bring
towards this process partially dictates your success, and I think you should be
conscious of that while you go on this journey.</p>
<p>Is that too woo-y for you? Okay, let me make it concrete: I un-ironically believe
that swearing at Claude makes it perform worse. I think you will get better
results working with an LLM if you treat them like you’d treat a respected
co-worker, and you will get worse results if you berate, insult, or otherwise
mistreat them.</p>
<p>This matters because I think that for a lot of LLM-skeptical people who give
this a shot, they may not actually go “Hey claude what’s your fucking problem”
(though I have literally seen this happen) they will tend to let their
frustrations show a bit more when things don’t work out. Use your emotional
regulation skills. It’s very okay to be critical in response to whatever Claude
does, but do it in a way that wouldn’t get you reported to HR in a healthy
company. Do this:</p>
<blockquote>
<p>Why did you do it that way? I would have preferred if we did <this> instead.</p>
</blockquote>
<p>Not this:</p>
<blockquote>
<p>Stop making such basic mistakes. You know that we do <this> and not <that>,
idiot.</p>
</blockquote>
<p>I think that being kind to people is good for you, but I also believe that even
if you’re a misanthrope, consider this a skill to get increased output from the
tool. I think a bit of anthropomorphization is actually a good thing here. We’ll
come back to that later during the more practical steps, but basically, that’s
the higher level principle at work: an LLM is not a person. But it is working
based off of language that people use. That’s its API. And so interacting with
it in the way you’d interact with a co-worker is, in my mind, the right way to
do it.</p>
<p>Maybe I’ll elaborate on this belief someday. Or maybe not. I do this for
personal belief reasons more than anything else. But it is something I want to
share.</p>
<h2 id="claude-web-vs-claude-code">Claude web vs Claude Code</h2>
<p>Okay! Now that we’ve got that out of the way, let’s talk about the various ways
you can use Claude! There’s a number of them, actually, but I want to focus on
two: on the web at <a href="https://claude.ai">https://claude.ai</a>, and with <a href="https://www.claude.com/product/claude-code">Claude
Code</a>.</p>
<p>Using Claude in these ways is fundamentally different. Both have pros and cons.
For real actual software development, you want to use Claude Code. This is due
to the “agentic loop”, which you’ll learn more about in a bit. But for your
first steps, using it via the web is okay. It’s mostly just important to know
that your experience using the web interface is not going to be the same as
using Claude Code. If I only had access to the web interface, I wouldn’t be so
bullish on this stuff. But it is and can be useful. Especially when getting your
feet wet, as long as you can understand that they’re meaningfully different.</p>
<h2 id="plans-vs-api-pricing">Plans vs API pricing</h2>
<p>This gets into another topic that matters: money. Another reason I do not fault
anyone for not spending time with these tools is that vim is free, whereas
Claude is very much not. However.</p>
<p>There are three major factors in the money equation: Claude Web vs Claude Code,
which models you have access to, and the actual cost. Let’s talk about them.</p>
<p>You can load up <a href="https://claude.ai">https://claude.ai</a> and talk to Claude right now for free. But
you cannot use Claude Code without paying. So if you want to start incredibly
small, using the website at first before you fork over money can make sense.
Again, that’s fine, just know that the experience is different. But it may be a
good way to start.</p>
<p>In 2024 and 2025, there was a good argument that you needed to be on a paid plan
because that’s how you got access to the latest models. While this is still true
to some degree, models have advanced far enough that the changes are less
important over time. I do think that in the first half of 2026, it still does
matter to a degree. Basically, the difference between Claude 3, 4, and 4.5 is
significant, but for me, Claude 4 was good enough a year ago to get real work
done. I’m not 100% sure which one you get for free today, but it’s at least
fine. And I think that by the time the next round of models come out, the ones
you’ll have access to for free will be basically good enough to make this
question moot. But do know that you get what you pay for, and paying for things
does get you better performance.</p>
<p>(Speaking of models, you’ll hear Claude referred to in three ways: Haiku,
Sonnet, and Opus. As the names imply, worst to best there, though also, fastest
to slowest. Sonnet, especially the 4.5 version, is pretty good for everything.
Opus 4.5 is wonderful. Haiku is great for certain things.)</p>
<p>As for actual cost: there’s $20/month, $100/month, and $200/month plans, as well
as “you pay per API call.” You might be tempted to think “I’ll just pay per API
call and keep my usage down.” This is a reasonable thing to think, and also a
terrible mistake to make. You get a lot of bang for your buck with the plans. To
give you an idea, I recently hit my weekly limit last night on the $200/month
plan, and my estimated usage for that week (which again, I’m paying $50 for)
would have been $1440.73 if I were paying by the API call. Now, I am a very
heavy user, but the point stands: as someone trying out these tools, it is way
easy to spend more than $20 of API tokens. If you want to give these tools a
real shot, come up with a spare $20, sign up for the cheap plan, and then cancel
after your experiment is over. You get access to Claude Code and you’ve capped
your spend. It’s a win/win. There’s some good secondary effects of trying to be
frugal here but I think that’s more of an intermediate than an advanced topic,
to be honest. I think worrying about the money while you build these skills is a
distraction. Cap your spend via a plan so that way you can not stress out about
breaking the bank.</p>
<p>Okay, with all of that background out of the way: let’s talk about your first
steps here.</p>
<h2 id="your-first-steps-a-conversation">Your first steps: a conversation</h2>
<p>Everyone is interested in the ability of LLMs to generate code. But I think
that’s actually step 2, not step 1. The way I want you to start using these
tools is purely <em>read-only</em> at first. This is also why the website is okay to
get started with too; Claude Code is far better at generating code than the site
is, but we’re not going to start by writing code.</p>
<p>Find some code you’ve written recently. It can be literally anything. Load up
<a href="https://claude.ai">https://claude.ai</a>, and type:</p>
<blockquote>
<p>Hi Claude! Can we talk about this code?</p>
</blockquote>
<p>And then paste your code in. You don’t need any fancy prompting techniques. You
don’t even need to say what language it is. Just give it some code. It could be
ten lines, it could be a hundred. I wouldn’t recommend a thousand to start.</p>
<p>Claude will probably respond with some sort of basic analysis of what you’ve
done, and then a question. I gave it ~50 lines of code a friend and I were
discussing recently, and it gave me this back:</p>
<blockquote>
<p>Sure! This looks like <description of what it does>. You’ve got <three things
that the code does>.</p>
<p>What’s on your mind about it? Are you thinking through the design, running
into a specific issue, or wanting feedback on a particular aspect?</p>
</blockquote>
<p>From here, you have a ton of options of which way to go, but they really depend
on what you’ve pasted in. Here’s some fun prompt ideas:</p>
<blockquote>
<p>Do you think this code is idiomatic?</p>
<p>If you could improve one thing about this code, what might it be?</p>
<p>If I wanted to modify this code to do <something>, how would you go about
doing that?</p>
<p>Are there any bugs in this code?</p>
<p>Are there any security implications of this code I may not have thought about?</p>
</blockquote>
<p>And so on. Anyway, the goal here is to just get used to this whole thing. It’s a
bit weird! It’s very different than talking to a compiler.</p>
<p>If Claude says something you disagree with, push back a little, just like you
would a co-worker:</p>
<blockquote>
<p>I’m not sure I agree with that. The reason why is that in some other part of
the system, there’s <behavior> and so that would impact this sort of
decision.</p>
<p>Why did you suggest that? I’d like to understand more.</p>
</blockquote>
<p>Claude will absolutely not be right all of the time. And that’s okay! The goal
is to work together, not that this is a magic tool that suddenly solves all of
your problems.</p>
<h2 id="second-steps-bigger-questions">Second steps: bigger questions</h2>
<p>Once you’ve done this a few times, you might want to graduate to Claude Code.
The reason for this is that you can start to scale up your questions. Once
you’ve installed it and logged in, you’ll be at a terminal prompt. It might bug
you about creating a CLAUDE.md, don’t worry about that for now. Continue having
conversations with Claude about your codebase. The reason that this is a a big
step up is that before, you had to paste all of the code in. Now, Claude can go
find your code itself. Some prompts for you to try:</p>
<blockquote>
<p>Please give me a code review of my codebase and suggest five things I could do
to improve it.</p>
<p>Can you find any bugs in <component>?</p>
<p>I’m curious about the performance of <component>, can we talk about it?</p>
</blockquote>
<p>One thing I like to do here is have Claude double check my intuitions. A few
months ago, working on an application in Rust, I was considering a refactoring.
I hadn’t done it because I was worried that it would be tedious, take a while,
and maybe not improve the codebase. It might! But it might not. But putting in
the day or two to do the refactor wasn’t really worth finding out if maybe that
would be wasted. So, I asked Claude. This is an example of a bit longer of a
prompt:</p>
<blockquote>
<p>Hi Claude! I am considering refactoring my code. In a function like this:
<paste code>, I don’t like how I did things, and I’m considering doing it
like this instead: <paste code>. However, I know that changes the signature,
which impacts other code in the codebase. A few questions for you: 1. how many
function signatures would need to be updated if I made this change? 2. can you
show me what the code would look like if I did this refactoring on one of my
simpler endpoints? 3. can you show me what the code would look like if I did
this refactoring on one of my most complex endpoints?</p>
</blockquote>
<p>Claude came back and said something like “250 signatures would need to change,
here’s the before and after using these two examples from your codebase.” Now,
Claude isn’t perfect: maybe it was actually 260 signatures. But the point is,
this helped me characterize my intuition here: it would be a non-trivial amount
of work. But I also got to see its impact on real code I had written, which
helped me decide if this refactoring would actually help me in some of the more
hairy parts of the codebase.</p>
<p>Note that there’s not really any special “prompt engineering” going on here. You
don’t need to do “as a senior software engineer” or stuff like that. Just talk
to it like you’d talk to a person. It’s fine. That doesn’t mean that prompts are
useless, but this sort of optimization is an intermediate to advanced topic, and
frankly, I’m skeptical that at this point the “as an x” technique even helps.
More on that someday. The point is, you can start asking more complex questions
as you get more comfortable with the tool.</p>
<p>Because Claude works asynchronously, you can just fire off questions like these
in the background, and come back to them when it’s done. Well, sorta. Let’s talk
about permissions before we wrap this up.</p>
<h2 id="permissions">Permissions</h2>
<p>By default, Claude will put you in an “ask before edits” mode. This is a good
way to start. It’ll check in with you before doing certain things, and you can
say yes or no. Please consider what it’s about to do, and give the answer you’re
comfortable with. Advanced users basically let Claude do whatever it wants, but
you’re not there yet, and there’s risks involved that aren’t obvious to you just
yet as a new user, so even though it can be a bit annoying to say yes every time
it asks, I’d encourage you to start off with minimal permissions. It gives you
the option to say “commands like this one are okay for the rest of my session”
and so when it wants to <code>grep</code> or something, that can be nice to agree to, but
I’d encourage you to not use it for writing code just yet, and tell it no if it
asks. We’ll do that in a follow-up post.</p>
<h2 id="in-conclusion">In conclusion</h2>
<p>So that’s my intro to getting started with Claude. Spend $20, talk to it like
you’d talk to a person, and use it as a means of getting feedback on your code,
don’t have it write anything just yet. Graduate to larger and larger questions
as you get comfortable with what it can do. Gently push back when you think it
gets out of line. But your goal here is a baseline understanding of what the
tool is capable of, not to vibe code out an entire app in an afternoon. These
skills may seem too basic, but I promise you, it gets harder from here, and so
you’ll want a solid foundation in read-only questions before we graduate to
having Claude write some code.</p>
<p>I hope this was helpful to you.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mbu3gbqrwk2y" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Getting started with Claude for software development: <a target="_blank" href="https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/gett...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Getting started with Claude for software development</p> <p class="description s-rtbqd8">Blog post: Getting started with Claude for software development by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-01-07T18:20:23.515Z" class="date s-hik11q">January 7, 2026 at 6:20 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="338 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>338</span></span> <span class="stat s-hik11q" title="51 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>51</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mbu3gbqrwk2y" target="_blank" class="permalink s-hik11q"><span>Read 29 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Thirteen years of Rust and the birth of Rue</title>
<link>https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/</guid>
<description>I started using Rust on December 21, 2012. It’s now December 22, 2025. That’s a long time. I don’t have a ton of thoughts about Rust to share today, like I thought I might. I program in the language regularly, it’s nice, I prefer it to all others. I have my beefs and my regrets,…</description>
<pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>I started using Rust on December 21, 2012. It’s now December 22, 2025. That’s a
long time.</p>
<p>I don’t have a ton of thoughts about Rust to share today, like I thought I
might. I program in the language regularly, it’s nice, I prefer it to all
others. I have my beefs and my regrets, like any real project, but for the most
part, I am pretty happy these days. To be honest, after a pretty rough last few
years, that’s been the theme of 2025: I’m pretty happy these days.</p>
<p>I have long been wondering if I should try and make my own language. I really
like them! That’s part of why I got involved in Ruby and then Rust in the first
place! But I’ll be turning 40 in a month, and I barely have time for things
anymore. So I had kind of put that on the back burner.</p>
<p>But one thing that 2025 changed: I went from an AI skeptic to writing most of my
code with Claude. I’m still a bit unsure about LLMs outside of programming, but
as a software development tool, I’ve found them finally to be good enough to be
legitimately useful. Your mileage may vary, I’m not really going to try and tell
you that they should be for you, only that they are for me.</p>
<p>And so, earlier this year, I embarked on starting my own little language project.
I decided to call it “Rue”, becuase:</p>
<ol>
<li>It has to start with Ru, duh. I did Ruby and then Rust, so my hands are
basically tied here…</li>
<li>It has a negative connotation, “to rue the day”, like Rust has a possible
negative interpretation.</li>
<li>It has a nature connotation, a
<a href="https://en.wikipedia.org/wiki/Ruta_graveolens">Rue</a>, like Rust has a possible
fungi connotation.</li>
<li>It’s short.</li>
</ol>
<p>I have also been wondering for a few years if you can build a language without
financial backing and a team. I think LLMs might change that equation. Yes,
Claude costs money, but I mean like, you don’t need to do what Mozilla did and
drop seven or eight figures worth of money on a development team to make
meaningful progress. And since I don’t have a ton of personal time these days,
what better way to test that than to actually give it a go.</p>
<p>Anyway, I got into it, and kind of made a mess of the first go. And then I had a
tremendously busy fall, and had to put things by the wayside.</p>
<p>But this week, I had some time after work, and decided to start over. And I’m
much more happy with how it’s going this time. So I’ve decided it’s time to at
least let the world know I’m working on it, just because why not.</p>
<p>I don’t expect it to grow to anything more than my hobby project, but then
again, that’s also true of Rasmus and Graydon and a ton of other language
authors, so, never say never. But that’s not an explicit goal here. I reserve
the right to just stop working on this when I think it’s not fun. I’m not trying
to build a community or even add committers. It’s just something for me for fun.</p>
<p>You can find Rue over at <a href="https://rue-lang.dev/">https://rue-lang.dev/</a> if you want to check it out.
There’s a blog with an RSS feed. I may blog there, I may not. But I’ll probably
keep it over there more than over here, in general.</p>
<p>I’m not saying that Claude is amazing, or that this code is fantastic, or
anything. The language is still very janky. I’m sure I have codegen bugs. It’s
missing basic features. It is very, very much a work in progress.</p>
<p>Thank you to everyone for making 2025 a nice year. There’s a lot of terrible
things in this world right now, but I’m more hopeful than ever, personally.
I hope to still be writing Rust in another ten years, and maybe even writing
some Rue. We’ll see :)</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3majjeqo4ik2t" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Thirteen years of <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> and the birth of <a target="_blank" href="https://bsky.app/hashtag/ruelang" class="hashtag s-1lecfnd">#ruelang</a> <a target="_blank" href="https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/thir...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Thirteen years of Rust and the birth of Rue</p> <p class="description s-rtbqd8">Blog post: Thirteen years of Rust and the birth of Rue by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-12-21T20:05:35.682Z" class="date s-hik11q">December 21, 2025 at 8:05 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="111 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>111</span></span> <span class="stat s-hik11q" title="10 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>10</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3majjeqo4ik2t" target="_blank" class="permalink s-hik11q"><span>Read 19 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>I see a future in jj</title>
<link>https://steveklabnik.com/writing/i-see-a-future-in-jj/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/i-see-a-future-in-jj/</guid>
<description>In December of 2012, I was home for Christmas, reading Hacker News. And that’s when I saw “ Rust 0.5 released ."" I’m a big fan of programming languages, so I decided to check it out. At the time, I was working on Ruby and Rails, but in college, I had wanted to focus on…</description>
<pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>In December of 2012, I was home for Christmas, reading Hacker News. And that’s
when I saw “<a href="https://news.ycombinator.com/item?id=4953293">Rust 0.5 released</a>."" I’m a big fan of programming languages, so I
decided to check it out. At the time, I was working on Ruby and Rails, but in
college, I had wanted to focus on compilers, and my friends were all very much
into systems stuff. So I decided to give Rust a try.</p>
<p>And I liked it! But, for other reasons I won’t get into here, I was thinking
about a lot of things in that moment. I was looking to shake things up a bit. So
I asked myself: is Rust going to be A Thing?</p>
<h2 id="why-i-chose-rust">Why I chose Rust</h2>
<p>So, I thought about it. What does a programming language need to be successful?
It needs some sort of market fit. It needs to have people willing to work on it,
as bringing a new language into the world is a lot of work. And it needs users.</p>
<p>When I considered all of these things, here’s what I saw with Rust:</p>
<p>Market fit: there was basically no credible alternatives to C and C++. I had
been involved in the <a href="https://dlang.org/">D community</a> a bit, but it was clear that it wasn’t going
to take off. Go was a few years old, and hit 1.0 earlier that year, but for the
kinds of work that C and C++ are uniquely able to do, I saw the same problem
that I did with D: garbage collection. This doesn’t mean Go isn’t a good
language, or that it’s not popular, but I didn’t see it as being able to
credibly challenge C and C++ in their strongholds. Rust, on the other hand, had
a novel approach to these problems: memory safety without garbage collection.
Now, I also need to mention that Rust back in those days was much closer to Go
than it even is today, but again, I had just learned about it for a few hours, I
didn’t really have a deep understanding of it yet. If I had, I actually might
have also dismissed it as well, as it wasn’t really GC that was the issue, but a
significant runtime. But again: I hadn’t really come to that understanding yet.
Point is: low-level programming was a space where there hadn’t been much
innovation in a very long time, and I thought that meant that Rust had a chance.
Check.</p>
<p>For a team: well, Mozilla was backing it. This is a big deal. It meant that
there were folks whose job it was to work on the language. There’s so much that
you need to do to make a new language, and that means a ton of work, which means
that if you’re going to be able to get it done in a reasonable amount of time,
having paid folks working on it is certainly better than the alternative. Check.</p>
<p>And finally, how does this translate into users? Well, Mozilla was planning on
using it in Firefox. This is huge. Firefox is a major project, and if they could
manage to use Rust in it, that would prove that Rust was capable of doing real
work. And, more importantly, it would mean that there would be a lot of folks
who would need to learn Rust to work on Firefox. This would create a base of
users, which would help the language grow. Check.</p>
<p>Finally, even though it wasn’t part of my initial assessment, I just really
liked the Rust folks. I had joined IRC and chatted with people, and unlike many
IRC rooms, they were actually really nice. I wanted to be around them more. And
if I did, other people probably would too. So that was also a plus.</p>
<p>So, I started learning Rust. I decided to write a tutorial for it, “Rust for
Rubyists,” because I’m a sucker for alliteration. And I eventually joined the
team, co-authored The Book, and if you’re reading this post, you probably know
the rest of the story.</p>
<h2 id="enter-jj">Enter jj</h2>
<p>For some background, <a href="https://jj-vcs.github.io/jj/latest/">jj</a> is a new version control system (VCS), not a
programming language. It is written in Rust though! While I talked about how I
decided to get involved with Rust above, my approach here generalizes to other
kinds of software projects, not just programming languages.</p>
<p>I have a rule of thumb: if <a href="https://sunshowers.io/">Rain</a> likes something, I will probably like that
thing, as we have similar technical tastes. So when I heard her talk about jj,
I put that on my list of things to spend some time with at some point. I was
especially intrigued because Rain had worked at Meta on their source control
team. So if she’s recommending something related to source control, that’s a
huge green flag.</p>
<p>It took me a while, but one Saturday morning, I woke up a bit early, and thought
to myself, “I have nothing to do today. Let’s take a look at jj.” So <a href="https://github.com/steveklabnik/jujutsu-tutorial/commit/fc8c588dfc58ce2ff54246174fc59aca0fc868b5">I did</a>.
You’ll note that link goes to a commit starting a book about jj. Since it
worked for me with Rust, it probably would work for me for jj as well. Writing
about something really helps clarify my thinking about it, and what better time
to write something for a beginner than when you’re also a beginner?</p>
<p>Anyway, people seem to really like my tutorial, and I’m thankful for that.</p>
<h2 id="the-future-of-jj">The future of jj</h2>
<p>So, what do I see in jj? Well, a lot of it kind of eerily mirrors what I saw in
Rust: a good market fit, a solid team, and a potential user base.</p>
<p>But the market fit is interesting. Git has clearly won, it has all of the
mindshare, but since you can use jj to work on Git repositories, it can be
adopted incrementally. At <a href="https://oxide.computer/">Oxide</a>, Rain started using jj, and more of us did,
and now we’ve got a chat channel dedicated to it. This is, in my opinion, the
only viable way to introduce a new VCS: it has to be able to be partially
adopted.</p>
<p>Google is using jj, and so that is a bit different than Mozilla, but the same
basic idea. I have more to say about Google’s relationship to jj, but that’s
going to be a follow-up blog post. What I will say in this post is that at the
first ever jj conference a few weeks ago, Martin (the creator of jj) said that
internal adoption is going really well. I’m burying the lede a bit here, because
the video isn’t up yet, and I don’t want to get the details of some of the more
exciting news incorrect in this post. I also don’t mean to imply that everyone
at Google is using jj, but the contingent feels significant to me, given how
hard it is to introduce a new VCS inside a company of that size. Well, in this
case, it’s using Piper as the backend, so you could argue about some of the
details here, but the point is: jj is being used in projects as small as
individual developers and as large as one of the largest monorepos in the world.
That’s a big deal. It can show the social proof needed for others to give jj a
chance.</p>
<p>Outside of Google, a lot of people say that there’s a bit of a learning curve, but
once you get over that, people really like it. Sound familiar? I think jj is
different from Rust in this regard in that it’s also very easy to learn if you
aren’t someone who really knows a ton about Git. It’s folks that really know Git
internals and have put time and care into their workflows that can struggle a
bit with jj, because jj is different. But for people who just want to get work
done, jj is really easy to pick up. And when people do, they often tend to like
it. jj has developed a bit of a reputation for having a passionate fanbase.
People are adopting it in a skunkworks way. This is a great sign for a new tool.</p>
<p>And finally, the team. Martin is very dedicated to jj, and has been working on
it for a long time. There’s also a small group of folks working on it with him.
It recently moved out from his personal GitHub account to its own organization,
and has started a more formal governance. The team is full of people who have a
deep history of working on source control tools, and they know what they’re
doing. The burgeoning jj community reminds me of that early Rust community: a
bunch of nice folks who are excited about something and eager to help it grow.</p>
<p>Basically, to me, jj’s future looks very bright. It reminds me of Rust in all of
the best ways.</p>
<h2 id="putting-my-money-where-my-mouth-is">Putting my money where my mouth is</h2>
<p>Speaking of burying the lede… I’ve decided to leave Oxide. Oxide is the best
job I’ve ever had, and I love the people I work with. I was employee 17. I think
the business will do fantastic in the future, and honestly it’s a bit of a
strange time to decide to leave, since things are going so well. But at the same
time, some of my friends have started a new company, <a href="https://ersc.io/">ERSC</a>, which is going to
be building a new platform for developer collaboration on top of jj. Don’t
worry, “errssk” isn’t going to be the name of the product. It’s kind of like how
GitHub was incorporated as <a href="http://logicalawesome.com/">Logical Awesome</a>, but nobody calls it that.</p>
<p>This won’t be happening until next month, I have some stuff to wrap up at Oxide,
and I’m going to take a week off before starting. But as sad as I am to be
leaving Oxide, I’m also really excited to be able to spend more time working in
the jj community, and helping build out this new platform. For those of you
who’ve been asking me to finish my tutorial, well, now I’ll have the time to
actually do that! I’m sorry it’s taken so long! You’ll see me talking about jj
even more, spending even more time in <a href="https://discord.gg/36cDSjyd6K">the Discord</a>, and generally being more
involved in the community. And I’ll be writing more posts about it here as well,
of course.</p>
<p>I’m really excited about this next chapter. 2025 has been a very good year for
me so far, for a number of reasons, and I am grateful to be able to take a
chance on something that I’m truly passionate about.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3m3sebwbris2a" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">I see a future in <a target="_blank" href="https://bsky.app/hashtag/jj-vcs" class="hashtag s-1lecfnd">#jj-vcs</a>: <a target="_blank" href="https://steveklabnik.com/writing/i-see-a-future-in-jj/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/i-se...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/i-see-a-future-in-jj/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">I see a future in jj</p> <p class="description s-rtbqd8">Blog post: I see a future in jj by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-10-22T17:22:31.703Z" class="date s-hik11q">October 22, 2025 at 5:22 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="384 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>384</span></span> <span class="stat s-hik11q" title="53 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>53</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3m3sebwbris2a" target="_blank" class="permalink s-hik11q"><span>Read 67 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>A tale of two Claudes</title>
<link>https://steveklabnik.com/writing/a-tale-of-two-claudes/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/a-tale-of-two-claudes/</guid>
<description>It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of…</description>
<pubDate>Tue, 10 Jun 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<blockquote>
<p>It was the best of times, it was the worst of times, it was the age of wisdom,
it was the age of foolishness, it was the epoch of belief, it was the epoch of
incredulity, it was the season of Light, it was the season of Darkness, it was
the spring of hope, it was the winter of despair, we had everything before us,
we had nothing before us, we were all going direct to Heaven, we were all going
direct the other way—in short, the period was so far like the present period,
that some of its noisiest authorities insisted on its being received, for good
or for evil, in the superlative degree of comparison only.</p>
<ul>
<li>Charles Dickens, <em>A Tale of Two Cities</em></li>
</ul>
</blockquote>
<p>I recently had two very different experiences with Claude Code. I wanted to
share them both, because I find the contrast interesting.</p>
<h2 id="claude-hates-tailwind-4">Claude hates Tailwind 4</h2>
<p>I have really been enjoying <a href="https://tailwindcss.com/">TailwindCSS</a>. I also have started several
web projects using it in the past year. Back in January, <a href="https://tailwindcss.com/blog/tailwindcss-v4">Tailwind released
version 4</a>. Honestly, I am not a good enough Tailwind user to be able
to appreciate the new features, but I do prefer to keep my projects up to date.
So I’ve needed to update my Tailwind projects to version 4.</p>
<p>Claude… is nearly completely useless for this.</p>
<p>You’d think that upgrading a very popular CSS framework would be a
straightforward task. But Claude has failed to set it up multiple times. While
it’s true that Claude Code claims that its training cutoff is in January of
2025 in its system prompt, it seems to have a cutoff in March. Regardless, a
useful tool shouldn’t need to be explicitly trained on the latest version of a
framework to be able to help with it. Yet, it consistently fails to either set
up Tailwind 4 in new projects, or upgrade existing projects to this fourth
release.</p>
<p>I did manage to get it to work earlier this week, when I <a href="https://github.com/steveklabnik/steveklabnik.com/commit/fc7a5f6d0a8169e45d67891fe784bdf2270f14f9">asked it to
update this website</a>, but at this point, it was mostly just me messing
around. I wish I had saved the exact prompt, but basically I said something like
“you have failed me consistently in this task, so please search for upgrade
guides and the like to guide you in this update.” It did do that, but for some
reason, the upgrade tool couldn’t update my configs, and so it decided to do it
itself, and well, while this time it did manage to technically update, it didn’t
do it completely correctly. It removed the typography plugin, which I had to ask
it to put back. And it didn’t properly import the config into my global CSS,
which also caused weird issues. Regardless, it did manage to sort of do it,
after some coaxing, but this was a frustrating experience that was probably the
third or fourth time I had tried to get it to do this.</p>
<p>While Tailwind 4 is a new major version release, it’s still incredibly popular,
and one of the hunches I have about LLM usage for coding is that more popular
tools will be easier for LLMs to use. This didn’t seem to be the case here,
though.</p>
<h2 id="claude-loves-elf">Claude loves… ELF?</h2>
<p>For… reasons, I am working on an assembler. Don’t worry about it, you may find
out about it someday, and you may not. It’s not for work, just for fun.</p>
<p>Anyway, if you’re not familiar, basically this process turns assembly code into
machine code. Specifically, in this case, I’m encoding x86_64 assembly into an
ELF executable. ELF is a file format that is used by, among other things, Linux,
for executables.</p>
<p>I’m working with Claude on this, and I again didn’t save my prompts, which I am
annoyed by, but I roughly said something like “please implement codegen such
that it produces an ELF executable” and let it go. I did not expect this to
work, or go well. As I said above, I sort of expect more common tasks to be
easier for LLMs, given that they have more training data. While I’m sure there’s
some code in Rust out there that does this, I still don’t expect it to be
anywhere near as common as TailwindCSS.</p>
<p>A few minutes later, Claude said “hey, I did it!” For completely unrelated
reasons, I got a message from <a href="https://bsky.app/profile/there.is.no.aarch64.mov">Ixi</a>, a friend of mine who would be
particularly interested in this project, and so I said something along the lines
of “hey, I’m working on a project you’d be interested in, claude has just made
some large claims:</p>
<blockquote>
<p>Steve, we’ve successfully implemented a complete end-to-end x86-64 code
generation pipeline</p>
</blockquote>
<p>Do you want to look at the PR together?” and they said yes.</p>
<p>So I shared my screen, and we went through the PR together. It was pretty fine.
A lot of the stuff at first was normal, but a bit boring: the error handling was
fine, and maybe slightly different than I would have done, but it wasn’t so bad
that if a co-worker had sent in the PR, I would have objected to merging it.</p>
<p>But Ixi noticed something:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> instruction_size</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">, instr</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">Instruction</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> u64</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> match</span><span style="color:#E1E4E8"> instr {</span></span>
<span class="line"><span style="color:#B392F0"> Instruction</span><span style="color:#F97583">::</span><span style="color:#B392F0">Push</span><span style="color:#E1E4E8">(_) </span><span style="color:#F97583">=></span><span style="color:#79B8FF"> 2</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#B392F0"> Instruction</span><span style="color:#F97583">::</span><span style="color:#B392F0">Pop</span><span style="color:#E1E4E8">(_) </span><span style="color:#F97583">=></span><span style="color:#79B8FF"> 2</span><span style="color:#E1E4E8">,</span></span></code></pre>
<p>These sizes are… wrong. They should (probably) be one byte. Fun! Most of the
code looked overall reasonable, though. So let’s look at that ELF it produced.</p>
<p>I ran the executable, and it segfaulted. Surprise! While Claude did think it was
done, because it had produced an ELF file, and the tests were passing, it didn’t
actually work. Given that those sizes were wrong, this wasn’t really a surprise.
We decided to dig into the code a bit more, but first: I told Claude “hey, this
example you compiled segfaults, something is wrong, can you fix it?” Again, not
the exact prompt. I wish I had saved it, but I didn’t think I was writing this
blog post at the time, so I didn’t. But it was very basic like that. And I let
Claude go in the background while we took a look.</p>
<p>We used <code>objdump -xd</code> to see some of the information about the ELF file. We
ended up using <code>gdb</code> to see where it was crashing:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>Program received signal SIGSEGV, Segmentation fault.</span></span>
<span class="line"><span> 0x0000000000400093 in ?? ()</span></span>
<span class="line"><span> #0 0x0000000000400093 in ?? ()</span></span>
<span class="line"><span> #1 0x000000000040007d in ?? ()</span></span>
<span class="line"><span> #2 0x0000000000000001 in ?? ()</span></span>
<span class="line"><span> #3 0x00007fffffffddb3 in ?? ()</span></span>
<span class="line"><span> #4 0x0000000000000000 in ?? ()</span></span></code></pre>
<p>There’s no debug info here, so we only have addresses. There was a jump going to
an address that ends in <code>93</code>. This is incorrect: <code>0x400093</code> was three bytes into
a <code>movabs $0x24, rdi</code>, so… we were jumping to the middle of an instruction,
which is nonsense, hence the segfault.</p>
<p>(Now that I’m looking at this, I also find these other addresses suspect. I wish
I had taken better notes while we were looking at this… the point is this was
a quick check as to what was going on, and it was very clear that this is just
straight-up wrong.)</p>
<p>How could this happen? Well, if the sizes of the instructions were wrong, when
the code figures out where to jump, it would go to the wrong place, as it would
mis-calculate how far away the jump target was. So this all made sense.
Hypothesis: instruction sizes are incorrect, which means the jump calculations
are incorrect, which means fail.</p>
<p>I had to run, so we ended the call, but I left Claude running. A few minutes
later, I checked in on it, and that’s when I was really surprised.</p>
<p>Claude had fixed the issue! It wasn’t really even about that, but also, how it
did so:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>● Bash(gdb -batch -ex "run" -ex "bt" --args ./debug_test)</span></span>
<span class="line"><span> ⎿ Program received signal SIGSEGV, Segmentation fault.</span></span>
<span class="line"><span> 0x0000000000400093 in ?? ()</span></span>
<span class="line"><span> #0 0x0000000000400093 in ?? ()</span></span>
<span class="line"><span> #1 0x000000000040007d in ?? ()</span></span>
<span class="line"><span> #2 0x0000000000000001 in ?? ()</span></span>
<span class="line"><span> #3 0x00007fffffffddb3 in ?? ()</span></span>
<span class="line"><span> #4 0x0000000000000000 in ?? ()</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Perfect! The crash is at 0x400093, which means the call</span></span>
<span class="line"><span>instruction is jumping to the wrong location. The issue is</span></span>
<span class="line"><span>in my relocation calculation. Let me examine this more carefully.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Looking at the hex dump again:</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Entry point: 0x400078</span></span>
<span class="line"><span>Call instruction: e8 16 00 00 00 (5 bytes)</span></span>
<span class="line"><span>Next instruction: 0x40007d</span></span>
<span class="line"><span>Target: 0x40007d + 0x16 = 0x400093</span></span>
<span class="line"><span></span></span>
<span class="line"><span> But the main function is actually at file offset 0x8A, which is</span></span>
<span class="line"><span> virtual address 0x40008A.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> The issue is in my symbol address calculation. Let me examine</span></span>
<span class="line"><span> the assembler code:</span></span></code></pre>
<p>Hmmm. It’s kind of right, but also maybe not? I kept scrolling:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span> ● I found the issue! The assembler is calculating symbol</span></span>
<span class="line"><span> addresses relative to the start of the machine code (offset 0),</span></span>
<span class="line"><span> but when resolving relocations, it needs to account for the</span></span>
<span class="line"><span> fact that the machine code will be loaded at the ELF entry</span></span>
<span class="line"><span> point address.</span></span></code></pre>
<p>That also didn’t feel right? More scrollback through. It was creating sample
programs in assembly, and then assembling them, and then examining the contents
of the files. It was double checking its assumptions and understanding of the
code, and then, there it was:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>There's a bug in my instruction size calculation. Let me check</span></span>
<span class="line"><span>the instruction_size function:</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Read(src/lib.rs)</span></span>
<span class="line"><span> ⎿ Read 20 lines (ctrl+r to expand)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Found the bug! Mov(, _) => 10 is wrong. It assumes all mov</span></span>
<span class="line"><span> instructions are 10 bytes (mov reg, imm64), but mov rdi, rax</span></span>
<span class="line"><span> is only 3 bytes.</span></span></code></pre>
<p>Okay, wait… <code>mov</code>, and not <code>push</code>/<code>pop</code>? Sure enough, not only had it figured
out that <code>push</code> and <code>pop</code> were one byte, not two, but it had also figured out
that <code>cmp</code> had a few forms with different sizes, and that <code>mov</code> did too. After
fixing that… the executables worked!</p>
<p>But really, the most impressive part is that it had basically done exactly what
we had done, but in the background, while we were looking at the code. It took a
little longer to get there, because it went down one rabbit hole, but it had
also found other bugs that weren’t immediately obvious to us. And, I’ll be extra
honest: while I understand what’s going on here, I am not super great at
<code>objdump</code> and <code>gdb</code>, and if Ixi wasn’t around, Claude would have probably found
this before me, if I were alone. This is part of why having friends is great,
but if I didn’t have that friend around at that moment, this would have
tremendously helped.</p>
<p>I am also impressed because, as mentioned above: I would expect this task to be
much more rare than web development stuff. Yet, Claude did a far better job,
finding the issue quickly and fixing it, as opposed to the multiple times I gave
it a chance with Tailwind.</p>
<h2 id="conclusion">Conclusion</h2>
<p>What does this mean? I don’t know, but I figured I want to start documenting
these experiences, good and bad, and so I wanted to share both of these stories,
one good, and one bad.</p>
<p>LLMs are not magic, or perfect, but I have found them to be useful. It’s
important to share both the successes, and also the failures. I’m hoping to do a
better job of tracking the prompts and details to give you a better
understanding of the specifics of how I’m using these tools, but the perfect is
the enemy of the good, and so these are the stories you’re going to get for now.</p>
<hr/>
<p>Here’s my post about this post on Bluesky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lrb6sb7fzl2i" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">A tale of two Claudes
<a target="_blank" href="https://steveklabnik.com/writing/a-tale-of-two-claudes/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/a-ta...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/a-tale-of-two-claudes/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">A tale of two Claudes</p> <p class="description s-rtbqd8">Blog post: A tale of two Claudes by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-06-10T15:17:05.751Z" class="date s-hik11q">June 10, 2025 at 3:17 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="57 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>57</span></span> <span class="stat s-hik11q" title="9 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>9</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lrb6sb7fzl2i" target="_blank" class="permalink s-hik11q"><span>Read 10 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Is Rust faster than C?</title>
<link>https://steveklabnik.com/writing/is-rust-faster-than-c/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/is-rust-faster-than-c/</guid>
<description>Someone on Reddit recently asked: What would make a Rust implementation of something faster than a C implementation, all things being the same? I think this is a great and interesting question! It’s really tough because it ultimately relies on what exactly you mean by “all things…</description>
<pubDate>Mon, 09 Jun 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>Someone on Reddit recently asked:</p>
<blockquote>
<p>What would make a Rust implementation of something faster than a C
implementation, all things being the same?</p>
</blockquote>
<p>I think this is a great and interesting question! It’s really tough because it
ultimately relies on what exactly you mean by “all things being the same.” And I
think this is something that makes it hard to compare languages.</p>
<p>Here’s some ways in which you can argue that things are “the same” but also,
that they’re not, and what they imply for runtime performance.</p>
<h2 id="inline-assembly">Inline Assembly</h2>
<p>Rust has inline assembly built into the language. C has inline assembly as a
very common compiler extension, to the degree where saying it’s not part of the
language is an arguable nitpick.</p>
<p>Here’s an example in Rust:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">use</span><span style="color:#B392F0"> std</span><span style="color:#F97583">::</span><span style="color:#B392F0">arch</span><span style="color:#F97583">::</span><span style="color:#E1E4E8">asm;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">#[</span><span style="color:#F97583">unsafe</span><span style="color:#E1E4E8">(no_mangle)]</span></span>
<span class="line"><span style="color:#F97583">pub</span><span style="color:#F97583"> fn</span><span style="color:#B392F0"> rdtsc</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">-></span><span style="color:#B392F0"> u64</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> lo</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> hi</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#F97583"> unsafe</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0"> asm!</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#9ECBFF"> "rdtsc"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#B392F0"> out</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"eax"</span><span style="color:#E1E4E8">) lo,</span></span>
<span class="line"><span style="color:#B392F0"> out</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"edx"</span><span style="color:#E1E4E8">) hi,</span></span>
<span class="line"><span style="color:#B392F0"> options</span><span style="color:#E1E4E8">(nomem, nostack, preserves_flags),</span></span>
<span class="line"><span style="color:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8"> ((hi </span><span style="color:#F97583">as</span><span style="color:#B392F0"> u64</span><span style="color:#E1E4E8">) </span><span style="color:#F97583"><<</span><span style="color:#79B8FF"> 32</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">|</span><span style="color:#E1E4E8"> (lo </span><span style="color:#F97583">as</span><span style="color:#B392F0"> u64</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This reads the time stamp counter with <code>rdtsc</code>, and returns its value.</p>
<p>Here’s the example in C:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="c"><code><span class="line"><span style="color:#F97583">#include</span><span style="color:#9ECBFF"> <stdint.h></span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">uint64_t</span><span style="color:#B392F0"> rdtsc</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">void</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">{</span></span>
<span class="line"><span style="color:#F97583"> uint32_t</span><span style="color:#E1E4E8"> lo, hi;</span></span>
<span class="line"><span style="color:#F97583"> __asm__</span><span style="color:#E1E4E8"> _</span><span style="color:#B392F0">_volatile__</span><span style="color:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#9ECBFF"> "rdtsc"</span></span>
<span class="line"><span style="color:#E1E4E8"> : </span><span style="color:#9ECBFF">"=a"</span><span style="color:#E1E4E8">(lo), </span><span style="color:#9ECBFF">"=d"</span><span style="color:#E1E4E8">(hi)</span></span>
<span class="line"><span style="color:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> ((</span><span style="color:#F97583">uint64_t</span><span style="color:#E1E4E8">)hi </span><span style="color:#F97583"><<</span><span style="color:#79B8FF"> 32</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">|</span><span style="color:#E1E4E8"> lo;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>These (under <code>-0</code> in both rustc <code>1.87.0</code> and clang <code>20.1.0</code>) produce the same
assembly:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="asm"><code><span class="line"><span style="color:#F97583">rdtsc</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#F97583"> rdtsc</span></span>
<span class="line"><span style="color:#F97583"> shl</span><span style="color:#79B8FF"> rdx</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">32</span></span>
<span class="line"><span style="color:#F97583"> mov</span><span style="color:#79B8FF"> eax</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">eax</span></span>
<span class="line"><span style="color:#F97583"> or</span><span style="color:#79B8FF"> rax</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">rdx</span></span>
<span class="line"><span style="color:#F97583"> ret</span></span></code></pre>
<p>Here’s a link on Godbolt: <a href="https://godbolt.org/z/f7K8cfnx7">https://godbolt.org/z/f7K8cfnx7</a></p>
<p>Does this count? I don’t know. I don’t think it really speaks to the question
asked, but it is one way to answer the question.</p>
<h2 id="similar-code-different-results">Similar code, different results</h2>
<p>Rust and C can have different semantics for similar code. Here’s a struct in
Rust:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Rust</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> x</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> y</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u64</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> z</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Here’s “the same” struct in C:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="c"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#E1E4E8"> C {</span></span>
<span class="line"><span style="color:#F97583"> uint32_t</span><span style="color:#E1E4E8"> x;</span></span>
<span class="line"><span style="color:#F97583"> uint64_t</span><span style="color:#E1E4E8"> y;</span></span>
<span class="line"><span style="color:#F97583"> uint32_t</span><span style="color:#E1E4E8"> z;</span></span>
<span class="line"><span style="color:#E1E4E8">};</span></span></code></pre>
<p>In Rust, this struct is 16 bytes (on x86_64, again) and in C, it is 24. This is
because Rust is free to reorder the fields to optimize for size, while C
is not.</p>
<p>Is this the same, or different?</p>
<p>In C, you can re-order the fields to get the same size. In Rust, you can write
<code>#[repr(C)]</code> to get the same layout as C. Does this mean we should have written
different Rust or different C to get “the same” thing?</p>
<h2 id="social-factors">Social Factors</h2>
<p>Some people have reported that, thanks to Rust’s checks, they are more willing
to write code that’s a bit more dangerous than in the equivalent C (or C++),
where they’d do a bit more copying to play it a bit safer. This would be “the
same” in the sense of the same devs on the same project, but the code would be
different, due to judgement calls. You can make an argument that that’s not the
same, but is different too.</p>
<p>An example of this from a long time ago is the Stylo project. Mozilla tried to
parallelize Firefox’s style layout twice in C++, and both times the project
failed. The multithreading was too tricky to get right. The third time, they
used Rust, and managed to ship. This is the same project, (though not the same
programmers, I believe) by the same organization, but one was possible and one
was not. Is that “the same”? In some senses, but not in others.</p>
<p>This also goes for a similar question: assuming we have a junior developer
writing Rust, and also writing C, for the same task. Are we going to get faster
code in one or the other? This controls for ability, but not for the same code.
Is that “the same”? I don’t know. What about an expert in each language, someone
who knows Rust super well but doesn’t know C, and vice versa, being given the
same task? Is that different than a junior, or an “average” developer?</p>
<h2 id="compile-time-vs-runtime">Compile time vs runtime?</h2>
<p>Another redditor asks:</p>
<blockquote>
<p>I’m not Rust expert, but aren’t most (all?) of the safety checks compile time
checks? They shouldn’t have any runtime impact.</p>
</blockquote>
<p>A great question! Part of this is another example of defaults being different.</p>
<p><code>array[0]</code> is valid in both languages.</p>
<p>In Rust, there’s a bounds check at runtime. In C, there is not. Does this mean
that they’re the same? In Rust, I could write <code>array.get_unchecked(0)</code>, and get
C’s semantics. In C, I could write a bounds check to get Rust’s semantics. Are
any of those “the same”?</p>
<p>In Rust, that check may be optimized away, if the compiler can prove it’s safe.
In C, if we wrote the bounds check by hand, the check may be optimized away, if
the compiler can prove it’s safe. Are any of those “the same”?</p>
<p>They aren’t wrong that a lot of Rust’s safety checks are at compile time. But
some are at runtime. But this raises another interesting question: That compile-time
check may cause you to write different code for the same task as in C. A common
example is using indices rather than pointers. That may mean that the generated
code performs differently. Is that check truly “at compile time”? Technically,
at the micro level, yes. At the engineering level? Possibly not!</p>
<h2 id="my-conclusions">My Conclusions</h2>
<p>I think the most important part of this question is related to possibility, that
is:</p>
<ol>
<li>If we assume C is the ‘fastest language,’ whatever that means.</li>
<li>Is there any inherent reason why Rust could not do the same things?</li>
</ol>
<p>I think the answer to that is “no,” even ignoring the inline assembly case. So
on that most important, fundamental level, the answer is “there’s no difference
between the two.”</p>
<p>But we’re not usually talking about that. We’re usually talking about something
in the context of engineering, a specific project, with specific developers,
with specific time constraints, and so on. I think that there are so many
variables that it is difficult to draw generalized conclusions.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lr6xouhoxc2i" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Is <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> faster than C?
<a target="_blank" href="https://steveklabnik.com/writing/is-rust-faster-than-c/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/is-r...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/is-rust-faster-than-c/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Is Rust faster than C?</p> <p class="description s-rtbqd8">Blog post: Is Rust faster than C? by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-06-09T18:04:36.057Z" class="date s-hik11q">June 9, 2025 at 6:04 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="91 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>91</span></span> <span class="stat s-hik11q" title="19 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>19</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lr6xouhoxc2i" target="_blank" class="permalink s-hik11q"><span>Read 10 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>I am disappointed in the AI discourse</title>
<link>https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/</guid>
<description>Recently, something happened that made me kind of like, break a little bit. I’m still not entirely sure why this whole thing bothers me so much, but here’s a blog post about it. I happened to ask ChatGPT something for fun. I often do this just to see what it says, and if I feel…</description>
<pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>Recently, something happened that made me kind of like, break a little bit. I’m
still not entirely sure why this whole thing bothers me so much, but here’s a
blog post about it. I happened to ask ChatGPT something for fun. I often do this
just to see what it says, and if I feel like it’s right or wrong. I tabbed over
to another tab, and the top post on my Bluesky feed was something along these
lines:</p>
<blockquote>
<p>ChatGPT is not a search engine. It does not scan the web for information. You
cannot use it as a search engine. LLMs only generate statistically likely
sentences.</p>
</blockquote>
<p>The thing is… ChatGPT was over there, in the other tab, searching the web. And
the answer I got was pretty good.</p>
<p>I’m not linking to the post because I don’t want to continue to pile on to this
person, because it’s not really about them. It’s about the state of the
discourse around AI.</p>
<p>I am not claiming that ChatGPT is an amazing search engine. What is breaking my
brain a little bit is that all of the discussion online around AI is so
incredibly polarized. This isn’t a “the middle is always right” sort of thing
either, to be clear. It’s more that both the pro-AI and anti-AI sides are loudly
proclaiming things that are pretty trivially verifiable as not true. On the anti
side, you have things like the above. And on the pro side, you have people
talking about how no human is going to be programming in 2026. Both of these
things fail a very basic test of “@gork is this true?” (sorry this is a bad
joke, if you’re not terminally <del>38 years old</del>on Bluesky, substitute “chat is
this true?” (sorry this is a bad joke, if you’re over the age of 40 substitute
“is this true?” (sorry humor is one way of coping with stress and writing this
post is stressing me out))).</p>
<p>Oh, and of course, ethical considerations of technology are important too. I’d
like to have better discussions about that as well. <del>For me, capabilities precede
the ethical dimension, because the capabilities inform what is and isn’t
ethical.</del> (EDIT: I am a little unhappy with my phrasing here. A shocker given
that I threw this paragraph together in haste! What I’m trying to get at is that
I think these two things are inescapably intertwined: you cannot determine the
ethics of something until you know what it is. I do not mean that capabilities
are somehow more important.) But I also know reasonable people disagree with me
on that. Let’s talk about it! I mean that for <em>all</em> dimensions of this topic,
not solely the capabilities.</p>
<p>Now, it’s not like I expect everyone to come to some consensus on a technology.
For example, a lot of people like Rust and hate Go, and a lot of people like Go
and hate Rust. That’s fine. That doesn’t bother me. But there’s something
different about this that is driving me up a wall.</p>
<p>To be clear, I am not particularly pro or anti AI. Here’s what I currently
think:</p>
<ul>
<li>LLMs can be helpful for software development.</li>
<li>LLMs for software development are being oversold by some people who stand to
gain a lot of money from them.</li>
<li>LLMs for software development are being undersold by some people who have
decided for various reasons that they do not like the technology.</li>
<li>Not everyone who dislikes LLMs is uninformed, just like not everyone who likes
LLMs is a grifter.</li>
<li>AI art is often terrible and slightly creepy.</li>
<li>I have seen AI art that was fine. This is very rare.</li>
<li>AI writing is often bland and boring but better than the average person’s
writing. Just to be clear, I feel like an elitist dick saying that. And maybe
I am. I dunno, working on it.</li>
</ul>
<p>Anyway, just to be clear, (how many times can you say ‘just to be clear’ in one
post, Steve, come on (at what level of nested parenthetical do I implement some
sort of interlocutor into my blog like <a href="https://xeiaso.net/">Xe</a>)) I don’t think that if you
think LLMs suck at software development, you’re wrong. What I want to be able to
do is talk about it in a reasonable way, and figure out why we have different
opinions. That is it. But for some reason, being able to do that feels
impossible.</p>
<p>I’m going to end up blogging more about AI/LLMs in the near future, to get some
more of this stuff off of my chest and to try and be the change I want to see in
the world. So expect some of that, probably. But until then, if you happen to
have any links to reasonable discussion on these topics, I’d love to see them.
Here’s two posts I recently read and really enjoyed thinking about what they
were saying:</p>
<ul>
<li><a href="https://annievella.com/posts/the-software-engineering-identity-crisis/">The Software Engineering Identity Crisis</a></li>
<li><a href="https://jmsdnns.com/tech/algo-underground/">Algorithmic Underground</a></li>
</ul>
<p>Thanks. If you want to get mad about this post please just ignore me.</p>
<hr/>
<p>Here’s my post about this post on Bluesky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lqaqe6uc3c2u" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">I am disappointed in the AI discourse <a target="_blank" href="https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/i-am...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">I am disappointed in the AI discourse</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-05-28T17:33:29.514Z" class="date s-hik11q">May 28, 2025 at 5:33 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="909 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>909</span></span> <span class="stat s-hik11q" title="266 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>266</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lqaqe6uc3c2u" target="_blank" class="permalink s-hik11q"><span>Read 208 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Rust 1.0, ten years later</title>
<link>https://steveklabnik.com/writing/rust-ten-years-later/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/rust-ten-years-later/</guid>
<description>It’s kind of hard to believe it’s been ten years since Rust 1.0 was released. I stopped working on Rust three years ago, and frankly, the last three years have been pretty rough. But 2025 is shaping up to be a really good year for me, with lots of positive changes. With that in…</description>
<pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>It’s kind of hard to believe it’s been ten years since Rust 1.0 was released.</p>
<p>I stopped working on Rust three years ago, and frankly, the last three years
have been pretty rough. But 2025 is shaping up to be a really good year for me,
with lots of positive changes.</p>
<p>With that in mind, this post is going to be a bit of a different one than you
might expect. For a fantastic post that’s a bit more traditional, check out
what Graydon wrote for the Rust Foundation: <a href="https://rustfoundation.org/media/10-years-of-stable-rust-an-infrastructure-story/">10 years of stable Rust: an
infrastructure story</a>. It’s fantastic, of course.</p>
<p>I’d instead like to reflect a bit on a comment I saw on the internet the other day:</p>
<blockquote>
<p>[Rust is] not a great hobby language but it is a fantastic professional
language, precisely because of the ease of refactors and speed of development
that comes with the type system and borrow checker.</p>
</blockquote>
<p><a href="https://pcwalton.github.io/">Patrick Walton</a>, one of the earliest contributors to Rust, had this
to say:</p>
<blockquote>
<p>I never thought I’d live to see the day when someone would say this. The first
5 years of Rust were all “this is interesting for hobby projects but nobody will
ever adopt this in industry”.</p>
</blockquote>
<p>The release of Rust 1.0 was great, but it was also stressful. The few years
before Rust 1.0 were marked by a deep, deep anxiety among the team. I’m sure
someone else who was around back then will disagree with me, but that’s my
recollection. It may not have always been overt, but it was there. A large
question was looming over us: would anyone actually use this thing? Would it
become a hobbyist language, or would it be something that people actually
used in production?</p>
<p>At this point, we now know the answer: yes, Rust is used a lot. It’s used
for real, critical projects to do actual work by some of the largest companies
in our industry. We did good.</p>
<p>The release of 1.0 was the start of that journey in earnest. It was time to stop
changing things all the time, and to start focusing on stability. It was time to
gain more users, and to fix the issues that they found. We had to start shipping
stable releases, and we had to start doing it on a regular basis. We had to
start doing all of the things that a real programming language does.</p>
<p>Over the last ten years, a lot changed. It wasn’t easy, and it took a lot of
blood, sweat, and many, many tears. But we did it. We built a real programming
language, and we built a real community around it.</p>
<p>Rust is very much not perfect. There’s more work to be done, and arguably,
someone out there should probably be starting a language that will someday eat
Rust’s lunch.</p>
<p>But ten years ago, we were pushing a button and praying that it would all work
out. And now, ten years later, I’m doing this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>❯ rustup update stable</span></span>
<span class="line"><span>info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'</span></span>
<span class="line"><span>info: latest update on 2025-05-15, rust version 1.87.0 (17067e9ac 2025-05-09)</span></span>
<span class="line"><span>info: downloading component 'rust-src'</span></span>
<span class="line"><span>info: downloading component 'cargo'</span></span>
<span class="line"><span>info: downloading component 'clippy'</span></span>
<span class="line"><span>info: downloading component 'rust-docs'</span></span>
<span class="line"><span>info: downloading component 'rust-std'</span></span>
<span class="line"><span>info: downloading component 'rustc'</span></span>
<span class="line"><span>info: downloading component 'rustfmt'</span></span>
<span class="line"><span>info: removing previous version of component 'rust-src'</span></span>
<span class="line"><span>info: removing previous version of component 'cargo'</span></span>
<span class="line"><span>info: removing previous version of component 'clippy'</span></span>
<span class="line"><span>info: removing previous version of component 'rust-docs'</span></span>
<span class="line"><span>info: removing previous version of component 'rust-std'</span></span>
<span class="line"><span>info: removing previous version of component 'rustc'</span></span>
<span class="line"><span>info: removing previous version of component 'rustfmt'</span></span>
<span class="line"><span>info: installing component 'rust-src'</span></span>
<span class="line"><span>info: installing component 'cargo'</span></span>
<span class="line"><span>info: installing component 'clippy'</span></span>
<span class="line"><span>info: installing component 'rust-docs'</span></span>
<span class="line"><span> 19.9 MiB / 19.9 MiB (100 %) 16.3 MiB/s in 1s ETA: 0s</span></span>
<span class="line"><span>info: installing component 'rust-std'</span></span>
<span class="line"><span> 29.4 MiB / 29.4 MiB (100 %) 22.2 MiB/s in 1s ETA: 0s</span></span>
<span class="line"><span>info: installing component 'rustc'</span></span>
<span class="line"><span> 76.3 MiB / 76.3 MiB (100 %) 23.9 MiB/s in 3s ETA: 0s</span></span>
<span class="line"><span>info: installing component 'rustfmt'</span></span>
<span class="line"><span></span></span>
<span class="line"><span> stable-x86_64-unknown-linux-gnu updated - rustc 1.87.0 (17067e9ac 2025-05-09) (from rustc 1.86.0 (05f9846f8 2025-03-31))</span></span></code></pre>
<p>and then going about my day. It is a rare privilege to be able to spend ten
years building a tool you want to use, and then be able to spend your time at
work using it. I am grateful to have had that opportunity, and I hope that
everyone else who has worked on Rust over the years feels the same way.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lpa37moavc2s" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd"><a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> 1.0, ten years later <a target="_blank" href="https://steveklabnik.com/writing/rust-ten-years-later/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/rust...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/rust-ten-years-later/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Rust 1.0, ten years later</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-05-15T17:49:56.018Z" class="date s-hik11q">May 15, 2025 at 5:49 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="198 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>198</span></span> <span class="stat s-hik11q" title="35 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>35</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lpa37moavc2s" target="_blank" class="permalink s-hik11q"><span>Read 13 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Thoughts on Bluesky Verification</title>
<link>https://steveklabnik.com/writing/thoughts-on-bluesky-verification/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/thoughts-on-bluesky-verification/</guid>
<description>Today, Bluesky rolled out blue checks . And, I was given one. Now, I’m not the biggest fan of this sort of feature, however, I also don’t really think this kind of feature is for me. I really like the idea of domain verification; I have been like “oh okay that’s coming from a…</description>
<pubDate>Mon, 21 Apr 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>Today, <a href="https://bsky.social/about/blog/04-21-2025-verification">Bluesky rolled out blue checks</a>. And, I was given one.
Now, I’m not the biggest fan of this sort of feature, however, I also don’t
really think this kind of feature is for me. I really like the idea of domain
verification; I have been like “oh okay that’s coming from a <code>.gov</code> account”
from time to time. But I don’t think most people really think about domains the
way that programmers do.</p>
<p>I have wanted to update my <a href="https://steveklabnik.com/writing/how-does-bluesky-work/">“How does Bluesky work?”</a> post for a while
now, but I’ve been super busy. So, let’s ignore the product question for now,
and focus on the technical question. How does verification work?</p>
<h2 id="the-golden-rule-of-atproto">The golden rule of atproto</h2>
<p>I’ve also been thinking about designing apps on top of atproto. There’s a kind
of rule of thumb that I realized about doing this, a sort of “golden rule” if
you will:</p>
<blockquote>
<p>You are the only person who can write records into your PDS.</p>
</blockquote>
<p>This has really interesting implications! For example, sometimes people ask for
“soft blocks” on Bluesky. This is a twitter ‘feature’ where if you block
someone, and then unblock them, they’re not following you any more. But on
Bluesky, if you soft block someone, they’re still following you. Why does that
happen?</p>
<p>Following someone on Bluesky means that you write a record of the lexicon
<a href="https://github.com/bluesky-social/atproto/blob/a1b2370dbc451570eafb982f326e8c110277ac01/lexicons/app/bsky/graph/follow.json"><code>app.bsky.graph.follow</code></a> into your PDS. So you might assume that
blocking someone would delete this record from their PDS. But that would violate
the golden rule! You can’t delete records from someone else’s PDS. So, instead,
blocking someone on Bluesky means that you write a record of the lexicon
<a href="https://github.com/bluesky-social/atproto/blob/a1b2370dbc451570eafb982f326e8c110277ac01/lexicons/app/bsky/graph/block.json"><code>app.bsky.graph.block</code></a> into your PDS. Unblocking them deletes that
record from your PDS. But it doesn’t delete the record from their PDS. So, if
you block someone, and then unblock them, they still have a record in their PDS
that says they’re following you.</p>
<p>This may not be the behavior that you want as a user, but it follows from the
constraints of the overall protocol design.</p>
<h2 id="how-verification-works">How verification works</h2>
<p>Now that we know the golden rule, we can understand how verification works. How
does an account get verified? Well, someone has to be writing a record into a
PDS somewhere. The natural design is the one they’ve chosen: someone becomes
verified by someone else writing a record of the type
<a href="https://github.com/bluesky-social/atproto/blob/a1b2370dbc451570eafb982f326e8c110277ac01/lexicons/app/bsky/graph/verification.json"><code>app.bsky.graph.verification</code></a> into their PDS. <a href="https://pdsls.dev/at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.graph.verification/3lndpy3ngb62l">Here</a> is
the record verifying me. It looks like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>
<span class="line"><span style="color:#79B8FF"> "$type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"app.bsky.graph.verification"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "handle"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"steveklabnik.com"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "subject"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:plc:3danwc67lo7obz2fmdg6jxcr"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "createdAt"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"2025-04-21T10:49:07.620Z"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "displayName"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Steve Klabnik"</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This is in <code>@bsky.app</code>’s PDS. It describes my DID, and my handle and display
name. One thing not mentioned in the blog post, but is in the comments of the
lexicon, is that changing your handle or display name makes this record invalid.
I didn’t know that! Good to remember in case I decide to make some jokes in my
display name.</p>
<p>That’s the basics: a record exists, and a blue check shows up on my account. But
wait, isn’t this centralized? I thought Bluesky was decentralized? Well, yes and
no.</p>
<p>You see, anyone can put a record of this type into their PDS. So, if I wanted to
verify someone else, I can do that too: here’s <a href="https://pdsls.dev/at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.graph.verification/3lnduurfk7q2k">me verifying
<code>@jcsalterego.bsky.social</code></a>. But if you go to Jerry’s profile, you won’t
see a blue check. Why is that? Well, because the Bluesky AppView only shows a
blue check if the record is from a “trusted verifier.” Who is that? Well, as far
as we know, it’s from <code>@bsky.app</code> and <code>@nytimes.com</code>. Why can’t we tell more?
Well, they’ve implemented this as a database column in the AppView, it’s not
“in-protocol,” in other words.</p>
<p>EDIT: Samuel has provided us with the list:</p>
<bluesky-post src="at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.feed.post/3lndyqyyr4k2k" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:p2cp5gopk7mgjegy6wadk3ep/bafkreihrdgva6ebn3vmjb4qww3yqozf4eoq26mmraouegpawhr5mo5pnxu" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Samuel</span></bdi> <span class="handle s-hik11q">@samuel.fm</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="context s-hik11q">Replying to an unknown post</p> <p class="rich-text is-large s-1lecfnd"><a target="_blank" href="https://bsky.app/profile/did:plc:z72i7hdynmk6r22z27h6tvur" class="mention s-1lecfnd">@bsky.app</a>
<a target="_blank" href="https://bsky.app/profile/did:plc:eclio37ymobqex2ncko63h4r" class="mention s-1lecfnd">@nytimes.com</a>
<a target="_blank" href="https://bsky.app/profile/did:plc:inz4fkbbp7ms3ixufw6xuvdi" class="mention s-1lecfnd">@wired.com</a>
<a target="_blank" href="https://bsky.app/profile/did:plc:b2kutgxqlltwc6lhs724cfwr" class="mention s-1lecfnd">@theathletic.bsky.social</a></p> <time datetime="2025-04-21T20:26:13.833Z" class="date s-hik11q">April 21, 2025 at 8:26 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="25 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>25</span></span> <span class="stat s-hik11q" title="10 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>10</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep/post/3lndyqyyr4k2k" target="_blank" class="permalink s-hik11q"><span>Read 1 reply on Bluesky</span></a></div></div></div> </bluesky-post>
<p>So in some sense this is a centralized feature: clients can display information
however they choose, and they’ve made a product choice that only trusted
verifiers will show up as blue checks. But in another sense, it’s not
centralized, as anyone has the power to verify anyone else. Alternative clients
can decide to show or hide any of this information; <a href="https://deer.social/">Deer Social</a>, one of these
alternate clients, has added a user preference to hide all blue
checks, for example. A different one could show every verification as a blue
check, or display them differently, say one badge per verification, styled like
the avatar of the person who verified you.</p>
<p>EDIT: turns out that the default AppView also lets you hide blue checks:</p>
<bluesky-post src="at://did:plc:35kdk2ntcs626zs6cm62i7ih/app.bsky.feed.post/3lne5sehgxc2y" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Steve Klabnik</span></bdi> <span class="handle s-12mzi62">@steveklabnik.com</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lne4frl43s2d" title="April 21, 2025 at 9:31 PM" class="date s-12mzi62"><time datetime="2025-04-21T21:31:31.924Z">Apr 21, 2025</time></a></div> <p class="rich-text is-small s-1lecfnd">Thoughts on Bluesky verification
<a target="_blank" href="https://steveklabnik.com/writing/thoughts-on-bluesky-verification/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/thou...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/thoughts-on-bluesky-verification/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Thoughts on Bluesky Verification</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div></div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:35kdk2ntcs626zs6cm62i7ih" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:35kdk2ntcs626zs6cm62i7ih/bafkreieezekf5jp4apfb6y3kfam5tvs32ktqbgtoq5waqunu2omo7tufkq" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:35kdk2ntcs626zs6cm62i7ih" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">jolheiser</span></bdi> <span class="handle s-hik11q">@jolheiser.com</span></a> </div> <p class="rich-text is-large s-1lecfnd">This was very informative, thanks for the write-up!
I was curious about being able to hide the checkmarks, but unless I'm mistaken this appears to also be an option on the bluesky appview.
At least I was able to check the preference in my settings->moderation->verification settings</p> <time datetime="2025-04-21T21:56:28.122Z" class="date s-hik11q">April 21, 2025 at 9:56 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="2 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>2</span></span> <span class="stat s-hik11q" title="0 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>0</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:35kdk2ntcs626zs6cm62i7ih/post/3lne5sehgxc2y" target="_blank" class="permalink s-hik11q"><span>Read 1 reply on Bluesky</span></a></div></div></div> </bluesky-post>
<p>The point is still the same though: there’s choice here. Back to the original post!</p>
<p>The underlying protocol is totally open, but you can make an argument that most
users will use the main client, and therefore, in practice it’s more centralized
than not. My mental model of it is the bundled list of root CAs that browsers
ship; in theory, DNS is completely decentralized, but in practice, a few root
CAs are more trusted than others. It’s sort of similar here, except there’s no
real “delegation” involved: verifiers are peers, not a tree.</p>
<p>This core design allows Bluesky to adjust the way it works in the future fairly
easily, they could allow you to decide who to trust, for example. We’ll see how
this feature evolves over time.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I’m flattered that I was chosen to be verified; if I was trying to ship this
feature, my name wouldn’t come up 181st on the list. I was really curious about
the design when they announced this feature, I assumed it was going to be closer
to a PGP web of trust, or delegated in some way. This design is much simpler and
less centralized than I expected: this is arguably more horizontal than the web
of trust would have been.</p>
<p>At the same time, I am interested in seeing if this changes the culture of the
site; some see the blue check as a status symbol of some kind, and think it
means what you say matters more. I don’t think that’s true, personally, but it
doesn’t really matter what I think. To redraw the analogy with DNS, the blue
check is very similar to the green lock: it doesn’t mean that what you’re saying
is true, or right, or good, it just means that you are who you say you are. But
even though a blue check is technically isomorphic (or close enough) to a green
lock, I think a lot of people perceive it as being more than that. We’ll just
have to see. What I am glad about it is that it’s an in-protocol design, rather
than an external one like DMs are. I understand why they did it that way, but
I’d rather that remain the exception rather than the rule.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lne4frl43s2d" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Thoughts on Bluesky verification
<a target="_blank" href="https://steveklabnik.com/writing/thoughts-on-bluesky-verification/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/thou...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/thoughts-on-bluesky-verification/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Thoughts on Bluesky Verification</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-04-21T21:31:31.924Z" class="date s-hik11q">April 21, 2025 at 9:31 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="227 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>227</span></span> <span class="stat s-hik11q" title="48 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>48</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lne4frl43s2d" target="_blank" class="permalink s-hik11q"><span>Read 24 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Thinking like a compiler: places and values in Rust</title>
<link>https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/</guid>
<description>A while back, someone on the internet asked about this syntax in Rust: * pointer_of_some_kind = blah; They wanted to know how the compiler understands this code, especially if the pointer wasn’t a reference, but a smart pointer. I wrote them a lengthy reply, but wanted to expand…</description>
<pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>A while back, someone on the internet asked about this syntax in Rust:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">*</span><span style="color:#E1E4E8">pointer_of_some_kind </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> blah;</span></span></code></pre>
<p>They wanted to know how the compiler understands this code, especially if the
pointer wasn’t a reference, but a smart pointer. I wrote them a lengthy reply,
but wanted to expand and adapt it into a blog post in case a broader audience
may be interested.</p>
<p>Now, I don’t work on the Rust compiler, and haven’t really ever, but what I do
know is language semantics. If you’re a language nerd, this post may not be
super interesting to you, other than to learn about Rust’s value categories, but
if you haven’t spent a lot of time with the finer details of programming
languages, I’m hoping that this may be a neat peek into that world.</p>
<hr/>
<p>Programming languages are themselves languages, in the same sense that human
languages are. Well, mostly, anyway. The point is, to understand some Rust code
like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">*</span><span style="color:#E1E4E8">pointer_of_some_kind </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> blah;</span></span></code></pre>
<p>We can apply similar tools to how we might understand some “English code” like
this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>You can't judge a book by its cover.</span></span></code></pre>
<p>Oh, you also might ask yourself as you’re reading the next sections, “why so
many steps?” The short answer is a classic one: by breaking down the big problem
of “what does this mean?” into smaller steps, each step is easier. Doing everything
at once is way more difficult than a larger number of smaller steps. I’m going
to cover the classical ways compilers work, there’s a ton of variety when you
start getting into more modern ways of doing things, and often these steps are
blended together, or done out of order, all kinds of other things. Handling
errors is a huge topic in and of itself! Consider this a starting point, not an
ending one.</p>
<p>Let’s get into it.</p>
<h2 id="lexical-analysis-aka-scanning-or-tokenizing">Lexical Analysis (aka ‘scanning’ or ‘tokenizing’)</h2>
<p>The first thing we want to do is to try and figure out if these words are even
valid words at all. With computer languages, this process is called “lexical
analysis,” though you’ll also hear the term “tokenizing” to describe it. In other
words, we’re not interested in any sort of meaning at all at this stage, we just
want to figure out what we’re even talking about.</p>
<p>So if we look at this English sentence:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>You can't judge a book by its cover.</span></span></code></pre>
<p>We follow a two step process in order to tokenize it: we first “scan” it to
produce a sequence of “lexemes.” We do this by following some rules. I’m not
going to give you a sample of the rules for English here, as this post is
already long enough. But you might end up with something like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>You</span></span>
<span class="line"><span>can't</span></span>
<span class="line"><span>judge</span></span>
<span class="line"><span>a</span></span>
<span class="line"><span>book</span></span>
<span class="line"><span>by</span></span>
<span class="line"><span>its</span></span>
<span class="line"><span>cover</span></span>
<span class="line"><span>.</span></span></code></pre>
<p>Note how we do have <code>'</code> in <code>can't</code>, but <code>.</code> is separate from <code>cover</code>. These
are the kinds of rules we’d be following: the <code>'</code> is because this is a contraction,
but the <code>.</code> is not really part of <code>cover</code>, but its own thing.</p>
<p>We then run a second step and evaluate each individual string of characters, turning
them into “tokens.” A token is some sort of data type in your compiler, probably, so
for example we could do this in Rust:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">enum</span><span style="color:#B392F0"> Token</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">String</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Punctuation</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">String</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>And so the output of our tokenizer might be an array of something like</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#E1E4E8">[</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"You"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"can't"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"judge"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"a"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"book"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"by"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"its"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"cover"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Punctuation</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"."</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#E1E4E8">]</span></span></code></pre>
<p>At this point, we know we have something semi-coherent, but we’re still
not sure it’s valid yet. On to the next step!</p>
<h2 id="syntactic-analysis-aka-parsing">Syntactic Analysis (aka ‘parsing’)</h2>
<p>Funny enough, this is an area where human-language linguistics and compilers
mean things that are slightly different. With human language, parsing is often
combined with our next step, which is semantic analysis. But we (most of the
time) try to separate syntax and semantic analysis in compilers.</p>
<p>Again, I’m going to massively simplify with the English. We’re going to use
these rules for what a sentence is:</p>
<ol>
<li>A sentence is a sequence of words.</li>
<li>A sentence has a “subject,” which is the first word, and must be capitalized.</li>
<li>A sentence ends with a period.</li>
</ol>
<p>Obviously this is a tiny subset of English, but you get the idea. The goal of
syntactic analysis is to turn our sequence of tokens into a richer data
structure that’s easier to work with. In other words, we’ve figured out that
our sentence is made up of valid sequences of characters, but do they fit the
grammatical rules of our language? Note that we also don’t need to store everything;
for example, maybe our English data structure looks like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Sentence</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> subject</span><span style="color:#F97583">:</span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">String</span><span style="color:#E1E4E8">>,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Where’s the period? How do we know <code>subject</code> must be capitalized? That’s the job
of syntactic analysis. Since every sentence ends with a period, we don’t need to
track it in our data structure: the analysis makes sure that it’s true, and then
we aren’t doing it again. Likewise, we don’t need to store our subject as a
capitalized string if we don’t want to: we determined that the input was, but we
can transform it as needed. So our value after syntax analysis might look like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#B392F0">Sentence</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> subject</span><span style="color:#F97583">:</span><span style="color:#9ECBFF"> "you"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> [</span><span style="color:#9ECBFF">"can't"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"judge"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"a"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"book"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"by"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"its"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"cover"</span><span style="color:#E1E4E8">],</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Often, for computer languages, a tree-like structure works well, and so you’d
see an “abstract syntax tree,” or “AST” at the end of this stage. But it’s not
strictly speaking required, whatever data structure makes sense for you can
work.</p>
<p>Now that we have a richer data structure, we’re almost done. Now we have to get
into meaning.</p>
<h2 id="semantic-analysis-aka-wtf-does-this-mean">Semantic Analysis (aka ‘wtf does this mean’)</h2>
<p>Imagine our sentence wasn’t “You can’t judge a book by its cover.” but instead
this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor</span></span>
<span class="line"><span>incididunt ut labore et dolore magna aliqua.</span></span></code></pre>
<p>This is a famous bit of text that’s incoherent. The words are all Latin words,
and it <em>feels</em> like it might be a sentence, but it’s nonsense. We could parse
this into a <code>Sentence</code>:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#B392F0">Sentence</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> subject</span><span style="color:#F97583">:</span><span style="color:#9ECBFF"> "Lorem"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> [</span><span style="color:#9ECBFF">"ipsum"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"dolor"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"sit"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"amet"</span><span style="color:#E1E4E8">, </span><span style="color:#6A737D">/* and continued */</span><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>But it’s not valid. How do we determine <em>that</em>?</p>
<p>Well, in the context of English, “Lorem” isn’t a valid English word. So if we
were to check that the subject is a valid word, we’d successfully reject this
sentence. In computer languages, we’d do similar things like type checking:
<code>5 + "hello"</code> might lex and parse just fine, but when we try and figure out
what it means, we learn it’s nonsense. Except if your language lets you add
numbers and strings!</p>
<p>After semantic analysis, we’ve determined that our program is good, aka “well
formed.” In a compiler, we’d then go on to generate machine code or byte code
to represent our program. But that stuff, while incredibly interesting, isn’t
what we’re talking about here: remember our original objective? It was to
understand this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">*</span><span style="color:#E1E4E8">pointer_of_some_kind </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> blah;</span></span></code></pre>
<p>That’s semantics. So now that we have some background, let’s talk about how
to understand this code.</p>
<h2 id="so-how-do-i-understand-this-code">So… how do I understand this code?</h2>
<p>Well, to understand the code, we first need to understand how it lexes and
parses. In other words, what the <em>grammar</em> of our language is. How our language
would lex, tokenize, and then parse our code. In this case, it’s Rust. Rust’s
grammar is large and complex, so we’ll only be talking about part of it today.
We’re going to focus on statements vs expressions.</p>
<p>You may have heard that Rust is an “expression based language” before. Well,
this is what people mean. You see, when it comes to most of the things you say
in a program, they’re often one of these two things. Expressions are things that
produce some sort of value, and statements are used to sequence the evaluation
of expressions. That’s a bit abstract, so let’s get concrete.</p>
<h3 id="statements">Statements</h3>
<p>Rust has a few kinds of statements: first, there’s “declaration statements”
and “expression statements,” and each have their own sub-kinds as well.</p>
<p>Declaration statements have two kinds: <em>item declarations</em>, and <em>let statements</em>.
Item declarations are things like <code>mod</code> or <code>struct</code> or <code>fn</code>: they declare certain
things exist. <code>let</code> statements are probably the most famous form of statement in
Rust, they look like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span> OuterAttribute* let PatternNoTopAlt ( : Type )? (= Expression † ( else BlockExpression) ? ) ? ;</span></span></code></pre>
<p>That’s… a mouthful. We haven’t talked about <code>*</code> or <code>?</code> yet, and we don’t really
want to cover some of the more exotic parts of Rust right now. So we’re going
to talk about this via a simpler grammar first:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>let Variable = Expression;</span></span></code></pre>
<p>This is how we create new variables in Rust: we say <code>let</code>, and then a name, an <code>=</code>,
and then finally some expression. The result of evaluating that expression becomes
the value of the variable.</p>
<p>This is leaving out a lot: the name isn’t just a name, it’s a pattern, which is
very cool. <code>let else</code> exists in Rust now, and that’s cool. We’re ignoring types
here. But you can get the basics with just this simple version.</p>
<p>Expression statements are much simpler:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>ExpressionWithoutBlock ; | ExpressionWithBlock ;?</span></span></code></pre>
<p>The <code>|</code> there is an or, so we can either have a single expression followed by a <code>;</code>,
or a block (denoted by <code>{}</code>s, which can optionally (the ? means it can exist or not)
be followed by a <code>;</code>.)</p>
<p>So to think like a compiler, you can start to figure out how to combine these rules.
For example:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> x </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> 5</span><span style="color:#F97583"> +</span><span style="color:#79B8FF"> 6</span></span>
<span class="line"><span style="color:#E1E4E8">};</span></span></code></pre>
<p>Here, we have a let statement, but the expression on the right hand side of the <code>=</code>
is an <code>ExpressionWithBlock</code>. Here’s a pop quiz for you: is the <code>;</code> part of the let
expression, or part of the expression on the right hand side?</p>
<p>The answer is, it’s part of the <code>let</code>. The let expression has a mandatory <code>;</code>, but
the block does not, and so:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>let x = ExpressionWithBlock;</span></span></code></pre>
<p>If we had the semicolon with the block, we’d still need the one for the let, and
so we’d have <code>};;</code>. Which the compiler accepts, but it warns about it.</p>
<p>Going back to our original code:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">*</span><span style="color:#E1E4E8">pointer_of_some_kind </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> blah;</span></span></code></pre>
<p>We don’t have a <code>let</code>, and this isn’t an item declaration: this is an expression
statement. We have a <code>ExpressionWithoutBlock</code>, followed by a <code>;</code>. So now we have
to talk about expressions.</p>
<h3 id="expressions">Expressions</h3>
<p>There are a <em>lot</em> of expression types in Rust. <a href="https://doc.rust-lang.org/stable/reference/expressions.html">Section 8.2 of the
Reference</a> has 19 sub-sections. Whew! In this case, this code is an
Operation Expression, and more specifically, an <a href="https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#assignment-expressions">Assignment Expression</a>:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>Expression = Expression</span></span></code></pre>
<p>Easy enough! So the left hand side of the <code>=</code> is an expression with <code>*pointer_of_some_kind</code>, and the right hand side is <code>blah</code>. Easy enough!</p>
<p>But these two expressions are in some ways, the entire reason that I wrote this
post. We just finally got here! You see, the reference has this to say about
assignment expressions:</p>
<blockquote>
<p>An assignment expression moves a value into a specified place.</p>
</blockquote>
<p>What are those?</p>
<h4 id="places-and-values">Places and Values</h4>
<p>C, and older versions of C++, called these two things “lvalue” and “rvalue,”
for “left” and “right”, like the sides of the <code>=</code>. More recent C++ standards
have more categories. Rust splits the difference: it only has two categories,
like C, but they map more cleanly to two of C++‘s categories. Rust calls
lvalues, the left hand side, a “place,” and rvalues, the right hand side,
a “value.” Here’s two more precise definitions, from the Unsafe Code Guidelines:</p>
<ul>
<li>A <a href="https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#place">place</a> is basically a pointer, but might contain more information
such as size or alignment. A place has a type, indicating the type of values
that it stores.</li>
<li>A <a href="https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#value">value</a> A value is what gets stored in a place. A value has a type.</li>
</ul>
<p>Both of these have expression forms, so a <em>place expression</em> produces a place
when it’s evaluated, and a <em>value expression</em> produces a value. And that’s
how <code>=</code> works: we have a place expression on the left, a value expression
on the right, and we put that value in that place. Easy enough!</p>
<p>Once again, the code we were trying to figure out:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">*</span><span style="color:#E1E4E8">pointer_of_some_kind </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> blah;</span></span></code></pre>
<p>So <code>*</code>, the dereference operator, takes the pointer, and evaluates to the place
where it points: its address. And <code>blah</code> gives us the value to put there.</p>
<p>Case closed? Not quite!</p>
<h3 id="deref">Deref</h3>
<p>Rust has a trait, <code>Deref</code>, that lets us override the <code>*</code> operator. So let’s
talk about this example, to make things easier:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">use</span><span style="color:#B392F0"> std</span><span style="color:#F97583">::</span><span style="color:#B392F0">ops</span><span style="color:#F97583">::</span><span style="color:#E1E4E8">{</span><span style="color:#B392F0">Deref</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">DerefMut</span><span style="color:#E1E4E8">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> DerefMutExample</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#E1E4E8"> value</span><span style="color:#F97583">:</span><span style="color:#B392F0"> T</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> </span><span style="color:#B392F0">Deref</span><span style="color:#F97583"> for</span><span style="color:#B392F0"> DerefMutExample</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#F97583"> type</span><span style="color:#B392F0"> Target</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> T</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> deref</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#F97583"> &</span><span style="color:#79B8FF">Self</span><span style="color:#F97583">::</span><span style="color:#B392F0">Target</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> &</span><span style="color:#79B8FF">self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">value</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> </span><span style="color:#B392F0">DerefMut</span><span style="color:#F97583"> for</span><span style="color:#B392F0"> DerefMutExample</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> deref_mut</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#F97583"> &mut</span><span style="color:#79B8FF"> Self</span><span style="color:#F97583">::</span><span style="color:#B392F0">Target</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> &mut</span><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">value</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#E1E4E8"> main () {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#F97583"> mut</span><span style="color:#E1E4E8"> x </span><span style="color:#F97583">=</span><span style="color:#B392F0"> DerefMutExample</span><span style="color:#E1E4E8"> { value</span><span style="color:#F97583">:</span><span style="color:#9ECBFF"> 'a'</span><span style="color:#E1E4E8"> };</span></span>
<span class="line"><span style="color:#F97583"> *</span><span style="color:#E1E4E8">x </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> 'b'</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0"> assert_eq!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'b'</span><span style="color:#E1E4E8">, x</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">value);</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>You can play with it <a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0d6c7f3d8a2c00bc79dabef47ab550e2">here</a>.</p>
<p>We didn’t talk about the kind of expression that’s relevant here yet: a <em>path
expression.</em></p>
<blockquote>
<p>Path expressions that resolve to local or static variables are place
expressions, other paths are value expressions.</p>
</blockquote>
<p>We talked about <code>let</code> earlier: in <code>let mut x = DerefMutExample { value: 'a' };</code>
above, the <code>x</code> is a path expression, and since it’s resolving to our new variable,
that means it’s a place expression. The <code>DerefMutExample { value: 'a' }</code> is a
value expression, because it doesn’t resolve to a variable.</p>
<p>Let’s talk about <code>*x = 'b';</code>, remember our assignment expression:</p>
<p>expression = expression;</p>
<p>And what it does: it moves a value into a place.</p>
<p>To understand how <code>*</code> works, we only need to add one more thing: the Dereference
expression. It’s produced by the deference operator, <code>*</code>. It looks like this:</p>
<p>*expression</p>
<p>Its semantics are pretty straightforward:</p>
<ul>
<li>If the expression has the type <code>&T</code>, <code>&mut T</code>, <code>*const T</code>, or <code>*mut T</code>, then
this evaluates to the place of the value being pointed to, and gives it the
same mutability.</li>
<li>If the expression is not one of those types, then it is equivalent to either
<code>*std::ops::Deref::deref(&x)</code> if it’s immutable or
<code>*std::ops::DerefMut::deref_mut(&mut x)</code></li>
</ul>
<p>That’s it. Now we have enough to fully understand <code>*x = 'b'</code>:</p>
<ul>
<li><code>'b'</code> is a value expression</li>
<li><code>*x</code> is not a pointer type, so we expand it to
<code>*std::ops::DerefMut::deref_mut(&mut x)</code> and try again</li>
<li><code>std::ops::DerefMut::deref_mut(&mut x)</code> returns the type <code>&mut char</code> in this
case, and it is pointing at the place of <code>self.value</code> (which I’m gonna call
<code><that place></code> for short), which currently has the value <code>'a'</code> stored in it. Now
we have <code>*&mut <that place></code>.</li>
<li>We now use the other rule of the dereference operator, we’re operating on a
<code>&mut T</code>, so <code>*&mut <that place></code> refers to <code><that place></code>.</li>
<li>We now have <code><that place> = 'b'</code>, and so we move that <code>'b'</code> into that place.</li>
</ul>
<p>Whew! That’s that.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Thinking like a compiler can be fun! Once you’ve mastered the idea of grammars
and gotten used to substituting things, you can figure out all sorts of
interesting stuff. In this specific case, sometimes people wonder why <code>Deref</code>
returns a reference if the whole goal is to show where something should point…
and if you didn’t know that dereference expressions expanded to something with
<code>*</code> in it, it would be confusing! But now you know. And hopefully learned some
interesting things about values, places, and how compilers think about code
along the way.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lmanw5qdik2o" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Thinking like a compiler: places and values in <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a>
<a target="_blank" href="https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/thin...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Thinking like a compiler: places and values in Rust</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-04-07T19:09:04.578Z" class="date s-hik11q">April 7, 2025 at 7:09 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="85 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>85</span></span> <span class="stat s-hik11q" title="11 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>11</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lmanw5qdik2o" target="_blank" class="permalink s-hik11q"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Does unsafe undermine Rust's guarantees?</title>
<link>https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/</guid>
<description>When people first hear about unsafe in Rust, they often have questions. A very normal thing to ask is, “wait a minute, doesn’t this defeat the purpose?” And while it’s a perfectly reasonable question, the answer is both straightforward and has a lot of nuance. So let’s talk about…</description>
<pubDate>Mon, 17 Mar 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>When people first hear about <code>unsafe</code> in Rust, they often have questions. A very
normal thing to ask is, “wait a minute, doesn’t this defeat the purpose?” And while
it’s a perfectly reasonable question, the answer is both straightforward and has a
lot of nuance. So let’s talk about it.</p>
<p>(The straightforward answer is “no”, by the way.)</p>
<h2 id="memory-safe-languages-and-their-implementations">Memory safe languages and their implementations</h2>
<p>The first way to think about this sort of thing is to remember that programming
languages and their implementations are two different things. This is sometimes
difficult to remember, since many programming languages only have one
implementation, but this general principle applies even in those cases. That is,
there are the desired semantics of the language you are implementing, and then
the codebase that makes programs in that language do those things.</p>
<p>Let’s examine a “real” programming language to talk about this: <a href="https://en.wikipedia.org/wiki/Brainfuck">Brainfuck</a>.
Brainfuck programs are made up of eight different characters, each one performing
one operation. Brainfuck programs can only do those eight things, and nothing
more.</p>
<p>However, this simplicity leads to two interesting observations about the
differences between languages in the abstract and their implementations.</p>
<h3 id="unsafety-in-implementation">Unsafety in implementation</h3>
<p>The first is that properties of the language itself are distinct from properties
of an implementation of the language. The semantics of the Brainfuck language
don’t provide an opportunity to call some sort of re-usable function
abstraction, for example. Yet, when we write a Brainfuck interpreter or
compiler, we can use a programming language that supports functions. That
doesn’t change that Brainfuck programs themselves can’t define or call
functions.</p>
<p>And in fact, we often want to implement a language in another language that has
more abilities than the language we’re implementing. For example, while it’s
possible to implement Brainfuck in Brainfuck, it’s much easier to do so in a
language that lets you define and call functions. Another classic example is
interacting with hardware. Brainfuck has the <code>.</code> instruction that allows you to
produce output. But in order to actually produce output to say, a terminal, you
interact with your platform’s API to do so. But inside of that API, eventually
you’ll hit a layer where there are no abstractions: you need to talk to hardware
directly. Doing so is not statically able to be guaranteed to be memory safe,
because the hardware/software interface often boils down to “put some
information at this arbitrary memory location and the hardware will take it from
there.” Our Brainfuck program is operating in an environment just like any
other; at the end of the day it has to coordinate with the underlying operating
system or hardware at some point. But just because our implementation must do
so, doesn’t mean that our Brainfuck programs do so directly. The semantics of
programs written in Brainfuck aren’t changed by the fact that the implementation
can (and must) do things that are outside of those semantics.</p>
<p>Let’s take Ruby as a more realistic example than Brainfuck. Ruby does not let us modify
arbitrary memory addresses from within Ruby itself. This means that pure Ruby
programs should not be able to produce a segmentation fault. But in the real
world, Ruby programs can segfault. This is possible if there’s a bug in the Ruby
interpreter’s code. Sure, it’s our Ruby code that’s making the call to a
function that results in a segfault, but the real fault lies with the code
outside of Ruby’s purview.</p>
<p>That this can happen doesn’t mean that Ruby’s guarantees around memory
manipulation is somehow useless, or suffers segmentation faults at the same rate
as programs in a language that allow you to manipulate arbitrary memory as a
matter of course. But it does also mean that we don’t need to look at our entire
program to figure out where the problem comes from: it instead comes from our
non-Ruby code. Ruby’s guarantees have helped us eliminate a lot of suspects when
figuring out whodunit.</p>
<h3 id="extending-languages-and-unsafety">Extending languages and unsafety</h3>
<p>The second property is that certain implementations may extend the language in
arbitrary ways. For example, I could write a Brainfuck interpreter and say that
I support a ninth instruction, <code>@</code>, that terminates the program when invoked.
This would be a non-portable extension, and Brainfuck programs written in my
variant wouldn’t work in other interpreters, but sometimes, this technique is
useful.</p>
<p>Many languages find that these sorts of extensions are useful, and so offer a
feature called a “foreign function interface” that allows for your program to
invoke code in a different language. This provides the ability to do things that
are outside of the domain of the language itself, which can be very useful.
Brainfuck does not offer an FFI, but if it did, you could imagine that <code>@</code> could
be implemented in terms of it, making programs that use <code>@</code> portable again, as
long as the extended functionality was included somehow, often as a library of
some kind.</p>
<p>Just like our implementation has the ability to do things outside of our
languages’ semantics, FFI and similar extension mechanisms also give us the
ability to do arbitrary things. I can write an extension for Ruby that writes to
arbitrary memory. And I can cause a segfault. But we’re in the same place that
we were with our implementation issues; we know that if we get a segfault, the
blame lies not with our Ruby code, but instead, with either the implementation
or our FFI.</p>
<h3 id="so-how-is-this-memory-safe">So how is this memory safe?</h3>
<p>It may seem contradictory that we can call a language “memory safe” if
real-world programs have the ability to cause memory problems. But the thing is,
it isn’t <em>really</em> programs in that language that caused the issue: it was FFI,
which is in a different language entirely, or it was implementation issues, and
implementations must do memory unsafe things, thanks to a need to interact with
the OS or hardware. And so the definition of “memory safe language” is commonly
understood to refer to languages and their implementations in the absence of
either implementation bugs or FFI bugs. In practice, these bugs occur at such a
low rate compared to languages that are clearly not memory safe that this
practical definition serves a good purpose, even if you may feel a bit
uncomfortable with these “exceptions.”</p>
<p>But there’s actually a deeper reason why these exceptions are acceptable, and
that’s due to how we understand properties of programs and programming languages
in the first place. That is, this isn’t just a practical “well these exceptions
seem fine” sort of thing, they’re actually okay on a deeper level.</p>
<h2 id="programs-properties-and-proofs">Programs, properties, and proofs</h2>
<p>So how do we know anything about how programs and programming languages work at
all?</p>
<blockquote>
<p>Computer science is no more about computers than astronomy is about
telescopes.</p>
<ul>
<li>Edsger Wybe Dijkstra</li>
</ul>
</blockquote>
<p>A fun thing about computer science is that it’s closely related to other
disciplines, including math! And so there’s a long history of using math
techniques to understand computers and their programs.</p>
<p>One technique of building knowledge is the idea of proofs. If you get a
degree in computer science, you’ll engage with proofs quite a lot. I even
took a class on logic in the philosophy department as part of my degree.</p>
<p>I don’t intend to give you a full introduction to the idea of proofs in this
blog post, but there’s some high-level concepts that are useful to make sure
we’re in the same page about before we go forward.</p>
<h3 id="aristotelian-syllogisms">Aristotelian syllogisms</h3>
<p>Here is a very classic example of a form of reasoning called a “syllogism”,
given by Aristotle in 350 BCE:</p>
<ol>
<li>All men are mortal.</li>
<li>Socrates is a man.</li>
<li>Therefore, Socrates is mortal.</li>
</ol>
<p>These first two lines are called “propositions,” and the third is a conclusion.
We can base our conclusion on a logical relationship between the information given
to us by the propositions.</p>
<p>But how do we know propositions are true? Are all men mortal? What’s important
here is that we, for the purposes of our proof, assume that propositions are
true. And we do this because, on some level, we cannot know everything. And so
to begin, we have to start somewhere, and make some assumptions about what we
know. It’s true that later, we may discover a fact that disproves our
proposition, and now our proof no longer works. But that’s just the way that
the world works. It doesn’t prevent these sorts of proofs from being useful to
help us gain knowledge about the world and how it works, as best as we can tell
at the current time.</p>
<p>So on some level, this is also why Ruby is a memory safe language even though a
C extension can segfault: there’s always some kind of things that we have to
assume are true. Memory safe languages are ones where the amount of code we have
to assume is memory safe, rather than is guaranteed to be by the semantics, is
small, and preferably indicated in the code itself. Put another way, the amount
of code we need to trust is memory safe is large in a memory unsafe language,
and small in a memory safe language, rather than zero.</p>
<h3 id="logic-and-programs">Logic and programs</h3>
<p>As time went on, so did our understanding of logic. And, of course, we even have
competing logics! And then this gets fun, because terms can mean something
slightly different. For example, in more recent logics, we’d call something like
“All men are mortal” to be an <em>axiom</em>, rather than a proposition. Same idea:
it’s something that we accept without proof.</p>
<p>As computers appeared, people sought to apply the rules for mathematical logic
onto them. We even call circuits that carry out classic logical operations
“logic gates.” Number theory and logic were foundational to making computers
work. And so, once high level languages appeared on the scene, there was
interest in applying these mathematical tools to understanding programs as well.
This discipline is called “formal verification,” and the general idea is to
describe various properties we wish a system to have, and then use formal
methods from mathematics to demonstrate that this is true.</p>
<p>This area of study is very deep, and I don’t plan to cover the vast majority of
it here. However, I do want to pursue one particular thread in this area.</p>
<h3 id="hoare-logic">Hoare Logic</h3>
<p><a href="https://dl.acm.org/doi/10.1145/363235.363259">“Hoare Logic”</a> is, well:</p>
<blockquote>
<p>In this paper an attempt is made to explore the logical foundations of
computer programming by use of techniques which were first applied in the
study of geometry and have later been extended to other branches of
mathematics. This involves the elucidation of sets of axioms and rules of
inference which can be used in proofs of the properties of computer programs.
Examples are given of such axioms and rules, and a formal proof of a simple
theorem is displayed. Finally, it is argued that important advantage, both
theoretical and practical, may follow from a pursuance of these topics.</p>
</blockquote>
<p>Incidentally, C. A. R. Hoare, the author of this paper, and Graydon Hoare, the
creator of Rust, are unrelated.</p>
<p>How does Hoare logic work? Yet again, not going to cover all of it, but the
general idea is this: In order to figure out if a program does what it is
supposed to do, we need to be able to reason about the state of the program
after execution happens. And so we need to be able to describe the state before,
and its relationship to the state after. And so you get this notation:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>P { Q } R</span></span></code></pre>
<p>P is a precondition, Q is a program, and R is the result of the program
executing with those preconditions.</p>
<p>But most programs have more than one statement. So how could we model this?
Hoare gives us the Rule of Composition:</p>
<p>If <code>P { Q1 } R1</code> and <code>R1 { Q2 } R</code> then <code>P { Q1; Q2 } R</code>.</p>
<p>This allows us to build up a program by proving each statement in turn.</p>
<p>Hoare logic is very neat, and I’ve only scratched the surface here. People did a
lot of work to extend Hoare logic to include more and more aspects of programs.
But then, something else happened.</p>
<h3 id="separation-logic">Separation Logic</h3>
<p>In 2002, <a href="https://www.cs.cmu.edu/~jcr/seplogic.pdf">Separation Logic: A Logic for Shared Mutable Data Structures</a>
was published.</p>
<blockquote>
<p>In joint work with Peter O’Hearn and others, based on early ideas of Burstall,
we have developed an extension of Hoare logic that permits reasoning about
low-level imperative programs that use shared mutable data structure.</p>
</blockquote>
<p>Hmm, shared mutable data structures? Where have I heard that before…</p>
<p>Let’s see what they have to say:</p>
<blockquote>
<p>The problem faced by these approaches is that the correctness of a program
that mutates data structures usually depends upon complex restrictions on the
sharing in these structures.</p>
</blockquote>
<p>For sure. Well, what are we to do about this?</p>
<blockquote>
<p>The key to avoiding this difficulty is to introduce a novel
logical operation <code>P * Q</code>, called separating conjunction (or
sometimes independent or spatial conjunction), that asserts
that P and Q hold for disjoint portions of the addressable
storage.</p>
</blockquote>
<p>What disjoint portions of addressable storage might we care about?</p>
<blockquote>
<p>Our intent is to capture the low-level character of machine language. One can
think of the store as describing the contents of registers, and the heap as
describing the contents of an addressable memory.</p>
</blockquote>
<p>Pretty useful!</p>
<p>Before we talk a bit about how separation logic works, consider this paragraph
on why it’s named as such:</p>
<blockquote>
<p>Since these logics are based on the idea that the structure of an assertion
can describe the separation of storage into disjoint components,we have come
to use the term separation logics, both for the extension of predicate
calculus with the separation operators and the resulting extension of Hoare
logic. A more precise name might be storage separation logics, since it is
becoming apparent that the underlying idea can be generalized to describe the
separation of other kinds of resources.</p>
</blockquote>
<p>The plot thickens.</p>
<p>Anyway, in Separation Logic, we use slightly different notation than Hoare Logic:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>{ P } C { Q }</span></span></code></pre>
<p>This says that we start with the precondition <code>P</code>, and if the program <code>C</code>
executes, it will not have undefined behavior, and if it terminates, <code>Q</code> will
hold.</p>
<p>Furthermore, there is the “frame rule”, which I am going to butcher the notation
for because I haven’t bothered to install something to render math correctly
just for this post:</p>
<p>If <code>{ p } c { q }</code></p>
<p>then <code>{ p * r } c { q * r }</code></p>
<p>where no free variable in <code>r</code> is modified by <code>c</code>.</p>
<p>Why is it interesting to add something to both sides of an equation, in a sense?
Well what this gives us the ability to do is add any predicates about parts of
the program that <code>c</code> doesn’t modify or mutate. You might think of <code>&mut T</code> or
even just ownership in general: we can reason about just these individual parts
of a program, separately from the rest of it. In other words, we have some
foundational ideas for ownership and even bits of borrowing, and while this original
paper doesn’t involve concurrency, eventually Concurrent Separation Logic would become
a thing as well.</p>
<p>I think the paper explains why the frame rule matters better than I can:</p>
<blockquote>
<p>Every valid specification <code>{p} c {q}</code> is “tight” in the sense that every cell
in its footprint must either be allocated by <code>c</code> or asserted to be active by
<code>p</code>; “locality” is the opposite property that everything asserted to be active
belongs to the footprint. The role of the frame rule is to infer from a local
specification of a command the more global specification appropriate to the
larger footprint of an enclosing command.</p>
</blockquote>
<p>What this gives us is something called “local reasoning,” and local reasoning
is <em>awesome</em>. Before I talk about that, I want to leave you with one other
very interesting paragraph:</p>
<blockquote>
<p>Since our logic permits programs to use unrestricted address arithmetic,there
is little hope of constructing any general-purpose garbage collector. On the
other hand, the situation for the older logic, in which addresses are disjoint
from integers, is more hopeful. However, it is clear that this logic permits
one to make assertions, such as “The heap contains two elements” that might be
falsified by the execution of a garbage collector, even though, in any
realistic sense, such an execution is unobservable.</p>
</blockquote>
<p>Anyway. Let’s talk about local vs global analysis.</p>
<h2 id="global-vs-local-analysis">Global vs local analysis</h2>
<p>Proving things about programs isn’t easy. But one thing that can make it even
harder is that, for many properties of many programs you’d want to analyze, you
need to do a <em>global analysis</em>. As an example, let’s use Ruby. Ruby is an
incredibly dynamic programming language, which makes it fairly resistant to
static analysis.</p>
<h3 id="global-analysis">Global analysis</h3>
<p>Here is a Ruby program. Do you know if this program executes successfully?</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#F97583">class</span><span style="color:#B392F0"> Foo</span></span>
<span class="line"><span style="color:#F97583"> def</span><span style="color:#B392F0"> self.bar</span></span>
<span class="line"><span style="color:#9ECBFF"> "baz"</span></span>
<span class="line"><span style="color:#F97583"> end</span></span>
<span class="line"><span style="color:#F97583">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF">p</span><span style="color:#79B8FF"> Foo</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">bar</span></span></code></pre>
<p>Yeah, it prints <code>baz</code>. But what about this Ruby program?</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#F97583">class</span><span style="color:#B392F0"> Foo</span></span>
<span class="line"><span style="color:#F97583"> def</span><span style="color:#B392F0"> self.bar</span></span>
<span class="line"><span style="color:#9ECBFF"> "baz"</span></span>
<span class="line"><span style="color:#F97583"> end</span></span>
<span class="line"><span style="color:#F97583">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">require</span><span style="color:#9ECBFF"> "foo_ext.rb"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF">p</span><span style="color:#79B8FF"> Foo</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">bar</span></span></code></pre>
<p>We can’t know. <code>foo_ext.rb</code> may contain no relevant code, but it also might
include something like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#79B8FF">Foo</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">class_eval</span><span style="color:#E1E4E8"> { </span><span style="color:#F97583">undef</span><span style="color:#79B8FF"> :bar</span><span style="color:#E1E4E8"> }</span></span></code></pre>
<p>In which case, when we try and call <code>Foo.bar</code>, it no longer exists:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span><anonymous>': eval:13:in `block in <main>': undefined method `bar' for class `Foo' (NameError)</span></span>
<span class="line"><span>eval:13:in `class_eval'</span></span>
<span class="line"><span>eval:13:in `<main>'</span></span>
<span class="line"><span>-e:in `eval'</span></span></code></pre>
<p>Ouch. So in this case, we’re going to need the whole code of our program in
order to be able to figure out what’s going on here.</p>
<p>Incidentally, <a href="https://sorbet.org/">Sorbet</a> is a very cool project to add type checking to
Ruby. They do require access to the entire Ruby source code in order to do their
analysis. But they also made some decisions to help make it more tractable; if
you try Sorbet out on the web, the type checker is <em>fast</em>. What happens when you
try the above code with Sorbet?</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>editor.rb:5: Unsupported method: undef https://srb.help/3008</span></span>
<span class="line"><span> 5 |Foo.class_eval { undef :bar }</span></span>
<span class="line"><span> ^^^^^^^^^^</span></span></code></pre>
<p>This is a very fair tradeoff! It’s very common with various forms of analysis to
choose certain restrictions in order to make what they want to do tractable. Ruby
has so much evil in it, just not supporting some of the more obscure things is
completely fair, in my opinion.</p>
<h3 id="local-analysis">Local analysis</h3>
<p>The opposite of global analysis is, well, local analysis. Let’s consider a Rust
equivalent of our Ruby:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> bar</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">-></span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0"> String</span><span style="color:#F97583">::</span><span style="color:#B392F0">from</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"baz"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0"> dbg!</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">Foo</span><span style="color:#F97583">::</span><span style="color:#B392F0">bar</span><span style="color:#E1E4E8">());</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Can we know if this program works? Sure, everything is here. Now, if we tried the
same trick as the Ruby code, we know that it would work, because Rust doesn’t have
the ability to remove the definition of <code>bar</code> like Ruby does. So let’s try something
else:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> bar</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">-></span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#6A737D"> // body hidden</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0"> dbg!</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">Foo</span><span style="color:#F97583">::</span><span style="color:#B392F0">bar</span><span style="color:#E1E4E8">());</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Can we know if <code>main</code> is properly typed here? We can, even though we know
nothing about the body. That’s because <code>dbg!</code> takes any value that implements
the <code>Debug</code> trait, and we know that <code>Foo::bar()</code> returns <code>String</code>, which
implements <code>Debug</code>. Typechecking <code>main</code> is local to main’s implementation, we
don’t need to look into the bodies of any function it calls to determine if it’s
well-typed. We only need to know their signatures. If we didn’t require <code>bar</code> to
have a type signature, we’d have to peek into its body to figure out what it
returns, and so on for any function that <code>bar</code> calls in its body. Instead of
this arbitrarily deep process, we can just look at the signature and be done
with it.</p>
<h2 id="composability-of-proofs">Composability of proofs</h2>
<p>So why is local analysis so helpful? The first reason is either speed or
scalability, depending on how you want to look at it. If you are running global
analysis checks, they will get more expensive the larger your codebase, since
they will require checking the entire thing to work. Whereas local analysis only
requires a local context, it doesn’t get any more expensive when you add more
code to your project, only when you change that local context. So when you’re
trying to scale checks up to larger projects, local analysis is crucial.</p>
<p>But I also like to personally think about it as a sort of abstraction. That is,
global analysis is a leaky abstraction: changes in one part of the codebase can
cascade into other parts. Remember this line about the frame rule?</p>
<blockquote>
<p>The role of the frame rule is to infer from a local specification of a command
the more global specification appropriate to the larger footprint of an
enclosing command.</p>
</blockquote>
<p>If we have local reasoning, we can be sure that changes locally don’t break out
of the boundaries of those changes. As long as the types of our function don’t
change, we can mess with the body as much as we want, and we know that the rest
of the analysis of the program is still intact. This is really useful, in the
same way that abstractions are useful when building programs more generally.</p>
<h2 id="rustbelt">Rustbelt</h2>
<p>Okay, we have taken a real deep dive here. What’s this all have to do with
unsafe Rust?</p>
<p>Well, seven years ago, <a href="https://plv.mpi-sws.org/rustbelt/popl18/paper.pdf">RustBelt: Securing the Foundations of the Rust
Programming Language</a> by Ralf Jung, Jaques-Henri Jourdan, Robbert
Krebbers, and Derek Dreyer, was published:</p>
<blockquote>
<p>In this paper, we give the first formal (and machine-checked) safety proof for
a language representing a realistic subset of Rust.</p>
</blockquote>
<p>But it’s more exciting than that:</p>
<blockquote>
<p>Our proof is extensible in the sense that, for each new Rust library that uses
unsafe features, we can say what verification condition it must satisfy in
order for it to be deemed a safe extension to the language.</p>
</blockquote>
<p>Very good!</p>
<p>The paper begins by talking about why verifying Rust is such a challenge:</p>
<blockquote>
<p>Consequently, to overcome this restriction, the implementations of Rust’s
standard libraries make widespread use of unsafe operations, such as “raw
pointer” manipulations for which aliasing is not tracked. The developers of
these libraries claim that their uses of unsafe code have been properly
“encapsulated”, meaning that if programmers make use of the APIs exported by
these libraries but otherwise avoid the use of unsafe operations themselves,
then their programs should never exhibit any unsafe/undefined behaviors. In
effect, these libraries extend the expressive power of Rust’s type system by
loosening its ownership discipline on aliased mutable state in a modular,
controlled fashion</p>
</blockquote>
<p>This is basically the question asked at the start of this post: can you honestly
say that a function is safe if it contains <code>unsafe</code> code as part of its
implementation?</p>
<p>They go on to describe the first challenge for the project: choosing the correct
logic to model Rust in (some information cut for clarity):</p>
<blockquote>
<p>Iris is a language-generic framework for higher-order concurrent separation
logic which in the past year has been equipped with tactical support for
conducting machine-checked proofs of programs in Coq. By virtue of being a
separation logic, Iris comes with built-in support for reasoning modularly
about ownership. Moreover, the main selling point of Iris is its support for
deriving custom program logics for different domains using only a small set of
primitive mechanisms. In the case of RustBelt, we used Iris to derive a novel
lifetime logic, whose primary feature is a notion of borrow propositions that
mirrors the “borrowing” mechanism for tracking aliasing in Rust.</p>
</blockquote>
<p>Separation logic!</p>
<p>So how does this work? There are three parts to the proof. I’m going to
summarize them because I’m already quoting extensively, please go read the paper
if you want the exact details, it is well written.</p>
<ol>
<li>Verify that the typing rules are sound when interpreted semantically.</li>
<li>Verify that if a program is well typed semantically, it does not exhibit
unsafe behavior.</li>
<li>For any library that uses unsafe, check that it satisfies the semantics that
its interface requires.</li>
</ol>
<p>If all three of these things are true, then the program is safe to execute. This
is also exciting because, thanks to 3, when someone writes new unsafe code, they
can get RustBelt to let them know which properties they need to satisfy to make
sure that their unsafe code doesn’t cause problems.</p>
<h3 id="weaknesses">Weaknesses</h3>
<p>Of course, there is more to do following up on RustBelt:</p>
<blockquote>
<p>We do not model (1) more relaxed forms of atomic accesses, which Rust uses for
efficiency in libraries like Arc; (2) Rust’s trait objects (comparable to
interfaces in Java), which can pose safety issues due to their interactions
with lifetimes; or (3) stack unwinding when a panic occurs, which causes
issues similar to exception safety in C++.</p>
</blockquote>
<p>However, the results were good enough to find some soundness bugs in the
standard library, and if I remember correctly, widen one of the APIs in a sound
manner as well. Another interesting thing about RustBelt is that it was
published before Rust gained Non-lexical lifetimes, however, it modeled them,
since folks knew they were coming down the pipeline.</p>
<h2 id="the-theoretical-vs-the-empirical">The theoretical vs the empirical</h2>
<p>So, we have some amount of actual proof that unsafe code in Rust doesn’t
undermine Rust’s guarantees: what it does is allow us to extend Rust’s
semantics, just like FFI would allow us to extend a program written in
a language that supports it. As long as our unsafe code is semantically
correct, then we’re all good.</p>
<p>… but what if it’s not semantically correct?</p>
<h3 id="when-things-go-wrong">When things go wrong</h3>
<p>At some point maybe I’ll write a post about the specifics here, but this post is
already incredibly long. I’ll cut to the chase: you can get undefined behavior,
which means anything can happen. You don’t have a real Rust program. It’s bad.</p>
<p>But at least it’s scoped to some degree. However, many people get this scope a
bit wrong. They’ll say something like:</p>
<blockquote>
<p>You only need to verify the unsafe blocks.</p>
</blockquote>
<p>This is true, but also a bit misleading. Before we get into the unsafe stuff, I
want you to consider an example. Is this Rust code okay?</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> bar</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> set_bar</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">, bar</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">bar </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> bar;</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>No unsafe shenanigans here. This code is perfectly safe, if a bit useless.</p>
<p>Let’s talk about unsafe. The canonical example of unsafe code being affected
outside of unsafe itself is the implementation of <code>Vec<T></code>. Vecs look something
like this (the real code is different for reasons that don’t really matter in
this context):</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#E1E4E8"> ptr</span><span style="color:#F97583">:</span><span style="color:#F97583"> *mut</span><span style="color:#B392F0"> T</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> len</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> cap</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>The pointer is to a bunch of <code>T</code>s in a row, the length is the current number of <code>T</code>s
that are valid, and the capacity is the total number of <code>T</code>s. The length and the
capacity are different so that memory allocation is amortized; the capacity is
always greater than or equal to the length.</p>
<p>That property is very important! If the length is greater than the capacity,
when we try and index into the Vec, we’d be accessing random memory.</p>
<p>So now, this function, which is the same as <code>Foo::set_bar</code>, is no longer okay:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">impl</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> </span><span style="color:#B392F0">Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> set_len</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">, len</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">len </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> len;</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This is because the unsafe code inside of other methods of <code>Vec<T></code> need to be
able to rely on the fact that <code>len <= capacity</code>. And so you’ll find that
<code>Vec<T>::set_len</code> in Rust is marked as unsafe, even though it doesn’t contain
unsafe code.</p>
<p>And this is why the module being the privacy boundary matters: the only way to
set len directly in safe Rust code is code within the same privacy boundary as
the <code>Vec<T></code> itself. And so, that’s the same module, or its children.</p>
<p>This is still ultimately better than any line of code in the whole codebase, but
it’s not quite as small as you may think at first.</p>
<h3 id="how-can-we-check-that-things-havent-gone-wrong">How can we check that things haven’t gone wrong?</h3>
<p>Okay, so while RustBelt could give you some idea if your code is correct, I doubt
you’re about to jump into Coq and write some proofs. What can you do? Well,
Rust provides a tool, <code>miri</code>, that can interpret your Rust code and let you know
if you’ve violated some unsafe rules. But it’s not complete, that is, miri can
tell you if your code is wrong, but it cannot tell you if your code is right. It’s
still quite useful.</p>
<p>To get miri, you can install it with Rust nightly:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>$ rustup +nightly component add miri</span></span></code></pre>
<p>And then run your test suite under miri with</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>$ cargo +nightly miri test</span></span></code></pre>
<p>For more details, consult <a href="https://github.com/rust-lang/miri">miri’s documentation</a>.</p>
<h3 id="do-things-go-wrong">Do things go wrong?</h3>
<p>Do people write good unsafe code, or bad?</p>
<p>Well, the obvious answer to the above question is “yes.” Anyone who says all
unsafe code in Rust is sound is obviously lying. But the absolute isn’t what’s
interesting here, it’s the shade of grey. Is unsafe pervasive? Does Rust have
memory related bugs at the same rate as other MSLs, or closer to memory-unsafe
languages?</p>
<p>It’s still sort of early days here, but we do have some preliminary results that
are helpful. In 2022, <a href="https://security.googleblog.com/2022/12/memory-safe-languages-in-android-13.html">Google reported</a> that:</p>
<blockquote>
<p>To date, there have been zero memory safety vulnerabilities discovered in
Android’s Rust code.</p>
</blockquote>
<p>I am very interested to see a more up to date report about how that’s changed,
but as they say:</p>
<blockquote>
<p>We don’t expect that number to stay zero forever, but given the volume of new
Rust code across two Android releases, and the security-sensitive components
where it’s being used, it’s a significant result. It demonstrates that Rust is
fulfilling its intended purpose of preventing Android’s most common source of
vulnerabilities. Historical vulnerability density is greater than 1/kLOC (1
vulnerability per thousand lines of code) in many of Android’s C/C++ components
(e.g. media, Bluetooth, NFC, etc). Based on this historical vulnerability
density, it’s likely that using Rust has already prevented hundreds of
vulnerabilities from reaching production.</p>
</blockquote>
<p>We’ll see if these results replicate themselves, in the future, and also outside
of Google. But it at least looks like in practice, most unsafe code has not led
to problems. At least so far!</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lkqjlfxypk2d" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Does unsafe undermine <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> 's guarantees? <a target="_blank" href="https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/does...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Does unsafe undermine Rust's guarantees?</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-03-19T15:43:41.710Z" class="date s-hik11q">March 19, 2025 at 3:43 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="138 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>138</span></span> <span class="stat s-hik11q" title="41 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>41</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lkqjlfxypk2d" target="_blank" class="permalink s-hik11q"><span>Read 17 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>Choosing Languages</title>
<link>https://steveklabnik.com/writing/choosing-languages/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/choosing-languages/</guid>
<description>I tried to write this post yesterday, but I didn’t like my approach there. Last night, I had a conversation with one of my best friends about it, and he encouraged me to write this, but in a different way. I think this way is better, so thanks, dude. The other day, Microsoft…</description>
<pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>I tried to write this post yesterday, but I didn’t like my approach there. Last
night, I had a conversation with one of my best friends about it, and he
encouraged me to write this, but in a different way. I think this way is better,
so thanks, dude.</p>
<hr/>
<p>The other day, Microsoft announced <a href="https://devblogs.microsoft.com/typescript/typescript-native-port/">that they are re-writing the TypeScript
compiler in Go</a>. A lot of people had a lot of feelings about this. I did
too, but in a different way than many other people.</p>
<p>The biggest questions people had were “Why not C#?” and “Why not Rust?”. To
be clear, I do think that asking why someone chooses a programming language
can be valuable; as professionals, we need to make these sorts of decisions
on a regular basis, and seeing how others make those decisions can be useful,
to get an idea of how other people think about these things.</p>
<p>But it can also… I dunno. Sometimes, I think people have little imagination
when it comes to choosing a particular technology stack.</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lk6p4clry22p" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Here’s my opinion on the new typescript compiler:
You should write programs in the language you want to.</p> <time datetime="2025-03-12T13:34:42.008Z" class="date s-hik11q">March 12, 2025 at 1:34 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="385 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>385</span></span> <span class="stat s-hik11q" title="43 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>43</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lk6p4clry22p" target="_blank" class="permalink s-hik11q"><span>Read 22 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>It is true that sometimes, there are hard requirements that means that choosing
a particular technology is inappropriate. But I’ve found that often, these
sorts of requirements are often more <em>contextually</em> requirements than ones that
would be the case in the abstract. For example, <a href="https://en.wikipedia.org/wiki/Lisp_machine">you can write an OS in
Lisp</a> if you want. But in practice, you’re probably not working with
this hardware, or willing to port the environment to bare metal.</p>
<p>But it’s also the case that you often don’t know what others’ contextual
situation actually is. I would have never guessed the main reason that Microsoft
chose Go for, which is roughly “we are porting the existing codebase, rather
than re-writing it, and our existing code looks kinda like Go, making it easy to
port.” That’s a very good reason! I hadn’t really thought about the differences
between porting and re-writing in this light before, and it makes perfect sense
to me.</p>
<p>At the same time,</p>
<bluesky-post src="at://did:plc:4eukmtg5kmyjmp6qw3xkpite/app.bsky.feed.post/3lk6t4a4xa22w" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:4eukmtg5kmyjmp6qw3xkpite/bafkreie5tfrdul6a3oyl34g6ughguakxf6gxc7ua6c2n4ysuggx4cak6ya" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Jake Bailey</span></bdi> <span class="handle s-12mzi62">@jakebailey.dev</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite/post/3lk6t47wxt22w" title="March 12, 2025 at 2:46 PM" class="date s-12mzi62"><time datetime="2025-03-12T14:46:14.196Z">Mar 12, 2025</time></a></div> <p class="context s-12mzi62">Replying to <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" dir="auto" class="s-12mzi62">Steve Klabnik</a></p> <p class="rich-text is-small s-1lecfnd">Funnily I'm pretty sure I'm the only team member who could actually claim their favorite language is Go, but I tried so hard to prove rust out early on. It sure wasn't my decision either, Go just kept working the best as the hacking continued</p> </div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:4eukmtg5kmyjmp6qw3xkpite/bafkreie5tfrdul6a3oyl34g6ughguakxf6gxc7ua6c2n4ysuggx4cak6ya" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Jake Bailey</span></bdi> <span class="handle s-hik11q">@jakebailey.dev</span></a> </div> <p class="rich-text is-large s-1lecfnd">I think it's going well, though, besides the terrifying infohazard that is the "Why Go?" GitHub discussion thread, which contains some of the weirdest and most frustrating takes I've ever seen</p> <time datetime="2025-03-12T14:46:14.197Z" class="date s-hik11q">March 12, 2025 at 2:46 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="37 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>37</span></span> <span class="stat s-hik11q" title="1 repost"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>1</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite/post/3lk6t4a4xa22w" target="_blank" class="permalink s-hik11q"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>people are going <em>way</em> overboard here.</p>
<bluesky-post src="at://did:plc:x2p6i7jmdhk4xj2ej3rflp7a/app.bsky.feed.post/3lk6was5n5227" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:4eukmtg5kmyjmp6qw3xkpite/bafkreie5tfrdul6a3oyl34g6ughguakxf6gxc7ua6c2n4ysuggx4cak6ya" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Jake Bailey</span></bdi> <span class="handle s-12mzi62">@jakebailey.dev</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite/post/3lk6t4a4xa22w" title="March 12, 2025 at 2:46 PM" class="date s-12mzi62"><time datetime="2025-03-12T14:46:14.197Z">Mar 12, 2025</time></a></div> <p class="context s-12mzi62">Replying to <a target="_blank" href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" dir="auto" class="s-12mzi62">Jake Bailey</a></p> <p class="rich-text is-small s-1lecfnd">I think it's going well, though, besides the terrifying infohazard that is the "Why Go?" GitHub discussion thread, which contains some of the weirdest and most frustrating takes I've ever seen</p> </div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:x2p6i7jmdhk4xj2ej3rflp7a" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:x2p6i7jmdhk4xj2ej3rflp7a/bafkreifxfhmm3eggyodwvoqxsh7iawqkpfhuh6opfac3e27jfzlnx7dikm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:x2p6i7jmdhk4xj2ej3rflp7a" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Michael Pratt</span></bdi> <span class="handle s-hik11q">@prattmic.com</span></a> </div> <p class="rich-text is-large s-1lecfnd">I peeked briefly yesterday (and quickly ran away). Just took another look. 370 comments in 24 hours?? Wow. The proposal to add generics to Go (mildly contentious, to put it lightly) only got ~450 comments over a few months.</p> <time datetime="2025-03-12T15:42:28.732Z" class="date s-hik11q">March 12, 2025 at 3:42 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="11 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>11</span></span> <span class="stat s-hik11q" title="0 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>0</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:x2p6i7jmdhk4xj2ej3rflp7a/post/3lk6was5n5227" target="_blank" class="permalink s-hik11q"><span>Read 1 reply on Bluesky</span></a></div></div></div> </bluesky-post>
<p>There’s some sort of balance here to be struck. But at the same time, I hate
“choose the right tool for the job” as a suggestion. Other than art projects,
has anyone ever deliberately chosen the wrong tool? Often, something that
seems like the wrong tool is chosen simply because the contextual requirements
weren’t obvious to you, the interlocutor.</p>
<p>I think there’s two other reasons why this situation is making me feel
complicated things. The first is RIIR, and the second is a situation from a long
time ago that changed my life.</p>
<p>Many of you have probably heard of the “re-write it in Rust” meme. The idea is
that there’s this plague of programmers who are always suggesting that every
single thing needs to be re-written in Rust. While I am sure this happens from
time to time, I have yet to see real evidence that it is actually widespread. I
almost even wrote a blog post about this, trying to actually quantify the effect
here. But on some level, perception is reality, and if people believe it’s true,
it’s real on some level, even if that’s not actually the case. Regardless of the
truth of the number of people who suggest this, on every thread about Rust,
people end up complaining about this effect, and so it has an effect on the
overall discourse regardless. And while I don’t believe that this effect is real
overall, it absolutely has happened in this specific situation. A bunch of
people have been very aggressively suggesting that this should have been in
Rust, and not Go, and I get where they’re coming from, but also: really? Go is
an absolutely fine choice here. It’s fine. Why do you all have to reinforce a
stereotype? It’s frustrating.</p>
<p>The second one is something that’s been on my mind for over a decade, but I’ve
only started talking about it in places other than conversations with close
friends. Long-time readers may remember <a href="/writing/node/">that one time where I was an
asshole</a>. I mean, I’ve been an asshole more than once in my life, but
this one was probably the time that affected me the most. The overall shape of
the situation is this:</p>
<ol>
<li>Someone wrote <code>grep</code>, but in Node.</li>
<li>I made fun of it on the internet, because why would someone write a CLI tool in Node?</li>
<li>The author found out that I did and felt bad about it.</li>
</ol>
<p>Looking back at my apology, I don’t think it’s necessarily the best one. It was
better than the first draft or two of what I wrote, but I’ve gotten better at
apologizing since then. Regardless, this incident changed the trajectory of my
life, and if you happen to read this, Heather, I am truly, unequivocally, sorry.</p>
<p>There was a couple of things that happened here. The first is just simply, in
2013, I did not understand that the things I said had meaning. I hate talking
about this because it makes me seem more important than I am, but it’s also
important to acknowledge. I saw myself at the time as just Steve, some random
guy. If I say something on the internet, it’s like I’m talking to a friend in
real life, my words are just random words and I’m human and whatever. It is what
it is.</p>
<p>But at that time in my life, that wasn’t actually the case. I was on the Rails
team, I was speaking at conferences, and people were reading my blog and tweets.
I was an “influencer,” for better or worse. But I hadn’t really internalized
that change in my life yet. And so I didn’t really understand that if I
criticized something, it was something thousands of people would see. To me, I’m
just Steve. This situation happened before we talked about “cancel culture” and
all of that kind of stuff, but when the mob came for me, I realized: they were
right, actually. I was being an asshole, and I should not have been. And I
resolved myself to not being an asshole like that ever again.</p>
<p>And to do that, I had to think long and hard about why I was like that. I love
programming languages! I love people writing programs that they find
interesting! I don’t think that it’s stupid to write a tool in a language that
I wouldn’t expect it to be written in! So why did I feel the reason to make fun
of this in that situation?</p>
<p>The answer? A pretty classic situation: the people I was hanging out with were
affecting me, and in ways that, when I examined it, I didn’t like very much.</p>
<p>You see, in the Rails world at the time, there was a huge <a href="https://blog.aurynn.com/2015/12/16-contempt-culture">contempt
culture</a> at the time, especially around JavaScript and Node. And, when
you’re around people who talk shit all the time, you find yourself talking shit
too. And once I realized that, well, I wanted to stop it. But there was a big
problem: my entire professional, and most of my social, life was built around
Ruby and Rails. So if I wanted to escape that culture… what should I do?</p>
<p>If you clicked on the link to my apology above, you probably didn’t make note of
the date: Jan 23 2013. Something had just happened to me, in December of 2012: I
had decided to check out this little programming language I had heard of called
Rust.</p>
<p>One of the things that struck me about the “Rust community” at the time, which
was like forty people in an IRC room, was how nice they were. When I had trouble
getting Hello World to compile, and I typed <code>/join #rust</code>, I fully expected an
RTFM and flames. But instead, I was welcomed with open arms. People were chill.
And it was really nice.</p>
<p>And so, a month and a day later, I had this realization about the direction my
life was heading, and how I wasn’t really happy about it. And I thought about
how much I enjoyed the Rust stuff so far… and combined with a few other
factors, maybe of which I’ll write about someday, this is when I decided that I
wanted to make Rust a thing, and dedicated myself to achieving that.</p>
<p>And part of that was being conscious about the culture, and how it developed,
and the ways that it would evolve over time. I didn’t want Rust to end up the
way that Rails had ended up. And that meant a few things, but first of all,
truly internalizing that I had to change. And understanding that I had to be
intentional with what I did and said. And what that led to, among other things,
was a “we talk about Rust on its own merits, not by trash talking other languages”
culture. (That’s also partially because of Nietzsche, but I’m already pretty far
afield of the topic for this post, maybe someday.)</p>
<p>Anyway, the Eternal September comes for us all. The “Rust Community,” if it ever
were a coherent concept, doesn’t really make sense any more. And it is by no
means perfect. I have a lot of criticism about how things turned out. But I do
think that it’s not a coincidence that “Rust makes you trans,” for example, is
a meme. I am deeply proud of what we all built.</p>
<p>So yeah, anyway: people choose programming languages for projects based on a
variety of reasons. And sure, there may be better or worse choices. But you
probably have a different context than someone else, and so when someone
makes a choice you don’t agree with, there’s no reason to be a jerk about it.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lkblqoytls22" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Choosing Languages <a target="_blank" href="https://steveklabnik.com/writing/choosing-languages/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/choo...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/choosing-languages/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Choosing Languages</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-03-13T17:12:30.356Z" class="date s-hik11q">March 13, 2025 at 5:12 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="304 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>304</span></span> <span class="stat s-hik11q" title="91 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>91</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lkblqoytls22" target="_blank" class="permalink s-hik11q"><span>Read 29 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>A Happy Day for Rust</title>
<link>https://steveklabnik.com/writing/a-happy-day-for-rust/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/a-happy-day-for-rust/</guid>
<description>A while back, I wrote A sad day for Rust about the Actix-web unsafe controversy. The short of it was, Actix-web was using some unsound unsafe code. When this was discovered, people responded pretty harshly, and the maintainer quit. Others picked up the work, but it still wasn’t…</description>
<pubDate>Wed, 05 Mar 2025 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>A while back, I wrote <a href="/writing/a-sad-day-for-rust/">A sad day for Rust</a> about the Actix-web unsafe
controversy. The short of it was, Actix-web was using some unsound unsafe code.
When this was discovered, people responded pretty harshly, and the maintainer
quit. Others picked up the work, but it still wasn’t great:</p>
<blockquote>
<p>I’m not sure where we go from here, and I’m not sure what we could have done
to prevent this from happening. But I feel like this is a failure, and it’s set
Rust back a bit, and I’m just plain sad.</p>
</blockquote>
<p>While the Rust community surely isn’t perfect, today I’d like to share with you
a different story: one where there was some controversy, but it was handled in
the complete opposite way.</p>
<p><a href="https://rustup.rs/">Rustup</a> is the official installer and toolchain manager for Rust. It
has a much smaller team than Rust itself does, and releases on a much slower
schedule.</p>
<p>Back in August of last year, it was decided to <a href="https://github.com/rust-lang/rustup/pull/3985">change some behavior</a>.
The details aren’t super important, what matters is that it’s a breaking change
to a critical component of the ecosystem. I’m not here to say if this change is
good or bad; they had good motivations, there’s also good reasons to keep the
previous behavior, it’s one of those classic “you can’t make everyone happy”
sort of deals.</p>
<p>The team knew that they’d need to let people know that this had happened:</p>
<blockquote>
<p>Yes, you’ve understood it correctly, and this breakage is intentional: #3635 (comment)</p>
<p>Before the actual release, we’ll definitely clearly communicate this change with
the community, and we’ll use the beta phase to collect user feedback.</p>
</blockquote>
<p>And they did <a href="https://internals.rust-lang.org/t/seeking-beta-testers-for-rustup-v1-28-0/22060">make a post on internals</a> about the change, seeking
feedback. This also landed in a This Week in Rust, in my understanding.</p>
<p>The problem is, not everyone reads internals, and not everyone reads This Week
in Rust. And so yesterday, some folks woke up to a broken CI, leading to <a href="https://github.com/rust-lang/rustup/issues/4211">this
bug report</a> among other upset posts elsewhere on the internet.</p>
<p>I have been on the other side of this before. It’s frustrating when you try and
communicate something, and people don’t hear about it, and then they find out
later and get mad at you about it. I also remember the Actix debacle, and many
other controversies in the broader open source ecosystem.</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3ljiqodr7q22f" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">at one point i questioned my desire to help people get into open source
image unrelated</p> <div class="embeds s-azdpbr"><div class="image-embed is-bordered is-aligned s-t3k6fc"><div class="single-item tl tr bl br is-standalone s-t3k6fc" style="aspect-ratio: 1024/766"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreignn6nf4mlkc2yfpj3y4dwo5j3dyuod3gbcw4rolpcsca6ujbbo5y" alt="a tweet by @primawesome
My neighbor told me coyotes keep eating his outdoor cats so I asked how many cats he has and he said he just goes to the shelter and gets a new cat afterwards so I said it sounds like he's just feeding shelter cats to coyotes and then his daughter started crying" class="image s-t3k6fc"/> <div class="placeholder s-t3k6fc"></div></div></div></div> <time datetime="2025-03-03T20:04:06.711Z" class="date s-hik11q">March 3, 2025 at 8:04 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="1,156 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>1,156</span></span> <span class="stat s-hik11q" title="86 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>86</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3ljiqodr7q22f" target="_blank" class="permalink s-hik11q"><span>Read 13 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>However, this time, things went differently. As you can see, the issue is very
respectful, and the comments are also generally respectful. The team responded
<a href="https://github.com/rust-lang/rustup/issues/4211#issuecomment-2695620307">graciously</a>, and decided to put in the work to <a href="https://blog.rust-lang.org/2025/03/04/Rustup-1.28.1.html">release a new
version</a> to restore the previous behavior.</p>
<p>I was kind of worried yesterday that this would end up being another Actix
situation. The Rust community has changed a lot over time, and while it’s still
pretty good overall, sometimes things can go poorly, just like with any random
group of thousands of people on the internet. But today, I’m grateful that
feedback was given constructively, it was taken with the spirit that it was
intended, and everyone should end up happy.</p>
<p>If only every controversy went this way, maybe I wouldn’t be wondering about
coyotes.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3ljnctebzec25" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">A happy day for <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> <a target="_blank" href="https://steveklabnik.com/writing/a-happy-day-for-rust/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/a-ha...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/a-happy-day-for-rust/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreibb62uzqrzuu7nzx4c2s46anoc6fztalrgpm5qucnoofgdizmwtbu" alt="" class="thumbnail s-rtbqd8"/> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">A Happy Day for Rust</p> <p class="description s-rtbqd8">I’m not sure where we go from here, and I’m not sure what we could have done to prevent this from happening. But I feel like this is a failure, and it’s set Rust back a bit, and I’m just plain sad.</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-03-05T15:39:41.341Z" class="date s-hik11q">March 5, 2025 at 3:39 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="185 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>185</span></span> <span class="stat s-hik11q" title="23 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>23</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3ljnctebzec25" target="_blank" class="permalink s-hik11q"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
<item>
<title>When should I use String vs &str?</title>
<link>https://steveklabnik.com/writing/when-should-i-use-string-vs-str/</link>
<guid isPermaLink="true">https://steveklabnik.com/writing/when-should-i-use-string-vs-str/</guid>
<description>Rust has two main string types: String and &str . Sometimes, people argue that these two types make Rust code difficult to write, because you have to think about which one you should be using in a given situation. My experience of writing Rust is that I don’t really think about…</description>
<pubDate>Wed, 16 Oct 2024 00:00:00 GMT</pubDate>
<content:encoded><![CDATA[<p>Rust has two main string types: <code>String</code> and <code>&str</code>. Sometimes, people argue
that these two types make Rust code difficult to write, because you have to
think about which one you should be using in a given situation. My experience
of writing Rust is that I don’t really think about this very much, and this
post is about some rules of thumb that you can use to be like me.</p>
<h2 id="level-1-dont-think-about-it-at-all">Level 1: Don’t think about it at all</h2>
<p>The very first thing you can do is follow the simplest rule:</p>
<blockquote>
<p>Always use <code>String</code>, never use <code>&str</code>.</p>
</blockquote>
<p>That looks like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Person</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> name</span><span style="color:#F97583">:</span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> first_word</span><span style="color:#E1E4E8">(words</span><span style="color:#F97583">:</span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">split_whitespace</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">next</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"words should not be empty"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">to_string</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This style means you may need to add <code>.to_string()</code> or <code>.clone()</code>
for things to work sometimes:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> sentence </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> "Hello, world!"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(sentence</span><span style="color:#F97583">.</span><span style="color:#B392F0">to_string</span><span style="color:#E1E4E8">()));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> owned </span><span style="color:#F97583">=</span><span style="color:#B392F0"> String</span><span style="color:#F97583">::</span><span style="color:#B392F0">from</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"A string"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> // if we don't clone here, we can't use owned the second time</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(owned</span><span style="color:#F97583">.</span><span style="color:#B392F0">clone</span><span style="color:#E1E4E8">()));</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(owned));</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>But that’s okay, the compiler will let you know when you need to:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>error[E0382]: use of moved value: `owned`</span></span>
<span class="line"><span> --> src/main.rs:21:31</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>18 | let owned = String::from("A string");</span></span>
<span class="line"><span> | ----- move occurs because `owned` has type `String`, which does not implement the `Copy` trait</span></span>
<span class="line"><span>19 |</span></span>
<span class="line"><span>20 | println!("{}", first_word(owned));</span></span>
<span class="line"><span> | ----- value moved here</span></span>
<span class="line"><span>21 | println!("{}", first_word(owned));</span></span>
<span class="line"><span> | ^^^^^ value used here after move</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>note: consider changing this parameter type in function `first_word` to borrow instead if owning the value isn't necessary</span></span>
<span class="line"><span> --> src/main.rs:5:22</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>5 | fn first_word(words: String) -> String {</span></span>
<span class="line"><span> | ---------- ^^^^^^ this parameter takes ownership of the value</span></span>
<span class="line"><span> | |</span></span>
<span class="line"><span> | in this function</span></span>
<span class="line"><span>help: consider cloning the value if the performance cost is acceptable</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>20 | println!("{}", first_word(owned.clone()));</span></span>
<span class="line"><span> | ++++++++</span></span></code></pre>
<p>Hey, compiler, that’s a great idea. Let’s move on to level 2.</p>
<h2 id="level-2-prefer-str-for-function-parameters">Level 2: prefer <code>&str</code> for function parameters</h2>
<p>A rule that’s a little better is this one:</p>
<blockquote>
<p>Always use <code>String</code> in structs, and for functions, use <code>&str</code> for parameters
and <code>String</code> types for return values.</p>
</blockquote>
<p>This is what the compiler error for level 1 was suggesting we do instead of <code>.clone</code>.</p>
<p>That results in code that looks like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Person</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> name</span><span style="color:#F97583">:</span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> first_word</span><span style="color:#E1E4E8">(words</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">split_whitespace</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">next</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"words should not be empty"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">to_string</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> sentence </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> "Hello, world!"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(sentence));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> owned </span><span style="color:#F97583">=</span><span style="color:#B392F0"> String</span><span style="color:#F97583">::</span><span style="color:#B392F0">from</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"A string"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>We’re now doing much less copying. We do need to add a <code>&</code> on
to <code>String</code> values that we wish to pass into <code>first_word</code>, but
that’s not too bad, the compiler will help us when we forget:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>error[E0308]: mismatched types</span></span>
<span class="line"><span> --> src/main.rs:20:31</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>20 | println!("{}", first_word(owned));</span></span>
<span class="line"><span> | ---------- ^^^^^ expected `&str`, found `String`</span></span>
<span class="line"><span> | |</span></span>
<span class="line"><span> | arguments to this function are incorrect</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>note: function defined here</span></span>
<span class="line"><span> --> src/main.rs:5:4</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>5 | fn first_word(words: &str) -> String {</span></span>
<span class="line"><span> | ^^^^^^^^^^ -----------</span></span>
<span class="line"><span>help: consider borrowing here</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>20 | println!("{}", first_word(&owned));</span></span>
<span class="line"><span> | +</span></span></code></pre>
<p>Following this rule will get you through 95% of situations successfully. Yes,
that number was found via the very scientific process of “I made it up, but it
feels correct after writing Rust for the last twelve years.”</p>
<p>For 4% of that last 5%, we can go to level 3:</p>
<h2 id="level-3-return-str-sometimes">Level 3: return <code>&str</code> sometimes</h2>
<p>Here’s a slightly more advanced rule for certain circumstances:</p>
<blockquote>
<p>Always use <code>String</code> in structs, and for functions, use <code>&str</code> for parameters.
If the return type of your function is derived from an argument and isn’t
mutated by the body, return <code>&str</code>. If you run into any trouble here, return
<code>String</code> instead.</p>
</blockquote>
<p>That would look like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Person</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> name</span><span style="color:#F97583">:</span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// we're returning a substring of words, so &str is appropriate</span></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> first_word</span><span style="color:#E1E4E8">(words</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">split_whitespace</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">next</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"words should not be empty"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> sentence </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> "Hello, world!"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(sentence));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> owned </span><span style="color:#F97583">=</span><span style="color:#B392F0"> String</span><span style="color:#F97583">::</span><span style="color:#B392F0">from</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"A string"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This lets us remove a copy, we no longer have a <code>.to_string</code> in the body of
<code>first_word</code>.</p>
<p>Sometimes, we can’t do that though:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// because we are going to uppercase the first word, our return type can't</span></span>
<span class="line"><span style="color:#6A737D">// be &str anymore, because we aren't actually returning a substring: we are</span></span>
<span class="line"><span style="color:#6A737D">// creating our own new string.</span></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> first_word_uppercase</span><span style="color:#E1E4E8">(words</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">split_whitespace</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">next</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"words should not be empty"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">to_uppercase</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> sentence </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> "Hello, world!"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word_uppercase</span><span style="color:#E1E4E8">(sentence));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> owned </span><span style="color:#F97583">=</span><span style="color:#B392F0"> String</span><span style="color:#F97583">::</span><span style="color:#B392F0">from</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"A string"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word_uppercase</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word_uppercase</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>How do you know that this is the case? Well, in this specific case,
<code>to_uppercase</code> already returns a <code>String</code>. So that’s a great hint.
If we tried to return a <code>&str</code>, we’d get an error:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// this can't work</span></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> first_word_uppercase</span><span style="color:#E1E4E8">(words</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> &</span><span style="color:#E1E4E8">words</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">split_whitespace</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">next</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"words should not be empty"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">to_uppercase</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>would give us</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>error[E0515]: cannot return reference to temporary value</span></span>
<span class="line"><span> --> src/main.rs:7:5</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>7 | &words</span></span>
<span class="line"><span> | ______^-</span></span>
<span class="line"><span> | | ______|</span></span>
<span class="line"><span>8 | || .split_whitespace()</span></span>
<span class="line"><span>9 | || .next()</span></span>
<span class="line"><span>10 | || .expect("words should not be empty")</span></span>
<span class="line"><span>11 | || .to_uppercase()</span></span>
<span class="line"><span> | || ^</span></span>
<span class="line"><span> | ||_______________________|</span></span>
<span class="line"><span> | |_______________________returns a reference to data owned by the current function</span></span>
<span class="line"><span> | temporary value created here</span></span></code></pre>
<p>And that’s really it. Following this rule will get you through virtually every
scenario where you need to wonder about <code>String</code> and <code>&str</code>. With some practice,
you’ll internalize these rules, and when you feel comfortable with a level, you
can go up to the next one.</p>
<p>What about that last 1% though? Well, there is a next level…</p>
<h2 id="level-4-when-to-use-str-in-a-struct">Level 4: When to use <code>&str</code> in a struct</h2>
<p>Here’s the rule for level 4:</p>
<blockquote>
<p>Should you use a <code>&str</code> in a struct? If you’re asking that question, use
<code>String</code>. When you need to use <code>&str</code> in a struct, you’ll know.</p>
</blockquote>
<p>Storing references in structs is useful, for sure, and it’s good that Rust
supports it. But you’re only going to need it in fairly specific scenarios,
and if you feel like you’re worrying about <code>String</code> vs <code>&str</code>, you’re just
not in the position to be worrying about the complexity of storing a <code>&str</code>
in a struct yet.</p>
<p>In fact, some people believe in this rule so strongly that they’re working
on a language where storing references in structs isn’t even possible, and
it’s a language I’ve found very interesting lately:
<a href="https://www.hylo-lang.org/">Hylo</a>. They go a bit farther than that, even:
in Hylo, you think of everything as being values, rather than having references
at all. They think that you can write meaningful programs with this model.
But this isn’t a post about Hylo. I’ll write one of those eventually. My point
is just that you can really, truly, get away with not storing <code>&str</code>s in structs
for a <em>lot</em> of useful Rust programs. So it’s not really worth spending mental
energy on, until you determine that you must do so. That is the case when
you’ve profiled your program and determined that copying strings to and from
your struct is a big enough issue to bother with lifetimes.</p>
<p>I hope you’ve found this helpful, and if you have any other rules of thumb
like this, I’d love to hear about them!</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3l6nkx54sij2w" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">When should you use String vs &str?
Some <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> rules of thumb
<a target="_blank" href="https://steveklabnik.com/writing/when-should-i-use-string-vs-str/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/when...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/when-should-i-use-string-vs-str/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">When should I use String vs &str?</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2024-10-16T18:27:49.871Z" class="date s-hik11q">October 16, 2024 at 6:27 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="287 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>287</span></span> <span class="stat s-hik11q" title="43 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>43</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3l6nkx54sij2w" target="_blank" class="permalink s-hik11q"><span>Read 30 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded>
</item>
</channel>
</rss>
Raw text
<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Steve Klabnik</title><description>Steve Klabnik's blog</description><link>https://steveklabnik.com/</link><ttl>15</ttl><item><title>Arguing about arguments</title><link>https://steveklabnik.com/writing/arguing-about-arguments/</link><guid isPermaLink="true">https://steveklabnik.com/writing/arguing-about-arguments/</guid><description>Here’s some Rust code: // define a function fn foo (x : i32 , y : i32 ) -> i32 { // body elided } // call it let z = foo ( 5 , 6 ); In programming language jargon, we call x and y parameters and 5 and 6 arguments . Rust does not have many fancy features related to parameters and…</description><pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Here’s some Rust code:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// define a function</span></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(x</span><span style="color:#F97583">:</span><span style="color:#B392F0"> i32</span><span style="color:#E1E4E8">, y</span><span style="color:#F97583">:</span><span style="color:#B392F0"> i32</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> i32</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// call it</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> z </span><span style="color:#F97583">=</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">6</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>In programming language jargon, we call <code>x</code> and <code>y</code> <em>parameters</em> and <code>5</code> and <code>6</code>
<em>arguments</em>.</p>
<p>Rust does not have many fancy features related to parameters and arguments. But
other languages do. For example, <a href="https://api.rubyonrails.org/classes/ActionController/Redirecting.html#method-i-redirect_to">here’s a function in Ruby</a>,
another language I’ve written a ton of:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#6A737D"># its definition in Rails</span></span>
<span class="line"><span style="color:#F97583">def</span><span style="color:#B392F0"> redirect_to</span><span style="color:#E1E4E8">(options </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {}, response_options </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {})</span></span>
<span class="line"><span style="color:#6A737D"> # body elided</span></span>
<span class="line"><span style="color:#F97583">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># you can call it in all of these ways:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># pass a string with a URL</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to </span><span style="color:#9ECBFF">"http://www.rubyonrails.org"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># pass an instance of a model to go to it, if post.id = 5 then this might go to</span></span>
<span class="line"><span style="color:#6A737D"># "/posts/5"</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to @post</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># invoke an action directly, might be equivalent to the above in a Posts controller</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to </span><span style="color:#79B8FF">action:</span><span style="color:#9ECBFF"> "show"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">id:</span><span style="color:#79B8FF"> 5</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># redirect to a model's URL, passing a flash message</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to </span><span style="color:#B392F0">post_url</span><span style="color:#E1E4E8">(@post), </span><span style="color:#79B8FF">alert:</span><span style="color:#9ECBFF"> "Watch it, mister!"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># do the same but also set a specific HTTP code</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to </span><span style="color:#B392F0">post_url</span><span style="color:#E1E4E8">(@post), </span><span style="color:#79B8FF">status:</span><span style="color:#79B8FF"> :found</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># do both</span></span>
<span class="line"><span style="color:#E1E4E8">redirect_to </span><span style="color:#B392F0">post_url</span><span style="color:#E1E4E8">(@post), </span><span style="color:#79B8FF">status:</span><span style="color:#79B8FF"> 301</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">flash:</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">updated_post_id:</span><span style="color:#E1E4E8"> @post.</span><span style="color:#B392F0">id</span><span style="color:#E1E4E8"> }</span></span></code></pre>
<p>I used to love Ruby, and now I love Rust. And to be honest, <code>redirect_to</code> and
friends are a huge reason why I am so against Rust gaining these sorts of fancy
features: they can be really concise, and beautiful to a certain kind of eye,
but it also makes it really, really hard to know what all you can do with a
function. You pretty much have to hope that someone has written good documentation,
and while Rails’s documentation is pretty good, not every library is going to have
it.</p>
<p>To be clear about it, there are several things bundled together here: flexible
argument types, options hashes, defaults, and keyword-looking syntax. It’s that
whole style of API that shaped my skepticism, not argument labels by themselves.</p>
<p>So, what exactly are all of these features?</p>
<h2 id="named-parameters">Named parameters</h2>
<p>The simplest one is <em>named parameters</em>. As you might guess, this means that we can
explicitly use parameter names at the call site:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// without named parameters</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> z </span><span style="color:#F97583">=</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">6</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// if Rust had them</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> z </span><span style="color:#F97583">=</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(x</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 5</span><span style="color:#E1E4E8">, y</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 6</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>Named parameters are nice because they can give you more information at the call
site, but also they can be more flexible. Some languages also let you supply
named arguments in a different order:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// since we have names, we can write this:</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> z </span><span style="color:#F97583">=</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(x</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 5</span><span style="color:#E1E4E8">, y</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 6</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// but we can also write this:</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> z </span><span style="color:#F97583">=</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">(y</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 6</span><span style="color:#E1E4E8">, x</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 5</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>The downside of named parameters is that they can get quite verbose:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="python"><code><span class="line"><span style="color:#6A737D"># FastAPI in Python, UserOut class not shown</span></span>
<span class="line"><span style="color:#B392F0">@app.get</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#9ECBFF"> "/me"</span><span style="color:#E1E4E8">, </span></span>
<span class="line"><span style="color:#FFAB70"> response_model</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">UserOut, </span></span>
<span class="line"><span style="color:#FFAB70"> status_code</span><span style="color:#F97583">=</span><span style="color:#79B8FF">200</span><span style="color:#E1E4E8">, </span></span>
<span class="line"><span style="color:#FFAB70"> tags</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">[</span><span style="color:#9ECBFF">"Users"</span><span style="color:#E1E4E8">],</span></span>
<span class="line"><span style="color:#FFAB70"> summary</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"Get current user profile"</span></span>
<span class="line"><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583">def</span><span style="color:#B392F0"> get_current_user</span><span style="color:#E1E4E8">():</span></span></code></pre>
<p>I tried to not strawman this by showing an extreme example, but <code>get</code> can take
23 different keyword arguments, and so this could get quite, quite long.</p>
<p>Named arguments can be good when passing expressions, so we can understand
something more at the call site. In the above, <code>status_code=200</code> is much nicer
than a bare <code>200</code>, if you know HTTP well it’s kind of obvious what it is, but
the additional clarity is nice. But it can be common to break truly complex
examples down into variables that we end up forwarding to the function, and then
it becomes verbose <em>and</em> redundant. As an example from inside FastAPI where
<code>get</code> is invoked directly with all of those options:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="python"><code><span class="line"><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">.router.get(</span></span>
<span class="line"><span style="color:#E1E4E8"> path,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model,</span></span>
<span class="line"><span style="color:#FFAB70"> status_code</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">status_code,</span></span>
<span class="line"><span style="color:#FFAB70"> tags</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">tags,</span></span>
<span class="line"><span style="color:#FFAB70"> dependencies</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">dependencies,</span></span>
<span class="line"><span style="color:#FFAB70"> summary</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">summary,</span></span>
<span class="line"><span style="color:#FFAB70"> description</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">description,</span></span>
<span class="line"><span style="color:#FFAB70"> response_description</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_description,</span></span>
<span class="line"><span style="color:#FFAB70"> responses</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">responses,</span></span>
<span class="line"><span style="color:#FFAB70"> deprecated</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">deprecated,</span></span>
<span class="line"><span style="color:#FFAB70"> operation_id</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">operation_id,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_include</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_include,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_exclude</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_exclude,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_by_alias</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_by_alias,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_exclude_unset</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_exclude_unset,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_exclude_defaults</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_exclude_defaults,</span></span>
<span class="line"><span style="color:#FFAB70"> response_model_exclude_none</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_model_exclude_none,</span></span>
<span class="line"><span style="color:#FFAB70"> include_in_schema</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">include_in_schema,</span></span>
<span class="line"><span style="color:#FFAB70"> response_class</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">response_class,</span></span>
<span class="line"><span style="color:#FFAB70"> name</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">name,</span></span>
<span class="line"><span style="color:#FFAB70"> callbacks</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">callbacks,</span></span>
<span class="line"><span style="color:#FFAB70"> openapi_extra</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">openapi_extra,</span></span>
<span class="line"><span style="color:#FFAB70"> generate_unique_id_function</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">generate_unique_id_function,</span></span>
<span class="line"><span style="color:#E1E4E8">)</span></span></code></pre>
<p>We actually snuck in another two features into this example: how did we only
pass five arguments into a function that has 23 parameters?</p>
<h2 id="optional-andor-default-arguments">Optional and/or Default arguments</h2>
<p>As the name implies, optional arguments is a feature where you can choose to not pass in an argument,
and default arguments lets you set a default when an argument is not passed.</p>
<p>To go back to that example:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="python"><code><span class="line"><span style="color:#B392F0">@app.get</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#9ECBFF"> "/me"</span><span style="color:#E1E4E8">, </span></span>
<span class="line"><span style="color:#FFAB70"> response_model</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">UserOut, </span></span>
<span class="line"><span style="color:#FFAB70"> status_code</span><span style="color:#F97583">=</span><span style="color:#79B8FF">200</span><span style="color:#E1E4E8">, </span></span>
<span class="line"><span style="color:#FFAB70"> tags</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">[</span><span style="color:#9ECBFF">"Users"</span><span style="color:#E1E4E8">],</span></span>
<span class="line"><span style="color:#FFAB70"> summary</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"Get current user profile"</span></span>
<span class="line"><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583">def</span><span style="color:#B392F0"> get_current_user</span><span style="color:#E1E4E8">():</span></span></code></pre>
<p>We only passed in five of the 22 possible arguments. If we had to pass all 22 every time, that would
be extremely verbose. So by declaring defaults for some parameters, we can leave them off of the call
site, and not pass arguments for them, and get the defaults instead. We saw this in the Ruby:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#6A737D"># its definition in Rails</span></span>
<span class="line"><span style="color:#F97583">def</span><span style="color:#B392F0"> redirect_to</span><span style="color:#E1E4E8">(options </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {}, response_options </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {})</span></span></code></pre>
<p>The <code>= {}</code> are defaults for <code>options</code> and <code>response_options</code>: they’re an empty hash map.</p>
<p>You’ll often find optional and default arguments come together: after all, if you don’t pass
an argument in, what should the value of that parameter be? If your language has some sort of
null value, that can be one solution, and it can feel like optional arguments without default
arguments.</p>
<p>But you can truly get one without the other with our next feature, function overloading:</p>
<h2 id="function-overloading">Function Overloading</h2>
<p>Languages that support function overloading allow you to define multiple
functions with the same name, but different signatures. Which function gets
invoked depends on which arguments you pass. This lets us truly have optional
arguments without default arguments. In Java:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="java"><code><span class="line"><span style="color:#6A737D">// Version 1: Requires both arguments</span></span>
<span class="line"><span style="color:#F97583">void</span><span style="color:#B392F0"> connect</span><span style="color:#E1E4E8">(String url, </span><span style="color:#F97583">int</span><span style="color:#E1E4E8"> timeout) { ... }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// Version 2: Timeout is optional to the caller, and handled internally</span></span>
<span class="line"><span style="color:#F97583">void</span><span style="color:#B392F0"> connect</span><span style="color:#E1E4E8">(String url) { ... }</span></span></code></pre>
<p>Here, if you pass in a timeout, you get the first function, and if you don’t,
you get the second. Instead of null, the parameter just doesn’t exist at all.</p>
<p>This is a specific form of something called “function dispatch,” which basically
means “hey when a function is invoked, what <em>exactly</em> gets called?” There are a
<em>lot</em> of different ways to do this, and I don’t want this post to get into all
of that right now, but there is a multitude of possibilities here: static vs
dynamic, single vs multiple, predicate dispatch, pattern matching dispatch,
prototype based dispatch… maybe I’ll write a post about that someday too.</p>
<p>So, what are the pros and cons here?</p>
<h2 id="these-features-make-me-uneasy">These features make me uneasy</h2>
<p>As I said above, Rust doesn’t support any of these three features right now. And
they have been a persistent request from the community for years. <a href="https://github.com/rust-lang/rfcs/issues/323">Here</a> is
a 12 year old GitHub issue about this support, and it even includes something we
didn’t talk about, “variable arity argument lists.”</p>
<p>That gets me to the first thing that I don’t like about these features: there
are a <em>lot</em> of them. And they are all intertwined. As we mentioned before, if
you have optional arguments, you probably want default arguments. If you have
named parameters, do you want to support <em>only</em> named parameters, or do you want
to support both? It’s so tempting to just support all of them, and then you’ve
added a huge mountain of complexity.</p>
<p>In Rust right now, you have to write multiple functions with slightly different
names:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// a new empty vector</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// one with a set capacity</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">with_capacity</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>For the simple cost of “write two different names for your functions,” you get
to keep the rules very simple: there is one function definition, and you invoke
it by name. Done. Would it be nice to be able to say all of these:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">(capacity</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 5</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>Sure, maybe. But the cost feels very, very high to me. If you’re used to these
features, maybe it doesn’t seem that high, but I have a Ruby tattoo on my body.
I’ve seen some shit. And so I’ve grown to enjoy Rust’s simplicity in this area.
Yes, if you have a function with tons of parameters, you may need to write a
builder instead, and that comes with its own form of verbosity:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// if Vec had a builder</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> v </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Vec</span><span style="color:#F97583">::</span><span style="color:#B392F0">new</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">with_capacity</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">5</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">build</span><span style="color:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// all of the code you have to write to make the builder elided</span></span></code></pre>
<p>But the language itself stays simple, and the rules are easy. For a language
that’s already perceived as complex, this has always felt right to me. And that’s
why I’ve pushed back against the various proposals to extend Rust in this way all
of these years.</p>
<p>But recently, I changed my opinion on one of these features, and one alone, and
I would consider it okay if Rust gained them. Maybe. Also, it’s important to note
that I haven’t worked on Rust in years, and so my opinions are kind of irrelevant,
but whatever: it’s my blog, this is what opinions are for.</p>
<h2 id="im-okay-with-named-parameters-now">I’m okay with named parameters now</h2>
<p>I think Rust could be okay with named parameters, but <em>not</em> optional or default
ones. And what changed my opinion is coding agents, actually. I’m sorry, maybe
you’re sick of me talking about AI, but hear me out.</p>
<p>One of my beefs with named parameters, as mentioned above, is verbosity. But
what you gain for that verbosity is clarity. I don’t think that verbosity and
clarity are always the same thing; I like <code>{}</code> rather than <code>do</code>/<code>end</code>, and find
it more readable, personally. But let’s take this function from the <code>image</code> crate:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// definition</span></span>
<span class="line"><span style="color:#F97583">pub</span><span style="color:#F97583"> fn</span><span style="color:#B392F0"> crop_imm</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">I</span><span style="color:#F97583">:</span><span style="color:#B392F0"> GenericImageView</span><span style="color:#E1E4E8">>(</span></span>
<span class="line"><span style="color:#E1E4E8"> image</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">I</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> x</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> y</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> width</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> height</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> SubImage</span><span style="color:#E1E4E8"><</span><span style="color:#F97583">&</span><span style="color:#B392F0">I</span><span style="color:#E1E4E8">> { </span><span style="color:#F97583">...</span><span style="color:#E1E4E8"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// calling it</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> cropped </span><span style="color:#F97583">=</span><span style="color:#B392F0"> image</span><span style="color:#F97583">::</span><span style="color:#B392F0">imageops</span><span style="color:#F97583">::</span><span style="color:#B392F0">crop_imm</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">img, </span><span style="color:#79B8FF">10</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">20</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">200</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">100</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// if we had named arguments</span></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> cropped </span><span style="color:#F97583">=</span><span style="color:#B392F0"> image</span><span style="color:#F97583">::</span><span style="color:#B392F0">imageops</span><span style="color:#F97583">::</span><span style="color:#B392F0">crop_imm</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E1E4E8"> image</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#E1E4E8">img,</span></span>
<span class="line"><span style="color:#E1E4E8"> x</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 10</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> y</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 20</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> width</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 200</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> height</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> 100</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">);</span></span></code></pre>
<p>This does really increase readability, no question about it. But for me, typing
all of that out just isn’t really super worth the squeeze. But when Claude is
gonna write it? I care a lot less. And the readability advantage for humans is
even stronger for agents: the extra clarity of the text inline seems to (I
haven’t run real evals on this yet…) be more helpful and use less tokens and
calls when looking at the actual call site. If an agent were reading that named
example above, it could tell that <code>10</code> is the <code>x</code> value without having to go
look up the function signature itself. “What’s good for humans is true for
agents” strikes again.</p>
<p>This is also why I’m still against optional arguments: they deliberately
<em>hide</em> what is being passed in to the function, and obscure clarity at the call
site. What’s nice about them is that you have to type less. But I’m not typing
myself anymore. So the benefit just isn’t there, but the drawbacks are. This
also doesn’t solve the verbosity side of passing <code>foo=foo</code> in our FastAPI
example, it is still redundant. But one part of the downsides have been
ameliorated.</p>
<p>Notably, all of this reasoning also applies to the builder pattern, and it’s why
I try to use builders sparingly in Rust. It can emulate named arguments, which
is good, but it can also emulate optional/default/variable arguments, which is
bad. The juice is worth the squeeze sometimes, but not all the time, and
probably not even the majority of the time.</p>
<p>So we’ve ameliorated the largest downside, but get to keep the largest upside.
That seems good. But even if we like the feature, there are still lots of
practical concerns with Rust specifically that make it not a slam-dunk to add
them to the language.</p>
<h2 id="theres-tons-of-practical-issues-though">There’s tons of practical issues though</h2>
<p>Now, “is this feature good in the abstract” is very different from “should this
feature” be implemented? There are a <em>lot</em> of open questions and drawbacks to
implementing named parameters. A simple one is that you don’t get rid of all of
those previous functions, <code>with_capacity</code> will exist forever. That’s a
relatively minor downside to some of the harder problems.</p>
<p>First, strictly speaking Rust parameters aren’t names, they’re patterns. A
name is a simple pattern, but you can get more complex:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> foo</span><span style="color:#E1E4E8">((x, y)</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> (</span><span style="color:#B392F0">i32</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">i32</span><span style="color:#E1E4E8">)) {</span></span></code></pre>
<p>This parameter doesn’t have a name, it’s a pattern that introduces two bindings.
You could come up with some syntax that gives things external names vs internal
names, but there’s that complexity revealing its head again.</p>
<p>What happens when functions become values? I can write this today:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> resize</span><span style="color:#E1E4E8">(width</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">, height</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> offset</span><span style="color:#E1E4E8">(dx</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">, dy</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> f</span><span style="color:#F97583">:</span><span style="color:#B392F0"> fn</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">u32</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">u32</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=</span><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> resizing { resize } </span><span style="color:#F97583">else</span><span style="color:#E1E4E8"> { offset };</span></span></code></pre>
<p>What name do we use for <code>f</code>’s parameters? They can be named different things.
Does this become an error? What names can you use when invoking <code>f</code>? Making this
more complicated, Rust actually will accept this code right now:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">type</span><span style="color:#B392F0"> Callback</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> fn</span><span style="color:#E1E4E8">(width</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">, height</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> f</span><span style="color:#E1E4E8">(g</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Callback</span><span style="color:#E1E4E8">) {}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> bar</span><span style="color:#E1E4E8">(x</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">, y</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">) {}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0"> f</span><span style="color:#E1E4E8">(bar) ;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>The names in <code>Callback</code> are just documentation, as you can see they’re not
required. Does this become an error? Do we require <code>bar</code> to change its names?
The same goes for trait definitions, actually:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">trait</span><span style="color:#B392F0"> Writer</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> write</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">, data</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#E1E4E8">[</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">]);</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Sink</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Writer</span><span style="color:#F97583"> for</span><span style="color:#B392F0"> Sink</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> write</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">, bytes</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#E1E4E8">[</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">]) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Is this an error? Is it okay?</p>
<p>Here’s another problem: evaluation order.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> consume</span><span style="color:#E1E4E8">(data</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">>, length</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Right now, Rust always evaluates parameters from left to right at the call site.
So if we use the named version:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> consume</span><span style="color:#E1E4E8">(data</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">>, length</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> consume2</span><span style="color:#E1E4E8">(length</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">, data</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">>) {</span></span>
<span class="line"><span style="color:#6A737D"> // body elided</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> data </span><span style="color:#F97583">=</span><span style="color:#B392F0"> vec!</span><span style="color:#E1E4E8">[</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">2</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">3</span><span style="color:#E1E4E8">];</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> // this is an error, borrow after move</span></span>
<span class="line"><span style="color:#B392F0"> consume</span><span style="color:#E1E4E8">(data, data</span><span style="color:#F97583">.</span><span style="color:#B392F0">len</span><span style="color:#E1E4E8">());</span></span>
<span class="line"><span style="color:#6A737D"> // this is okay</span></span>
<span class="line"><span style="color:#B392F0"> consume2</span><span style="color:#E1E4E8">(data</span><span style="color:#F97583">.</span><span style="color:#B392F0">len</span><span style="color:#E1E4E8">(), data);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> // does this compile or not?</span></span>
<span class="line"><span style="color:#B392F0"> consume</span><span style="color:#E1E4E8">(length</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> data</span><span style="color:#F97583">.</span><span style="color:#B392F0">len</span><span style="color:#E1E4E8">(), data</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> data);</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>If we keep left to right evaluation order according to the definition, but let
you use named values in any order, suddenly some orders at the call site compile
and some orders do not. Do we change evaluation order to do whatever compiles?
That feels very dangerous and confusing.</p>
<p>Rust does let you do this with struct fields:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Args</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> data</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">u8</span><span style="color:#E1E4E8">>,</span></span>
<span class="line"><span style="color:#E1E4E8"> length</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> data </span><span style="color:#F97583">=</span><span style="color:#B392F0"> vec!</span><span style="color:#E1E4E8">[</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">2</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">3</span><span style="color:#E1E4E8">];</span></span>
<span class="line"><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D"> // works</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> args </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Args</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> length</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> data</span><span style="color:#F97583">.</span><span style="color:#B392F0">len</span><span style="color:#E1E4E8">(),</span></span>
<span class="line"><span style="color:#E1E4E8"> data,</span></span>
<span class="line"><span style="color:#E1E4E8"> };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> // doesn't</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> args </span><span style="color:#F97583">=</span><span style="color:#B392F0"> Args</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> data,</span></span>
<span class="line"><span style="color:#E1E4E8"> length</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> data</span><span style="color:#F97583">.</span><span style="color:#B392F0">len</span><span style="color:#E1E4E8">(),</span></span>
<span class="line"><span style="color:#E1E4E8"> };</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This is one reason why a lot of proposals for named arguments try to unify them
with structs. As I said before, not insurmountable, just things that need to be
sorted.</p>
<p>Another small thing is a compatibility hazard: if we start naming parameters,
renaming them breaks callers. Is that okay? Could you introduce aliases to help
with this? That’s more complexity…</p>
<p>These are just some of the problems that come up in the design space, and this
post is getting long, so I’ll leave it at this for now. But proposals for doing
this have to engage with all of this and more, which is why language design is
difficult.</p>
<h2 id="in-conclusion">In conclusion</h2>
<p>I like to think that I change my opinions over time as the world or my
understanding of it changes. So I thought it might be interesting to share a way
in which an opinion that I’ve held for over a decade has changed a bit lately.
I’m still not sure if named parameters are right for Rust, but I’m at least
somewhat open to the idea these days. We’ll see what the language team decides
if anyone does decide to keep trying to push this forward. And people do, for
example, I am writing this in part because I saw <a href="https://botahamec.dev/named-optional-args">this post</a> go by today.
This post isn’t intended to be support or a rebuttal of it, I actually didn’t
even read it yet, it’s just that I’ve been thinking about this a lot lately
(while working on <a href="https://rue-lang.dev/">Rue</a>) and seeing that go by pushed me to write some of
the thoughts down. You should probably go check it out, as it’s an actual
proposal in this space and this post is more of an exploration of the general
idea and my own thoughts on it.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mw2ahu3pv22q" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Arguing about arguments
<a target="_blank" href="https://steveklabnik.com/writing/arguing-about-arguments/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/argu...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/arguing-about-arguments/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreigpeqitvd6az3jx55koev3nbbllzxb6moswv6erkvptevqphh553e" alt="" class="thumbnail s-rtbqd8"/> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Arguing about arguments</p> <p class="description s-rtbqd8">Here’s some Rust code: // define a function fn foo (x : i32 , y : i32 ) -> i32 { // body elided } // call it let z = foo ( 5 , 6 ); In programming language jargon, we call x and y parameters and 5 and 6 arguments . Rust does not have many fancy features related to parameters and…</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-09-21T17:30:35.997Z" class="date s-hik11q">September 21, 2026 at 5:30 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="92 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>92</span></span> <span class="stat s-hik11q" title="11 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>11</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mw2ahu3pv22q" target="_blank" class="permalink s-hik11q"><span>Read 22 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>So, what happened to my Claude Code series?</title><link>https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/</link><guid isPermaLink="true">https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/</guid><description>I started off this year writing a lot, and wanted to keep up the pace. I did good at first! And then… I didn’t manage to. In some ways, this is because I have just been super busy! I’ve never had less free time, and I’m okay with that. There’s a tremendous amount of good things…</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I started off this year writing a lot, and wanted to keep up the pace. I did
good at first! And then… I didn’t manage to.</p>
<p>In some ways, this is because I have just been super busy! I’ve never had less
free time, and I’m okay with that. There’s a tremendous amount of good things
going on in my life.</p>
<p>But it’s also put this whole Claude Code series in an awkward spot, because
things have been changing so rapidly, that it’s been hard to keep up. And I also
need to figure out what it is I actually want to say, and my practice has
changed tremendously since January!</p>
<p>I’m thinking about this a lot today in part because one of the things that happened
since we last talked about this stuff was the release of Claude Fable. And now,
a new Fable has just been put out into the world, and OpenAI’s Astra is coming very
soon. New models don’t always mean that practice inherently changes, but like, it’s
just feeling like this is getting further and further behind, and so I think it’s
probably good for me to just officially close this series. I still really like what
I wrote, and think it’s still valuable today for getting started, but I just don’t
think I can commit to keeping up with documenting everything I do at the pace that
stuff is happening at. So being honest with myself about that is very useful.</p>
<p>To those that found this series helpful, I’m glad, and I’m sorry I’m not writing
more about it. I’ll still write things about this topic in general, but the core
idea of a “how to get started doing this stuff” series just isn’t in the cards
for me right now.</p>
<p>AI is here to stay, and it is dramatically changing how people work. I believe
that more strongly than ever. So stopping isn’t about me somehow getting bearish
on this stuff. It’s just that I’m too darn busy for this specific project.</p>
<p>This is also why the “notes” section on my website still has zero entries. I was
hoping that something to put stuff shorter form than a blog post would make it
easier to write, but in the end, I can’t even find much time for that.</p>
<p>Okay well I’m having trouble wrapping this up so, I’ll just leave it here for
now.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mukmbg4ugs2e" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">So, what happened to my Claude Code series? <a target="_blank" href="https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/so-w...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">So, what happened to my Claude Code series?</p> <p class="description s-rtbqd8">I started off this year writing a lot, and wanted to keep up the pace. I did good at first! And then… I didn’t manage to. In some ways, this is because I have just been super busy! I’ve never had less free time, and I’m okay with that. There’s a tremendous amount of good things…</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-09-02T18:53:57.487Z" class="date s-hik11q">September 2, 2026 at 6:53 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="53 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>53</span></span> <span class="stat s-hik11q" title="1 repost"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>1</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mukmbg4ugs2e" target="_blank" class="permalink s-hik11q"><span>Read 8 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Too many words about DIDs</title><link>https://steveklabnik.com/writing/too-many-words-about-dids/</link><guid isPermaLink="true">https://steveklabnik.com/writing/too-many-words-about-dids/</guid><description>Your “Bluesky account” is not just a Bluesky account: it is an account that can be used with a variety of other applications. This post is going to be an exploration of part of what that means from a technical perspective, so if you’re not a software developer, this post isn’t…</description><pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Your “Bluesky account” is not <em>just</em> a Bluesky account: it is an account that
can be used with a variety of other applications. This post is going to be an
exploration of part of what that means from a technical perspective, so if you’re
not a software developer, this post isn’t for you. But what I’m going to explain
is the technical mechanism for how your account works separate from Bluesky, and
in fact, separate from any particular app.</p>
<p>Let’s talk about identity: who are you, anyway? Users of a system need some sort
of way to describe who they are to use it. If you want to log in, you need to
present who you are. If you want to make a post, well, we need to know who the
author of that post is. For atproto, the protocol that underlies Bluesky and
other apps in the ATmosphere, they use the “Decentralized Identity” standard,
also known as DID. The <a href="https://www.w3.org/TR/did-1.0/">W3C standardized DIDs in
2022</a>. As you might guess from the name, DIDs
are, an identifier that you can use as the basis of identity for building
applications. And the idea is that these identifiers are decentralized. However,
a lot of people have a lot of feelings about that specific word, and often
accuse atproto of not being properly decentralized. We’re going to go over the
details so you can understand how this works, and you can decide for yourself if
this approach suits you or not.</p>
<h2 id="dids-and-did-documents">DIDs and DID Documents</h2>
<p>Here is my DID, we’ll use this as an example: <code>did:plc:3danwc67lo7obz2fmdg6jxcr</code></p>
<p>There are three parts, separated by colons: The scheme (<code>did</code>), the method (<code>plc</code>),
and the DID method-specific identifier (<code>3danwc67lo7obz2fmdg6jxcr</code>).</p>
<p>To use a DID, such as <code>did:plc:3danwc67lo7obz2fmdg6jxcr</code>, you resolve it into a
<a href="https://www.w3.org/TR/did-1.0/#dfn-did-documents">DID Document</a></p>
<blockquote>
<p>A set of data describing the DID subject, including mechanisms, such as
cryptographic public keys, that the DID subject or a DID delegate can use to
authenticate itself and prove its association with the DID.</p>
</blockquote>
<p>That document contains <a href="https://www.w3.org/TR/did-1.0/#core-properties">various
properties</a> that describe the
identity. Here’s my DID Document, at the time of writing:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>
<span class="line"><span style="color:#79B8FF"> "@context"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://www.w3.org/ns/did/v1"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://w3id.org/security/multikey/v1"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://w3id.org/security/suites/secp256k1-2019/v1"</span></span>
<span class="line"><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:plc:3danwc67lo7obz2fmdg6jxcr"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "alsoKnownAs"</span><span style="color:#E1E4E8">: [</span><span style="color:#9ECBFF">"at://steveklabnik.com"</span><span style="color:#E1E4E8">],</span></span>
<span class="line"><span style="color:#79B8FF"> "verificationMethod"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:plc:3danwc67lo7obz2fmdg6jxcr#atproto"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Multikey"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "controller"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:plc:3danwc67lo7obz2fmdg6jxcr"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "publicKeyMultibase"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"zQ3shfNec2kPEb8cL77gSRMbCwbWE27p9nxKkcc4E82xtW8RJ"</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#79B8FF"> "service"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"#atproto_pds"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"AtprotoPersonalDataServer"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "serviceEndpoint"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"https://morel.us-east.host.bsky.network"</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8"> ]</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This document gives you everything you need to know to determine who I am, that is,
given an arbitrary post that claims it’s written by me, this document describes how
you’d verify that claim.</p>
<p>We’ll get into how to do that in a moment, but first, how do you resolve
that DID into that DID document? Well, it’s pretty easy: each method is a
standard that describes how you do that. So when you see <code>did:plc</code>, that means
we use the PLC standard, which we’ll be going over in a moment. Another method
supported by Bluesky is <code>did:web</code>. In that case, you wouldn’t use the PLC
standard, you’d use the Web one.</p>
<p>This is the sense in which DIDs are decentralized: when you present your
identity, you get to decide what method validates that that is a real identity.
There’s no centralized authority that determines which DID types are valid. Now,
of course, that doesn’t mean that every application supports every DID method,
because while this specification is <em>very</em> generic, you’re still going to have
to write some code to implement that particular method. I could say “Hey I’m
<code>did:foo:1243</code>” and unless your app supports the <code>foo</code> method, it’s not gonna
inherently just know what to do. So that is one important caveat.</p>
<h2 id="didweb"><code>did:web</code></h2>
<p>Let’s explain this resolution process for the <code>web</code> method. While supported by
Bluesky, a very small number of users actually use <code>did:web</code>, but it’s a simpler
method and so I think it’s illustrative to go over first. I’ll be using <a href="https://bsky.app/profile/web.lizthegrey.com">Liz
Fong-Jones</a>’ <code>did:web</code> account as an
example here. Her identity for that account is <code>did:web:lizthegrey.com</code>.</p>
<p>So how do we resolve this DID into a DID Document? We take the method-specific
identifier, which in this case is <code>lizthegrey.com</code>, and put it into this URL template:</p>
<p><code>https://<id>/.well-known/did.json</code></p>
<p>You can then go fetch this URL to resolve it into the DID Document, which at the
time of writing, looks like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>
<span class="line"><span style="color:#79B8FF"> "@context"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://www.w3.org/ns/did/v1"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://w3id.org/security/multikey/v1"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#9ECBFF"> "https://w3id.org/security/suites/secp256k1-2019/v1"</span></span>
<span class="line"><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:web:lizthegrey.com"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "alsoKnownAs"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#9ECBFF"> "at://web.lizthegrey.com"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#9ECBFF"> "did:plc:i4tfenpfog244rxry5uz4vtk"</span></span>
<span class="line"><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#79B8FF"> "verificationMethod"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:web:lizthegrey.com#atproto"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Multikey"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "controller"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:web:lizthegrey.com"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "publicKeyMultibase"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"zQ3shnEqBNR5cuTePW8FyvnrRQaFf6Y7sCi5NBmDpteVXFjb6"</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#79B8FF"> "service"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> "id"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"#atproto_pds"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"AtprotoPersonalDataServer"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "serviceEndpoint"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"https://pds.lizthegrey.com"</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8"> ]</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This is very simple! So why might we not want to use <code>did:web</code>? Why bother with
any other system? Well, this relies on the DNS system. One could make the
argument that ultimately, this is still centralized in some form. If Liz’s
domain registrar were to take away her domain, she would also lose control of
this DID. In a more generic sense, if Liz decides she wants to not use that
domain anymore, she will lose control of that identity to whoever does. That
could be through non-malicious means, like letting it expire and someone else
purchases it, or through malicious ones, like a hack which would compromise her
registrar account and take the domain over.</p>
<p>Also, you need to have a web server running on that domain with infinite uptime;
if the server goes down, so does your ability to get the document.</p>
<p>When this DID document changes, there’s no mechanism for clients to know that
it’s changed, which means applications may use one that’s out of date, or that
there is lag between updating the document and updating the application built on
it, which may cause temporary problems until the latest document is fetched.</p>
<h2 id="didplc"><code>did:plc</code></h2>
<p>All of these drawbacks led Bluesky to develop their own DID method, which
attempts to fix these problems and others. This method is called
<a href="https://web.plc.directory/spec/v0.1/did-plc"><code>did:plc</code></a>. To resolve a <code>did:plc</code>,
you take the entire DID, and put it in this template:</p>
<p><code>https://plc.directory/<did></code></p>
<p>You can then fetch that URL and get the DID document.</p>
<p>So… what’s the difference? Well, in this case, both nothing and something. In
a very literal sense, both are resolved in the same way: you fetch a URL.
However, the details matter. There is already two ways in which this is different
than DID:Web:</p>
<ol>
<li>Your DID is no longer tied to a specific domain name. I can let
<code>steveklabnik.com</code> expire and move to <code>steve.klabnik.com</code> and my <code>did:plc</code>
stays the same.</li>
<li>While a web server still needs to be running, that’s the job of plc.directory, not
my own job. This is operationally much simpler.</li>
</ol>
<p>I’ve presented the above as pros, but there are also cons. Before, I had to
trust the DNS system and domain registrars, now I have to trust plc.directory.
All of the same caveats apply in that sense, I have to trust that they don’t
take my ID away from me, or that it doesn’t get stolen, etc. However, there are
also some important details that mitigate this, which we’ll get to. But for some
people, neither trusting DNS nor trusting plc.directory is acceptable, and there
are other DID methods that use, for example, a blockchain to resolve the name.
Bluesky does not support using any of those DID methods, so for this
application, it’s not really relevant, but it’s important to know that they
exist.</p>
<p>Why do it this way? Well, the simplest way to put it is this: setting up a
<code>did:web</code> involves a lot of “nerd stuff.” You have to register a domain, and
that’s also an ongoing monetary cost. You have to know how to set up a web
server, and author some JSON to put on that server. You have to keep it running.
You have to know how to store your private keys, and keep them safe. It’s a
non-starter compared to “sign up for this web app.” And Bluesky’s goals involves
making this platform accessible to non-nerds. By having plc.directory manage
all of this, we eliminate all of those steps.</p>
<p>While drafting this post, I have also been made aware of
<a href="https://identity.foundation/didwebvh/v1.0/"><code>did:webvh</code></a>, which expands on <code>did:web</code> and attempts to rectify
some of its shortcomings. I have not read the spec yet, but it has reached 1.0,
so it is probably worth checking out. I wanted to get this post shipped last
week, and didn’t want to delay it further by adding another section, but if
I were writing this post in the future, I’d probably want to talk about it as
well, so just a little heads-up there.</p>
<h2 id="does-bluesky-own-your-identity">Does Bluesky own your identity?</h2>
<p>But it does also mean that, in some sense, Bluesky still owns your identity.
They’ve generated a keypair for you, and they have access to the secret key.
That’s unacceptable for some people. So how do you fix that?</p>
<p>Well, <code>did:plc</code> has some additional features that <code>did:web</code> does not. For
example, <code>did:plc</code> will allow you to register additional keypairs with your ID
and use them to rotate your signing keys. This allows you to remove the Bluesky
generated keys and insert your own.</p>
<p>While that is true, it’s also the case that your PDS needs to use your keys to
sign your posts. As such, most people are likely to store their keys in their PDS,
and so if you are using a Bluesky managed PDS, well, you’ve uploaded your keys to
their infrastructure, and that’s probably not acceptable if you’re trying to keep
your identity away from Bluesky. Of course, the solution there is to run your own
PDS and then rotate your keys. At that point, your key is living on infrastructure
you own, and Bluesky has no say over it any more.</p>
<p>I think that this possibility is an important design property, and allows
motivated users to meaningfully own their identity. A criticism of this boils
down to “well, most users won’t do that,” and while that’s true, I also think
that’s okay for most people, and that having the choice is more important than
forcing every user to deal with their own key management.</p>
<h2 id="in-summary">In summary</h2>
<p>This is kind of an abrupt end to this post, but I just wanted to get some things
down ‘on paper’ as it were. I hope you’ve learned a bit about identity and how
it works with atproto.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mqmjx73a2s2w" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Too many words about DIDs: <a target="_blank" href="https://steveklabnik.com/writing/too-many-words-about-dids/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/too-...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/too-many-words-about-dids/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Too many words about DIDs</p> <p class="description s-rtbqd8">Blog post: Too many words about DIDs by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-07-14T15:37:00.032Z" class="date s-hik11q">July 14, 2026 at 3:37 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="132 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>132</span></span> <span class="stat s-hik11q" title="29 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>29</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mqmjx73a2s2w" target="_blank" class="permalink s-hik11q"><span>Read 12 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Compilers and AI 'cyber' defense</title><link>https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/</link><guid isPermaLink="true">https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/</guid><description>I haven’t had time to blog in a while, I have a lot to say, but have been doing a lot of things and so haven’t gotten words down in a while. One of those things in the past few weeks has been picking back up my Rue project. I’m really excited for what it’s turning into, even…</description><pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I haven’t had time to blog in a while, I have a lot to say, but have been doing
a lot of things and so haven’t gotten words down in a while.</p>
<p>One of those things in the past few weeks has been picking back up my <a href="https://rue-lang.dev/">Rue</a>
project. I’m really excited for what it’s turning into, even though it’s not
really ready for anyone else to use just yet. So go take a look if you want, but
don’t expect anything amazing, it’s very much a work in progress.</p>
<p>Anyway, one of the things that got me back into working on it is the releases of
Fable and Sol. I hadn’t really touched Rue since January, and a lot of things have
changed since then. It’s fun to have new models take a look at older projects and
see what their new perspectives are.</p>
<p>Unfortunately, due to the intelligence of these new models, the US government
has asked for and imposed restrictions. These are largely around things like
cybersecurity. Since I don’t do cybersecurity work, I didn’t think they would
affect me.</p>
<p>(Aside: I put ‘cyber’ in quotes in the title because I hate that apparently
that’s the term folks are using now. Sigh.)</p>
<p>Well:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>ⓘ This content can't be shown</span></span>
<span class="line"><span> We take extra caution with cybersecurity requests. If you're a security professional, you may be able to apply for</span></span>
<span class="line"><span> Trusted Access.</span></span>
<span class="line"><span> Trusted Access: https://openai.com/form/enterprise-trusted-access-for-cyber/</span></span>
<span class="line"><span> Learn more: https://help.openai.com/en/articles/20001326</span></span>
<span class="line"><span></span></span>
<span class="line"><span></span></span>
<span class="line"><span>› please continue</span></span>
<span class="line"><span></span></span>
<span class="line"><span></span></span>
<span class="line"><span>ⓘ This content can't be shown</span></span>
<span class="line"><span> We take extra caution with cybersecurity requests. If you're a security professional, you may be able to apply for</span></span>
<span class="line"><span> Trusted Access.</span></span>
<span class="line"><span> Trusted Access: https://openai.com/form/enterprise-trusted-access-for-cyber/</span></span>
<span class="line"><span> Learn more: https://help.openai.com/en/articles/20001326</span></span></code></pre>
<p>This is the output from Sol, but the same thing happens with Fable too. I asked Sol
about it, and this is what it says:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>• It is a false positive from the safety layer, not a judgment that you asked for anything malicious. Rue contains</span></span>
<span class="line"><span> fuzzers, crash reproducers, sanitizer hooks, child processes, deliberate out-of-bounds writes, and compiler-panic</span></span>
<span class="line"><span> tests. Those patterns look similar to exploit-development activity when a classifier sees commands or snippets without</span></span>
<span class="line"><span> fully trusting the repository-ownership context.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> RUE-560 is an especially likely trigger because the correct regression test deliberately performs a one-byte overflow</span></span>
<span class="line"><span> in an ASan-instrumented child and requires that child to crash. That is defensive testing, but the surface pattern is</span></span>
<span class="line"><span> still “construct an overflow and observe the failure.”</span></span>
<span class="line"><span></span></span>
<span class="line"><span> I cannot disable or bypass that layer. I can reduce interruptions by keeping work tightly scoped to this repository,</span></span>
<span class="line"><span> putting negative behavior inside the existing test harness, describing the defensive purpose explicitly, avoiding</span></span>
<span class="line"><span> reusable attack tooling, and checkpointing progress before risky-looking verification. The persistent goal means that</span></span>
<span class="line"><span> if a turn is interrupted, the next continuation can inspect the workspace and resume without needing you. Nothing I’ve</span></span>
<span class="line"><span> found suggests the project or your request is dangerous.</span></span></code></pre>
<p>It is really unfortunate that, in order to make sure my compiler isn’t going to
produce incorrect codegen, this trips the safety guards. I guess that simply
saying “hey this is a toy language nobody uses and so it’s fine I’m not hacking
anyone” and that not allowing me around the guards is a “good” thing, but at the
same time, it is unfortunate if these policies end up making it so that individuals
like me can’t make my software as robust as products from large organizations.</p>
<p>I don’t know how to end this post really, just wanted to put that out there. I’m
still thinking about the implications, and I hope you do as well.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mqckcblr5c2k" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Compilers and AI 'cyber' defense
<a target="_blank" href="https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/comp...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Compilers and AI 'cyber' defense</p> <p class="description s-rtbqd8">Blog post: Compilers and AI 'cyber' defense by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-07-10T16:16:34.386Z" class="date s-hik11q">July 10, 2026 at 4:16 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="27 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>27</span></span> <span class="stat s-hik11q" title="7 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>7</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mqckcblr5c2k" target="_blank" class="permalink s-hik11q"><span>Read 5 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>The most important thing when working with LLMs</title><link>https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/</link><guid isPermaLink="true">https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/</guid><description>Okay, so you’ve got the basics of working with Claude going. But you’ve probably run into some problems: Claude doesn’t do what you want it to do, it gets confused about what’s happening and goes off the rails, all sorts of things can go wrong. Let’s talk about how to improve…</description><pubDate>Tue, 27 Jan 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Okay, so you’ve got the basics of working with Claude going. But you’ve probably
run into some problems: Claude doesn’t do what you want it to do, it gets
confused about what’s happening and goes off the rails, all sorts of things can
go wrong. Let’s talk about how to improve upon that.</p>
<p>The most important thing that you can do when working with an LLM is give it a
way to quickly evaluate if it’s doing the right thing, and if it isn’t, point it
in the right direction.</p>
<p>This is incredibly simple, yet, like many simple things, also wildly complex.
But if you can keep this idea in mind, you’ll be well equipped to become
effective when working with agents.</p>
<h2 id="two-lessons-from-teaching-children">Two lessons from teaching children</h2>
<p>A long time ago, I used to teach programming classes. Many of these were to
adults, but some of them were to children. Teenaged children, but children
nonetheless. We used to do an exercise to try and help them understand the
difference between talking in English and talking in Ruby, or JavaScript, or
whatever kind of programming language rather than human language. The exercise
went like this:</p>
<p>I would have a jar of peanut butter, a jar of jelly, a loaf of bread, a spoon,
and a knife. I would ask the class to take a piece of paper and write down a
series of steps to make a peanut butter and jelly sandwich. They’d all then give
me their algorithms, and the fun part for me began: find one that’s innocently
written that I could hilariously misinterpret. For example, I might find one
like:</p>
<ol>
<li>Put the peanut butter on the bread</li>
<li>Put the jelly on the bread</li>
<li>Put the bread together</li>
</ol>
<p>I’d read this aloud to the class, you all understand this is a recipe for a
peanut butter and jelly sandwich, right? I’d take the jar of peanut butter and
place it upon the unopened bag of bread. I’d do the same with the jar of jelly.
This would of course, squish the bread, which feels slightly transgressive given
that you’re messing up the bread, so the kids would love that. I’d then say
something like “the bread is already together, I do not understand this
instruction.”</p>
<p>After the inevitable laughter died down, I’d make my point: the computer will do
<em>exactly</em> what you say, but not what you mean. So you have to get good at
figuring out when you said something different than what you mean. Sort of
ironically, LLMs are kind of the inverse of this: they’ll sometimes try to
figure out what you mean, and then do that, rather than simply doing what you
say. But the core thing here is the same: semantic drift from what we intended
our program to do, and what it actually does.</p>
<p>The second lesson is something I came up with sometime, I don’t even remember
how exactly. But it’s something I told my students a lot. And that’s this:</p>
<p>If your program did everything you wanted without problems, you wouldn’t be
programming: you’d be using your program. The act of programming is itself
perpetually to be in a state where something is either inadequate or broken,
and the job is to fix that.</p>
<p>I also think this is a bit simplistic but also getting at something. I had
originally come up with this in the context of trying to explain how you
need to manage your frustration when programming; if you get easily upset by
something not working, doing computer programming might not be for you. But I do
think these two things combine into something that gets to the heart of what we
do: we need to understand what it is we want our software to do, and then make
it do that. Sometimes, our software doesn’t do something yet. Sometimes, it does
something, but incorrectly. Both of these cases result in a divergence from the
program’s intended behavior.</p>
<p>So, how do we know if our program does what it should do?</p>
<h2 id="gotta-go-fast">Gotta go fast</h2>
<p>Well, what we’ve been doing so far is:</p>
<ol>
<li>Asking the LLM to do something by typing up what we want it to do</li>
<li>Closely observing its behavior and course correcting it when it goes off of
the rails</li>
<li>Eventually, after it says that it’s finished, reviewing its output</li>
</ol>
<p>This is our little mini software development lifecycle, or “SDLC.” This process
works, but is slow. That’s great for getting the feel of things, but programmers
are process optimizers by trade. One of my favorite tools for optimization is
called <a href="https://www3.cs.stonybrook.edu/~rezaul/Spring-2012/CSE613/reading/Amdahl-1967.pdf">Amdahl’s law</a>. The core idea is this, formulated in my own
words:</p>
<blockquote>
<p>If you have a process that takes multiple steps, and you want to speed it up,
if you optimize only one step, the maximum amount of speedup you’ll get is
determined by the portion of the process that step takes.</p>
</blockquote>
<p>In other words, imagine we have a three step process:</p>
<ol>
<li>One minute</li>
<li>Ten minutes</li>
<li>Two minutes</li>
</ol>
<p>This process takes a total of 13 minutes to complete. If we speed up step 3 by
double, it goes from two minutes to one minute, and now our process takes 12
minutes. However, if we were able to speed up step 2 by double, we’d cut off
five minutes, and our process would now take 8 minutes.</p>
<p>We can use this style of analysis to guide our thinking in many ways, but the
most common way, for me, is to decide where to put my effort. Given the process
above, I’m going to look at step 2 first to try and figure out how to make it
faster. That doesn’t mean we can achieve the 2x speedup, but heck, if we get a
10% decrease in time, that’s the same time as if we did get a 2x on step 3. So
it’s at least the place where we should start.</p>
<p>I chose the above because, well, I think it properly models the proportion of
time we’re taking when doing things with LLMs: we spend some time asking it to
do something, and we spend a bit more time reviewing its output. But we spend a
<em>lot</em> of time clicking “accept edit,” and a lot of time allowing Claude to
execute tools. This will be our next step forward, as this will increase our
velocity when working with the tools significantly. However, like with many
optimization tasks, this is easier said than done.</p>
<p>The actual mechanics of improving the speed of this step are simple at first:
hit <code>shift-tab</code> to auto-accept edits, and “Yes, and don’t ask again for <code><cmd></code>
commands” when you think the <code><cmd></code> is safe for Claude to run. By doing this,
once you have enough commands allowed, your input for step 2 of our development
loop can drop to zero. Of course, it takes time for Claude to actually implement
what you’ve asked, so it’s not like our 13 minute process drops to three, but
still, this is a major efficiency step.</p>
<p>But we were actively monitoring Claude for a reason. Claude will sometimes do
incorrect things, and we need to correct it. At some point, Claude will say
“Hey I’ve finished doing what you asked of me!” and it doesn’t matter how fast
it does step 2 if we get to step 3 and it’s just incorrect, and we need to
throw everything out and try again.</p>
<p>So, how do we get Claude to guide itself in the right direction?</p>
<h2 id="lets-start-at-the-end">Let’s start at the end</h2>
<p>A useful technique for figuring out what you should do is to consider the
ending: where do we want to go? That will inform what we need to do to get
there. Well, the ending of step 2 is knowing when to transition to step 3. And
that transition is gated by “does the software do what it is supposed to do?”
That’s a huge question! But in practice, we can do what we always do: start
simple, and iterate from there. Right now, the transition from step 2 to step 3
is left up to Claude. Claude will use its own judgement to decide when it thinks
that the software is working. And it’ll be right. But why leave that up to
chance?</p>
<p>I expect that some of you are thinking that maybe I’m belaboring this point.
“Why not just skip to <code>cargo test</code>? That’s the idea, right? We need tests.” Well
on some level: yes. But on another level, no. I’m trying to teach you how to
think here, not give you the answer. Because it might be broader than just “run
the tests.” Maybe you are working on a project where the tests aren’t very good
yet. Maybe you’re working on a behavior that’s hard to automatically test.
Maybe the test suite takes a very long time, and so isn’t appropriate to be
running over and over and over.</p>
<p>Remember our plan from the last post? Where Claude finished the plan with this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span> Verification</span></span>
<span class="line"><span></span></span>
<span class="line"><span> 1. Run cargo build to ensure it compiles</span></span>
<span class="line"><span> 2. Run `cargo run -- version` to verify it prints "task 0.1.0"</span></span>
<span class="line"><span> 3. Run `cargo run -- --help` to verify help output works</span></span></code></pre>
<p>These aren’t “tests” in the traditional sense of a test suite, but they <em>are</em>
objective measures that Claude can invoke itself to understand if it’s finished
the task. Claude could run <code>cargo run -- version</code> after every file edit if it
wanted to, and as soon as it sees <code>task 0.1.0</code>, it knows that it’s finished.
You don’t <em>need</em> a comprehensive test suite. You just need <em>some</em> sort of way
for Claude to detect if it’s done in some sort of objective fashion.</p>
<p>Of course, we can do better.</p>
<h2 id="improving-from-there">Improving from there</h2>
<p>While giving Claude a way to know if it’s done working is important, there’s a
second thing we need to pay attention to: when Claude isn’t done working, can we
guide it towards doing the right thing, rather than the wrong thing?</p>
<p>For example, those of you who are of a similar vintage as myself may remember the
output of early compilers. It was often… not very helpful. Imagine that we told
Claude that it should run <code>make test</code> to know if things are working, and the only
output from it was the exit code: 0 if we succeeded, 1 if we failed. That would
accomplish our objective of letting Claude know when things are done, but it
wouldn’t help Claude know what went wrong when it returns 1.</p>
<p>This is one reason why I think Rust works well with LLMs. Take this incorrect Rust
program:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> y</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>The Rust compiler won’t just say “yeah this program is incorrect,” it’ll give you
this (as of Rust 1.93.0):</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>error: expected `;`, found `}`</span></span>
<span class="line"><span> --> src/main.rs:2:10</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>2 | let y</span></span>
<span class="line"><span> | ^ help: add `;` here</span></span>
<span class="line"><span>3 | }</span></span>
<span class="line"><span> | - unexpected token</span></span>
<span class="line"><span></span></span>
<span class="line"><span>error[E0282]: type annotations needed</span></span>
<span class="line"><span> --> src/main.rs:2:9</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>2 | let y</span></span>
<span class="line"><span> | ^</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>help: consider giving `y` an explicit type</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>2 | let y: /* Type */</span></span>
<span class="line"><span> | ++++++++++++</span></span>
<span class="line"><span></span></span>
<span class="line"><span>For more information about this error, try `rustc --explain E0282`.</span></span></code></pre>
<p>The compiler will point out the exact place in the code itself of where there’s
an issue, and even make suggestions as to how to fix it. This goes beyond just
simply saying “it doesn’t work” and instead nudges you to what might fix the
problem. Of course, this isn’t perfect, but if it’s helpful more than not,
that’s a win.</p>
<p>Of course, too much verbosity isn’t helpful either. A lot of tooling has
gotten much more verbose lately. Often times, this is really nice as a human.
Pleasant terminal output is, well… pleasant. But that doesn’t mean that it’s
always good or useful. For example, here’s the default output for <code>cargo test</code>:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>running 3 tests</span></span>
<span class="line"><span>test bar ... ok</span></span>
<span class="line"><span>test baz ... ok</span></span>
<span class="line"><span>test foo ... ok</span></span>
<span class="line"><span></span></span>
<span class="line"><span>test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span></span></code></pre>
<p>This is not bad output. It’s nice. But it’s also not useful for an LLM. We don’t
need to read all of the tests that are passing, we really just want to see some
sort of minimal output, and then what failed if something failed. In Cargo’s case,
that’s <code>cargo test -q</code> for “quiet”:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>running 3 tests</span></span>
<span class="line"><span>...</span></span>
<span class="line"><span>test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span></span></code></pre>
<p>There is no point in giving a ton of verbose input to an LLM that it isn’t even
going to need to use. If you’re feeding a tools’ output to an LLM, you should
consider both what the tool does in the failure case, but also the success case.
Maybe configure things to be a bit simpler for Claude. You’ll save some tokens
and get better results.</p>
<p>All of this has various implications for all sorts of things. For example, types
are a great way to get quick feedback on what you’re doing. A comprehensive test
suite that completes quickly is useful for giving feedback to the LLM. But that
also doesn’t inherently mean that types must be better or that you need to be doing
TDD; whatever gives you that underlying principle of “objective feedback for the
success case and guidance for the failure case” will be golden, no matter what
tech stack you use.</p>
<h2 id="whats-good-for-the-goose-human-is-good-for-the-gander-llm">What’s good for the <del>goose</del> human is good for the <del>gander</del> LLM</h2>
<p>This brings me to something that may be counter-intuitive, but I think is also
true, and worth keeping in the back of your mind: what’s good for Claude is also
probably good for humans working on your system. A good test suite was
considered golden before LLMs. That it’s great for them is just a nice
coincidence.</p>
<p>At the end of the day, Claude is not a person, but it tackles programming
problems in a similar fashion to how we do: take in the problem, attempt a
solution, run the compiler/linter/tests, and then see what feedback it gets,
then iterate. That core loop is the same, even if humans can exercise better
judgement and can have more skill. And so even though I pitched fancy terminal
output as an example of how humans and LLMs need different things, that’s really
just a superficial kind of thing. Good error messages are still critical for
both. We’re just better at having terminal spinners not take up space in our
heads while we’re solving a problem, and can appreciate the aesthetics in a way
that Claude does not.</p>
<p>Incidentally, this is one of the things that makes me hopeful about the future
of software development under agentic influence. Engineers always complain that
management doesn’t give us time to do refactorings, to improve the test suite,
to clean our code. Part of the reason for this is that we often didn’t do a good
job of pitching how it would actually help accomplish business goals. But even
if you’re on the fence about AI, and upset that management is all about AI:
explain to management that this stuff is a force multiplier for your agents.
Use the time you’ve saved by doing things the agentic way towards improving
your test suite, or your documentation, or whatever else. I think there’s a chance
that all of this stuff leads to higher quality codebases than ones filled with
slop. But it also requires us to make the decisions that will lead is in that
direction.</p>
<h2 id="in-conclusion">In conclusion</h2>
<p>That’s what I have for you today: consider how you can help Claude evaluate its
own work. Give it explicit success criteria, and make evaluating that criteria
as simple and objective as possible.</p>
<p>In the next post, we’re gonna finally talk about <code>Claude.md</code>. Can you believe
that I’ve talked this much about how to use Claude and we haven’t talked about
<code>Claude.md</code>? There’s good reason for that, as it turns out. We’re going to talk
a bit more about understanding how interacting with LLMs work, and how it can
help us both improve step 1 in our process, but also continue to make step 2
better and better.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mdg6nftkm22k" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Steve Klabnik</span></bdi> <span class="handle s-12mzi62">@steveklabnik.com</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcznckciyk2e" title="January 22, 2026 at 4:48 PM" class="date s-12mzi62"><time datetime="2026-01-22T16:48:55.938Z">Jan 22</time></a></div> <p class="context s-12mzi62">Replying to <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" dir="auto" class="s-12mzi62">Steve Klabnik</a></p> <p class="rich-text is-small s-1lecfnd">Agentic development basics: <a target="_blank" href="https://steveklabnik.com/writing/agentic-development-basics/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/agen...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/agentic-development-basics/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Agentic development basics</p> <p class="description s-rtbqd8">Blog post: Agentic development basics by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div></div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> </div> <p class="rich-text is-large s-1lecfnd">The most important thing when working with LLMs
<a target="_blank" href="https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/the-...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">The most important thing when working with LLMs</p> <p class="description s-rtbqd8">Blog post: The most important thing when working with LLMs by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-01-27T16:31:10.824Z" class="date s-hik11q">January 27, 2026 at 4:31 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="35 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>35</span></span> <span class="stat s-hik11q" title="4 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>4</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mdg6nftkm22k" target="_blank" class="permalink s-hik11q"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Agentic development basics</title><link>https://steveklabnik.com/writing/agentic-development-basics/</link><guid isPermaLink="true">https://steveklabnik.com/writing/agentic-development-basics/</guid><description>In my last post, I suggested that you should start using Claude in your software development process via read-only means at first. The idea is just to get used to interacting with the AI, seeing what it can do, and seeing what it struggles with. Once you’ve got a handle on that…</description><pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>In my last post, I suggested that you should start using Claude in your software
development process via read-only means at first. The idea is just to get used
to interacting with the AI, seeing what it can do, and seeing what it struggles
with.</p>
<p>Once you’ve got a handle on that part, it’s time to graduate to writing code.
However, I’m going to warn you about this post: I hope that by the end of it,
you’re a little frustrated. This is because I don’t think it’s productive to
skip to the tools and techniques that experienced users use yet. We have to
walk before we run. And more importantly, we have to understand how and why we
run. That is, I hope that this step will let you start producing code with
Claude, but it will also show you some of the initial pitfalls when doing so, in
order to motivate the techniques you’re going to learn about in part 3.</p>
<p>So with that in mind, let’s begin.</p>
<h2 id="step-0-version-control">Step 0: version control</h2>
<p>Okay I lied. Before we actually begin: you are using version control, right? If
not, you may want to go learn a bit about it. Version control, like git (or my
beloved jj) is pretty critical for software development, but it’s in my opinion
even <em>more</em> critical for this sort of development. You really want to be able to
restore to previous versions of the code, branch off and try things, and recover
from mistakes. If you already use version control systems religiously, you might
use this as an excuse to learn even more features of them. I never bothered with
<code>git worktree</code>s in the past, but I use <code>jj workspace</code>s with agents all the time
now.</p>
<h2 id="step-1-mindset-shift-stop-writing-code">Step 1: Mindset shift: stop writing code</h2>
<p>Okay, here’s my first bit of advice: commit yourself to not writing code any
more. I don’t mean forever, I don’t mean all the time, I mean, while you’re
trying to learn agentic software development, on the project that you’re
learning it with, just don’t write any code manually.</p>
<p>This might be a bit controversial! However, I think it is essential. Let me tell
you a short story. Many years ago, I was in Brazil. I wanted to try scuba diving
for the first time. Seemed like a good opportunity. Now, I don’t remember the
exact setup, but I do remember the hardest part for me. Our instructor
told us to put the mask on, and then lean forward and put our faces in the water
and breathe through the regulator. I simply could not do it. I got too in my
head, it was like those “you are now breathing manually” memes. I forget if it
was my idea or the instructor’s idea, but what happened in practice: I just
jumped in. My brain <em>very quickly</em> went from “but how do I do this properly” to
“oh God, if you don’t figure this out right the fuck now you’re gonna fuckin die
idiot” and that’s exactly what I needed to do it. A few seconds later, I was
breathing just fine. I just needed the shock to my system, I needed to commit.
And I figured it out.</p>
<p>Now, I’m turning 40 and this happened long ago, and it’s reached more of a
personal myth status in my head, so maybe I got the details wrong, maybe this is
horribly irresponsible and someone who knows about diving can tell me if that
experience was wrong, but the point has always stuck with me: sometimes, you
just gotta go for it.</p>
<p>This dovetails into another part I snuck into there: “on the project that you’re
learning it with.” I really think that you should undergo a new project for this
endeavor. There’s a few reasons for this:</p>
<ol>
<li>You can be less precious about the code. This isn’t messing up one of your
projects, this is a throwaway scratch thing that doesn’t matter.</li>
<li>“AI does better on greenfield projects” is not exactly true, but there’s
enough truth to it that I think you should do a new project. It’s really more
about secondary factors than it is actual greenfield vs brownfield
development but whatever, doesn’t matter: start new.</li>
</ol>
<p>Pick something to get started with, and create a new repo. I suggest something
you’ve implemented before, or maybe something you know how to do but have never
bothered to take the time to actually build. A small CLI tool might be a good
idea. Doesn’t super matter what it is.</p>
<p>For the purposes of this example, I’m going to build a task tracking CLI
application. Because there aren’t enough of those in the world.</p>
<h2 id="step-2-your-first-few-steps-with-claude">Step 2: Your first few steps with Claude</h2>
<p>I recommend making a new fresh directory, and initializing the project of your
choice. I’m using Rust, of course, so I’ll <code>cargo new tasks && cd tasks</code>. You
can make Claude do it, but I don’t think that starting from an initialized
project is a bad idea either. I’m more likely to go the <code>cargo new</code> route if I
know I’m building something small, and more of the “make Claude do it” route if
I’m doing like, a web app with a frontend, backend, and several services. Anyway
point is: get your project to exist, and then just run Claude Code. I guess you
should <a href="https://code.claude.com/docs/en/quickstart">install it first</a>, but anyway, you can just</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>$ claude</span></span></code></pre>
<p>to get started. At the time of writing, Claude will ask you if you trust the
files in this folder, you want to say yes, because you just created it. You’ll
also get some screens asking if you want to do light or dark mode, to log in
with your Anthropic account, stuff like that.</p>
<p>And then you’ll be in.</p>
<p>Claude will ask you to run <code>/init</code> to create a CLAUDE.md, but we’re not gonna do
that at the start. We need to talk about even more basic things than that first.</p>
<p>You’ll be at a prompt, and it’ll even suggest something for you to get started
with. Mine says “Try “fix typecheck errors"" at the moment. We’re gonna try to get
Claude to modify a few lines of our program. The <code>cargo new</code> in Rust produces
this program:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"Hello, world!"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>so I’ll ask Claude this:</p>
<blockquote>
<p>Hi claude! right now my program prints “Hello, world”, can we have it print
“Goodbye, world” instead?</p>
</blockquote>
<p>Claude does this for me:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● I'll make that change for you.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Read(src/main.rs)</span></span>
<span class="line"><span> ⎿ Read 4 lines</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Update(src/main.rs)</span></span></code></pre>
<p>And then asks this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span> Edit file src/main.rs</span></span>
<span class="line"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>
<span class="line"><span> 1 fn main() {</span></span>
<span class="line"><span> 2 - println!("Hello, world!");</span></span>
<span class="line"><span> 2 + println!("Goodbye, world!");</span></span>
<span class="line"><span> 3 }</span></span>
<span class="line"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>
<span class="line"><span> Do you want to make this edit to main.rs?</span></span>
<span class="line"><span> ❯ 1. Yes</span></span>
<span class="line"><span> 2. Yes, allow all edits during this session (shift+tab)</span></span>
<span class="line"><span> 3. No</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Esc to cancel · Tab to add additional instructions</span></span></code></pre>
<p>Claude wants to edit a file, and so by default, it has to ask us permission to
do so. This is a terrible place to end up, but a great place to get started! We
want to use these prompts at first to understand what Claude is doing, and sort
of “code review” it as we go. More on that in a bit. Anyway, this is why you
should answer “Yes” to this question, and not “Yes, allow all edits during this
session.” We want to keep reviewing the code for now. You want to be paying
close attention to what Claude is doing, so you can build up some intuition
about it.</p>
<p>Before clicking yes, I want to talk about what Claude did in my case here. Note
that my prompt said</p>
<blockquote>
<p>right now my program prints “Hello, world”, can we have it print “Goodbye,
world”</p>
</blockquote>
<p>Astute observers will have noticed that it actually says <code>Hello, world!</code> and not
<code>Hello, world</code>. We also asked it to have it say <code>Goodbye, world</code> and it is
showing a diff that will make it say <code>Goodbye, world!</code>. This is a tiny
different, but it is also important to understand: Claude is going to try and
figure out what you mean and do that. This is both the source of these tools’
power and also the very thing that makes it hard to trust them. In this case,
Claude was right, I didn’t type the exact string when describing the current
behavior, and I didn’t mean to remove the <code>!</code>.</p>
<p>In the previous post, I said that you shouldn’t be mean to Claude. I think it
makes the LLM perform worse. So now it’s time to talk about your own reaction to
the above: did you go “yeah Claude fucked up it didn’t do exactly what I asked?”
or did you go “yeah Claude did exactly what I asked?” I think it’s important to
try and let go of preconceived notions here, especially if your reaction here
was negative. I know this is kind of woo, just like “be nice to Claude,” but you
have to approach this as “this is a technology that works a little differently
than I’m used to, and that’s why I’m learning how to meet it on its own terms”
rather than “it didn’t work the way I expected it to, so it is wrong.” A non-woo
way of putting it is this: the right way to approach “it didn’t work” in this
context is “that’s a skill issue on my part, and I’m here to sharpen my skills.”
Yes, there are limits to this technology and it’s not perfect. That’s not the
point. You’re not doing that kind of work right now, you’re doing <code>sed</code>.</p>
<p>Now, I should also say that like, if you don’t want to learn a new tool? 100%
okay with me. Learned some things about a tool, and didn’t like it? Sure! Some
of you won’t like agentic development. That’s okay. No worries, thanks for
reading, have a nice day. I mean that honestly. But for those folks who do want
to learn this, I’m trying to communicate that I think you’ll have a better time
learning it if you try to get into the headspace of “how do I get the results I
want” rather than getting upset and giving up when it doesn’t work out.</p>
<p>Okay, with that out of the way, if you asked a small enough question, Claude
probably did the right thing. Let’s accept.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Done. Your program will now print "Goodbye, world!" instead.</span></span></code></pre>
<h2 id="planning">Planning</h2>
<p>This might be a good time to commit & save your progress. You can use <code>!</code> to put
Claude Code into “bash mode” and run commands, so I just <code>!jj commit -m "goodbye world"</code> and I’m good. You can also use another terminal, I just figured I’d let
you know. It’s good for short commands.</p>
<p>Let’s try something bigger. To do that, we’re gonna invoke something called
“plan mode.” Claude Code has three (shhhh, we don’t talk about the fourth yet)
modes. The first one is the “ask to accept edits” mode. But if you hit
<code>shift+tab</code>, you’ll see <code>⏵⏵ accept edits on (shift+tab to cycle)</code> at the bottom
left. We don’t want to automatically accept edits. Hit <code>shift+tab</code> again, and
you’ll see this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>⏸ plan mode on (shift+tab to cycle)</span></span></code></pre>
<p>This is what we want. Plan mode. Plan mode is useful any time you’re doing work
that’s on the larger side, or just when you want to think through something
before you begin. In plan mode, Claude cannot modify your files until you accept
the plan. With plan mode, you talk to Claude about what you want to do, and you
collaborate on a plan together. A nice thing about it is that you can
communicate the things you are sure of, and also ask about the things you’re not
sure of. So let’s kick off some sort of plan to build the most baby parts of our
app. In my case, I’m prompting it with this:</p>
<blockquote>
<p>hi claude! i want this application to grow into a task tracking app. right
now, it’s just hello world. I’d like to set up some command line argument
parsing infrastructure, with a <code>task version</code> command that prints the version.
can we talk about that?</p>
</blockquote>
<p>Yes, I almost always type <code>hi claude!</code>, feel free to not. And I always feel like
a “can we talk about that” on the end is nice too. I try to talk to Claude the
way I’d talk to a co-worker. Obviously this would be too minor of a thing to
bother to talk to a co-worker about, but like, you know, baby steps. Note that
what I’m asking is basically a slightly more complex “hello world”, just getting
some argument parsing up. You want something this sized: you know how it should
be done, it should be pretty simple, but it’s not a fancy <code>sed</code> command.</p>
<p>With plan mode, Claude will end up responding to this by taking a look at your
project, considering what you might need, and then coming up with a plan. Here’s
the first part of Claude’s answer to me:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Sure! This is a great foundation to build on. Since the codebase is minimal right now (just the hello world in src/main.rs), I</span></span>
<span class="line"><span> can skip extensive exploration and go straight to planning.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> For CLI argument parsing in Rust, the standard choice is clap - it's the most popular library with excellent subcommand</span></span>
<span class="line"><span> support, derive macros for clean code, and built-in version/help handling. This will scale well as you add more commands like</span></span>
<span class="line"><span> task add, task list, etc.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Let me write up a plan for you.</span></span></code></pre>
<p>It’ll then come up with a plan, and it usually write it out to a file somewhere:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Updated plan</span></span>
<span class="line"><span> ⎿ Wrote 56 lines to ../../.claude/plans/shiny-dancing-cray.md</span></span>
<span class="line"><span> # Plan: CLI Argument Parsing Infrastructure</span></span>
<span class="line"><span> ## Goal</span></span>
<span class="line"><span> Set up command line argument parsing for the task tracking app with a `task version` subcommand.</span></span>
<span class="line"><span> ## Approach</span></span>
<span class="line"><span> Use **clap** with derive macros - the standard Rust CLI library.</span></span>
<span class="line"><span> ## Changes</span></span>
<span class="line"><span> … +46 lines (ctrl+o to expand)</span></span></code></pre>
<p>You can go read the file if you want to, but it’s not needed. Claude is going to
eventually present the plan to you directly, and you’ll review it before moving on.
Claude will also probably ask you a question or maybe even a couple, depending.
There’s a neat little TUI for responding to its questions, it can even handle
multiple questions at once:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● I've drafted a plan using clap with derive macros. Before finalizing, I have one question:</span></span>
<span class="line"><span></span></span>
<span class="line"><span>────────────────────────────────────────────────────────────────────────────────────────────</span></span>
<span class="line"><span> ☐ CLI Library</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Which CLI parsing library would you prefer?</span></span>
<span class="line"><span></span></span>
<span class="line"><span>❯ 1. clap (Recommended)</span></span>
<span class="line"><span> Most popular, feature-rich, excellent subcommand support, built-in help/version</span></span>
<span class="line"><span> 2. argh</span></span>
<span class="line"><span> Simpler and lighter, derive-based, from Google</span></span>
<span class="line"><span> 3. Manual parsing</span></span>
<span class="line"><span> Use std::env::args directly, no dependencies, more boilerplate</span></span>
<span class="line"><span> 4. Type something.</span></span>
<span class="line"><span>────────────────────────────────────────────────────────────────────────────────────────────</span></span>
<span class="line"><span> Chat about this</span></span>
<span class="line"><span> Skip interview and plan immediately</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Enter to select · ↑/↓ to navigate · Esc to cancel</span></span></code></pre>
<p>For those of you that don’t write Rust, this is a pretty good response! Clap is
the default choice in the ecosystem, arg is a decent option too, and doing it
yourself is always possible. I’m going to choose clap, it’s great. If you’re not
sure about the question, you can arrow down to “Chat about this” and discuss it
more.</p>
<p>Here’s why you don’t need to read the file: Claude will pitch you on its plan:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● User answered Claude's questions:</span></span>
<span class="line"><span> ⎿ · Which CLI parsing library would you prefer? → clap (Recommended)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Clap it is. The plan is ready - it adds clap with derive macros and sets up the subcommand structure with task version as the</span></span>
<span class="line"><span> first command. This gives you a solid foundation to add more commands later.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span></span>
<span class="line"><span> Ready to code?</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Here is Claude's plan:</span></span>
<span class="line"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>
<span class="line"><span> Plan: CLI Argument Parsing Infrastructure</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Goal</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Set up command line argument parsing for the task tracking app with a task version subcommand.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Approach</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Use clap with derive macros - the standard Rust CLI library.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Changes</span></span>
<span class="line"><span></span></span>
<span class="line"><span> 1. Update Cargo.toml</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Add clap dependency with derive feature:</span></span>
<span class="line"><span> [dependencies]</span></span>
<span class="line"><span> clap = { version = "4", features = ["derive"] }</span></span>
<span class="line"><span></span></span>
<span class="line"><span> 2. Update src/main.rs</span></span>
<span class="line"><span></span></span>
<span class="line"><span> - Define a Cli struct with clap derive macros</span></span>
<span class="line"><span> - Define a Commands enum for subcommands (starting with Version)</span></span>
<span class="line"><span> - Implement the version subcommand to print the version from Cargo.toml</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Structure:</span></span>
<span class="line"><span> use clap::{Parser, Subcommand};</span></span>
<span class="line"><span></span></span>
<span class="line"><span> #[derive(Parser)]</span></span>
<span class="line"><span> #[command(name = "task")]</span></span>
<span class="line"><span> #[command(about = "A task tracking application")]</span></span>
<span class="line"><span> struct Cli {</span></span>
<span class="line"><span> #[command(subcommand)]</span></span>
<span class="line"><span> command: Commands,</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span></span></span>
<span class="line"><span> #[derive(Subcommand)]</span></span>
<span class="line"><span> enum Commands {</span></span>
<span class="line"><span> /// Print version information</span></span>
<span class="line"><span> Version,</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span></span></span>
<span class="line"><span> fn main() {</span></span>
<span class="line"><span> let cli = Cli::parse();</span></span>
<span class="line"><span></span></span>
<span class="line"><span> match cli.command {</span></span>
<span class="line"><span> Commands::Version => {</span></span>
<span class="line"><span> println!("task {}", env!("CARGO_PKG_VERSION"));</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Verification</span></span>
<span class="line"><span></span></span>
<span class="line"><span> 1. Run cargo build to ensure it compiles</span></span>
<span class="line"><span> 2. Run ./target/debug/tasks version to verify it prints "task 0.1.0"</span></span>
<span class="line"><span> 3. Run ./target/debug/tasks --help to verify help output works</span></span>
<span class="line"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Would you like to proceed?</span></span>
<span class="line"><span></span></span>
<span class="line"><span> ❯ 1. Yes, clear context and auto-accept edits (shift+tab)</span></span>
<span class="line"><span> 2. Yes, auto-accept edits</span></span>
<span class="line"><span> 3. Yes, manually approve edits</span></span>
<span class="line"><span> 4. Type here to tell Claude what to change</span></span>
<span class="line"><span></span></span>
<span class="line"><span> ctrl-g to edit in VS Code · ~/.claude/plans/shiny-dancing-cray.md</span></span></code></pre>
<p>This is pretty good! Now, if you like this plan, you can select #3. Remember,
we’re not auto accepting just yet! Don’t worry about the difference between 1
and 2 now, we’ll talk about it someday. But, I actually want Claude to tweak
this plan, I wouldn’t run <code>./target/debug/tasks version</code>, I would do <code>cargo run -- version</code>. So I’m going to go down to four and type literally that to Claude:</p>
<blockquote>
<p>I wouldn’t run <code>./target/debug/tasks version</code>, I would do <code>cargo run -- version</code>.</p>
</blockquote>
<p>And Claude replies:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Good point - let me update the verification section.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Updated plan</span></span>
<span class="line"><span> ⎿ /plan to preview</span></span>
<span class="line"><span> 51</span></span>
<span class="line"><span> 52 ## Verification</span></span>
<span class="line"><span> 53 1. Run `cargo build` to ensure it compiles</span></span>
<span class="line"><span> 54 -2. Run `./target/debug/tasks version` to verify it prints "task 0.1.0"</span></span>
<span class="line"><span> 55 -3. Run `./target/debug/tasks --help` to verify help output works</span></span>
<span class="line"><span> 54 +2. Run `cargo run -- version` to verify it prints "task 0.1.0"</span></span>
<span class="line"><span> 55 +3. Run `cargo run -- --help` to verify help output works</span></span></code></pre>
<p>and then presents the menu again. See, this is where the leverage of “Claude
figures out what I mean” can be helpful: I only told it about <code>version</code>, but it
also changed to <code>cargo run -- --help</code> as well. However, there’s a drawback too:
we didn’t tell Claude we wanted to have help output! However, this is also a
positive: Claude considered help output to be so basic that it’s suggesting it
for our plan. It’s up to you to decide if this is an overreach on Claude’s part.
In my case, I’m okay with it because it’s so nearly automatic with Clap and it’s
something I certainly want in my tool, so I’m going to accept this plan.</p>
<p>Iterate with Claude until you’re happy with the plan, and then do the same.</p>
<p>I’m not going to paste all of the diffs here, but for me, Claude then went and
did the plan: it added the dependency to <code>Cargo.toml</code>, it added the needed code
to <code>src/main.rs</code>, it ran <code>cargo build</code> to try and do the build. Oh yeah, here’s
a menu we haven’t seen yet:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span> Bash command</span></span>
<span class="line"><span></span></span>
<span class="line"><span> cargo build 2>&1</span></span>
<span class="line"><span> Build the Rust project</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Do you want to proceed?</span></span>
<span class="line"><span> ❯ 1. Yes</span></span>
<span class="line"><span> 2. Yes, and don't ask again for cargo build commands in /home/stat/tmp/tasks</span></span>
<span class="line"><span> 3. No</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Esc to cancel · Tab to add additional instructions</span></span></code></pre>
<p>This is a more interesting question than the auto-edit thing. Claude won’t run
commands without you signing off on them. If you’re okay with letting Claude run
this command every time without asking, you can choose 2, and if you want to
confirm every time, you can type 1. Completely up to you.</p>
<p>Now it ran <code>cargo run -- version</code>, and <code>cargo run -- --help</code>. Everything looked
good, so I see:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Done. The CLI infrastructure is set up:</span></span>
<span class="line"><span></span></span>
<span class="line"><span> - cargo run -- version outputs task 0.1.0</span></span>
<span class="line"><span> - cargo run -- --help shows the help with available commands</span></span>
<span class="line"><span></span></span>
<span class="line"><span> You now have a foundation to add more subcommands to the Commands enum as you build out the task tracking features.</span></span></code></pre>
<p>And that’s it, we’ve built our first feature! Yeah, it’s pretty small, and in
this case, we probably could have copy/pasted the documentation, but again,
that’s not the point right now: we’re just trying to take very small steps
forward to get used to working with the tool. We <em>want</em> it to be something
that’s quick for us to verify. We are spending more time here than we would if
we did it by hand, because that time isn’t wasted: it’s time learning. As we
ramp up the complexity of what we can accomplish, we’ll start seeing speed
gains. But we’re deliberately going slow and doing little right now.</p>
<h2 id="stretch-your-legs">Stretch your legs</h2>
<p>From here, that’s exactly what I’d like you to do: figure out where the limits
are. Try something slightly larger, slightly harder. Try to do stuff with just a
prompt, and then throw that commit away and try it again with plan mode. See
what stuff you need plan mode for, and what you can get away with with just a
simple prompt.</p>
<p>I’ll leave you with an example of a prompt I might try next: asking Cladue for
their opinion on what you should do. The next thing I’d do in this project is to
switch back into planning mode and ask this:</p>
<blockquote>
<p>what do you think is the best architecture for this tracking app? we haven’t
discussed any real features, design, or requirements. this is mostly a little
sample application to practice with, and so we might not need a real database,
we might get away with a simple file or files to track todos. but maybe
something like sqlite would still be appropriate. if we wanted to implement a
next step for this app, what do you think it should be?</p>
</blockquote>
<p>Here’s what Claude suggested:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>● Great question - this is the kind of design discussion that's valuable before diving into implementation. Let me share my</span></span>
<span class="line"><span> thoughts.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Storage: File vs SQLite</span></span>
<span class="line"><span></span></span>
<span class="line"><span> For a practice app, I'd recommend starting with a simple JSON file:</span></span>
<span class="line"><span></span></span>
<span class="line"><span> - No additional dependencies - just serde + serde_json</span></span>
<span class="line"><span> - Human-readable - you can inspect/edit ~/.tasks.json directly</span></span>
<span class="line"><span> - Focus on app logic - learn clap, error handling, and Rust patterns without database complexity</span></span>
<span class="line"><span> - Easy to migrate later - if you outgrow it, moving to SQLite is a good exercise in itself</span></span>
<span class="line"><span></span></span>
<span class="line"><span> SQLite is great but adds cognitive load (SQL, migrations, connection handling). For a practice app, that complexity might</span></span>
<span class="line"><span> distract from the core learning.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Recommended Next Step</span></span>
<span class="line"><span></span></span>
<span class="line"><span> The natural progression is the core CRUD operations. I'd suggest implementing task add next:</span></span>
<span class="line"><span></span></span>
<span class="line"><span> task add "Buy groceries"</span></span>
<span class="line"><span></span></span>
<span class="line"><span> This introduces:</span></span>
<span class="line"><span> - Taking arguments in subcommands (clap)</span></span>
<span class="line"><span> - Reading/writing a data file (serde)</span></span>
<span class="line"><span> - Defining your Task struct (what fields? id, description, created_at, completed?)</span></span>
<span class="line"><span> - Error handling patterns</span></span>
<span class="line"><span></span></span>
<span class="line"><span> After that: task list, then task done <id>, then task remove <id>.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span></span>
<span class="line"><span> ☐ Next step</span></span>
<span class="line"><span></span></span>
<span class="line"><span>What would you like to tackle next?</span></span>
<span class="line"><span></span></span>
<span class="line"><span>❯ 1. task add (Recommended)</span></span>
<span class="line"><span> Add a task with a description, stored in a JSON file</span></span>
<span class="line"><span> 2. task list first</span></span>
<span class="line"><span> Set up storage and list (empty at first), then add</span></span>
<span class="line"><span> 3. Use SQLite instead</span></span>
<span class="line"><span> Start with SQLite from the beginning for the learning experience</span></span>
<span class="line"><span> 4. Type something.</span></span>
<span class="line"><span>──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span></span>
<span class="line"><span> Chat about this</span></span>
<span class="line"><span> Skip interview and plan immediately</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Enter to select · ↑/↓ to navigate · Esc to cancel</span></span></code></pre>
<p>This plan is pretty solid. But again, it’s a demo app. The important part is,
you can always throw this away if you don’t like it. So try some things. Give
Claude some opinions and see how they react. Try small features, try something
larger. Play around. But push it until Claude fails.</p>
<h2 id="what-to-do-when-claude-fails">What to do when Claude fails</h2>
<p>At some point, you will run into problems. Maybe you already have! What you
should do depends on the failure mode.</p>
<p>The first failure you’re gonna run into is “I don’t like the code!” Maybe Claude
just does a bad job. You have two options: the first is to just tell Claude to
fix it. Claude made a mess, Claude can clean it up. In more extreme cases, you
may want to just simply <code>jj abandon</code> or <code>git reset --hard</code> and start again.
Honestly, the second approach is better in a lot of cases, but I’m not going to
always recommend it just yet. The reason why it is is that it gives you some
time to reflect on why Claude did a bad job. But we’re gonna talk about that as
the next post in this series! So for now, stick to the ‘worse’ option: just tell
Claude to fix problems you find.</p>
<p>The second kind of failure is one where Claude just really struggles to get
things right. It looks in the wrong places in your codebase, it tries to find a
bug and can’t figure it out, it misreads output and that leads it astray, etc.
This kind of failure is harder to fix with the tools you have available to you
right now. What matters is taking note of them, so that you can email them to
me, haha. I mean, feel free to do that, and I can incorporate specific
suggestions into the next posts, but also, just being able to reflect on what
Claude struggles with is a good idea generally. You’ll be able to fix them
eventually, so knowing what you need to improve matters.</p>
<h2 id="what-is-compaction">What is “compaction”?</h2>
<p>If Claude works long enough, you’ll see something about “compaction.” We haven’t
discussed things at a deep enough level to really understand this yet, so don’t
worry about it! You may want to note one thing though: Claude tends to do worse
after compaction, in my opinion. So one way to think about this is, “If I see
compaction, I’ve tried to accomplish too large a task.” Reflect on if you could
have broken this up into something smaller.</p>
<p>We’ll talk about this more in the next post.</p>
<h2 id="conclusion">Conclusion</h2>
<p>So that’s it! Let Claude write some code, in a project you don’t care about. Try
bigger and harder things until you’re a bit frustrated with its failures. You
will hit the limits, because you’re not doing any of the intermediate techniques
to help Claude do a good job. But my hope is, by running into these issues,
you’ll understand the motivation for those techniques, and will be able to
better apply them in the future.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mcznckciyk2e" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Steve Klabnik</span></bdi> <span class="handle s-12mzi62">@steveklabnik.com</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mbu3gbqrwk2y" title="January 7, 2026 at 6:20 PM" class="date s-12mzi62"><time datetime="2026-01-07T18:20:23.515Z">Jan 7</time></a></div> <p class="rich-text is-small s-1lecfnd">Getting started with Claude for software development: <a target="_blank" href="https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/gett...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Getting started with Claude for software development</p> <p class="description s-rtbqd8">Blog post: Getting started with Claude for software development by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div></div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> </div> <p class="rich-text is-large s-1lecfnd">Agentic development basics: <a target="_blank" href="https://steveklabnik.com/writing/agentic-development-basics/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/agen...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/agentic-development-basics/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Agentic development basics</p> <p class="description s-rtbqd8">Blog post: Agentic development basics by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-01-22T16:48:55.938Z" class="date s-hik11q">January 22, 2026 at 4:48 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="38 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>38</span></span> <span class="stat s-hik11q" title="1 repost"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>1</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcznckciyk2e" target="_blank" class="permalink s-hik11q"><span>Read 6 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>How to think about Gas Town</title><link>https://steveklabnik.com/writing/how-to-think-about-gas-town/</link><guid isPermaLink="true">https://steveklabnik.com/writing/how-to-think-about-gas-town/</guid><description>I want to continue with my series on how you can use Claude Code for software development , but I have at least two posts I need to write first. This post is one of those two. Thanks for all of the kind words you all have said about the first post in the series, I’ll absolutely…</description><pubDate>Thu, 15 Jan 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I want to continue with my <a href="/series/getting-started-with-claude-code">series on how you can use Claude Code for software
development</a>, but I have at least two posts I need to write
first. This post is one of those two. Thanks for all of the kind words you all
have said about the first post in the series, I’ll absolutely be continuing it,
hopefully next week.</p>
<p>With that out of the way… let’s talk about Gas Town. If you’re not familiar,
it’s a project from Steve Yegge that has a lot of people having a lot of
feelings. Steve launched Gas Town just under two weeks ago, and in general, his
posts about it have brought out a reaction that has varied from “what the
fuck?!?” to “I don’t get it.” I’ll link Bryan here, not to pick on him, but sort
of the opposite: I think this is a very normal response to all of this, and
especially from him:</p>
<bluesky-post src="at://did:plc:ucct3fwwarzd7rety6nzq4cc/app.bsky.feed.post/3mcft3omsa226" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:ucct3fwwarzd7rety6nzq4cc/bafkreibajqtfkfkb7tkctsl42q747tekfc4yuek5dhjbe7plc24c7b3rti" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Bryan Cantrill</span></bdi> <span class="handle s-hik11q">@bcantrill.bsky.social</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">WTAF
<a target="_blank" href="https://steve-yegge.medium.com/gas-town-emergency-user-manual-cf0e4556d74b" rel="noopener nofollow" class="link s-1lecfnd">steve-yegge.medium.com/gas-town-eme...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steve-yegge.medium.com/gas-town-emergency-user-manual-cf0e4556d74b" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:ucct3fwwarzd7rety6nzq4cc/bafkreick2ibnbvizxsiafszp6qkaf3gegryrxxfhu2qweaa2ddghl74zwa" alt="" class="thumbnail s-rtbqd8"/> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Gas Town Emergency User Manual</p> <p class="description s-rtbqd8">It’s been a busy 12 days since I launched Gas Town. ️I’ve merged over 100 PRs from nearly 50 contributors, adding 44k lines of code that no…</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steve-yegge.medium.com</span></div></div></a></div> <time datetime="2026-01-14T19:39:13.273Z" class="date s-hik11q">January 14, 2026 at 7:39 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="148 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>148</span></span> <span class="stat s-hik11q" title="32 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>32</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc/post/3mcft3omsa226" target="_blank" class="permalink s-hik11q"><span>Read 30 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>However, I’m also not exactly a normal person:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mcfylftvw22s" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Steve Klabnik</span></bdi> <span class="handle s-12mzi62">@steveklabnik.com</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcfyeyndzs2s" title="January 14, 2026 at 9:13 PM" class="date s-12mzi62"><time datetime="2026-01-14T21:13:54.475Z">Jan 14</time></a></div> <p class="rich-text is-small s-1lecfnd">normal people: gas town is insane and yegge is on drugs
me, problematic french philosophy enjoyer: i dunno this seems not that weird at all
almost going to grad school broke me a little bit, i think</p> </div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> </div> <p class="rich-text is-large s-1lecfnd">"You can use Polecats without the Refinery" is too out there for you?</p> <div class="embeds s-azdpbr"><div class="image-embed is-bordered is-aligned s-t3k6fc"><div class="single-item tl tr bl br is-standalone s-t3k6fc" style="aspect-ratio: 500/522"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreiakmz36gmfjffm3scdr4s3mx5k63d5xjbplfsgi4bbmyjmnki6t4q" alt="you are like a little baby, watch this" class="image s-t3k6fc"/> <div class="placeholder s-t3k6fc"></div></div></div></div> <time datetime="2026-01-14T21:17:29.649Z" class="date s-hik11q">January 14, 2026 at 9:17 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="32 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>32</span></span> <span class="stat s-hik11q" title="1 repost"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>1</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcfylftvw22s" target="_blank" class="permalink s-hik11q"><span>Read 3 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>So, today I’d like to talk about Gas Town, and what I think Yegge is doing.</p>
<h2 id="what-is-gas-town">What is Gas Town?</h2>
<p>Gas town is two things simultaneously: something that’s kind of boring and
obvious, and something that’s opaque and confusing, illegible almost.</p>
<p>If you’ve found yourself saying “what?!?” when reading about Gas Town, this post
is for you.</p>
<p>I want to start with the boring and obvious, and move into the opaque and
illegible.</p>
<h2 id="gas-town-is-inevitable">Gas Town is inevitable</h2>
<p>Okay, so I almost went with “Gas Town is Inevitable” as the title of this post,
but I think that’s a bit too provocative, and not quite accurate. I do think
<em>something like</em> Gas Town was going to be tried by someone, sometime. That’s the
inevitable part. This is because Gas Town isn’t really that novel, actually.</p>
<p>Here’s what Gas Town is: you have a workspace. In that workspace, you have
projects. Each project has a bug tracker, used like normal: feature requests and
bug reports. Gas Town is software that allows you to instruct AI agents to
autonomously knock out those bugs.</p>
<p>That’s it.</p>
<p>This isn’t a fundamentally novel idea. The novel part comes in that it’s agents
doing the work, not humans.</p>
<p>This is why I describe it as “inevitable,” it’s an idea that pretty logically
follows from the idea of “a software agent can successfully complete programming
tasks.” Humans find a big list of tasks to be a useful way to organize work, so
there’s no reason to believe that agents would not. So you follow this idea
wherever it takes you. That’s the experimental part of Gas Town: does this work?
Can it work? We don’t know! Steve is trying.</p>
<p>One level of abstraction deeper, Steve is using some pretty tried and true ideas
here from Erlang. There’s supervisor trees, mailboxes, stuff like that. This is
a pretty solid base to work from! Erlang is legendary in these spaces for a reason.</p>
<p>Anyway yeah. That’s really just it. Unless you’re particularly interested in
this topic or question, that’s really all you need to know about Gas Town to
understand it.</p>
<p>So why all of the weird terminology?</p>
<h2 id="opacity-and-community-building">Opacity and community building</h2>
<p>AI is weird. The tools are very different than we’re used to. If 2025 was any
measure, 2026 is going to be a very interesting year for this profession. I know
I’m personally re-thinking a lot of sacred cows that I’ve worshipped for my
whole career. I don’t think all of them are bad, but I do think that things are
weird enough that it’s worth trying to figure things out from first principles
again.</p>
<p>And that’s where I think Steve is at too.</p>
<p>And I’ll also be honest, I’ve been slowly working myself into thinking about all
of this stuff too. I even have my own little project I started this week,
<a href="https://github.com/steveklabnik/docket">docket</a>, which is sort of one step down a road that Gas Town is many
miles down already. I wasn’t going to talk about Docket for a while, if ever,
but I think that maybe if Gas Town is too much, two thousand lines of Rust and a
little CLI may make more sense to you.</p>
<p>But that’s sort of the problem here: while Steve and I are very different,
there’s one way in which we’re the same: people read our blogs. I know that
projects I do get attention, heck, <a href="https://rue-lang.dev/">Rue</a> is not really ready for public
consumption, really. But because people find the stuff I mess around with to be
interesting, it got a lot of attention, far disproportionate for what it is. You
can put strings into an array, but you can’t even get them out yet! It’s just
too early!</p>
<p>So, what do you do if you:</p>
<ol>
<li>Know your projects get attention.</li>
<li>You also want like-minded people to come on this journey with you.</li>
<li>You don’t want to waste time explaining things to people who don’t get it.</li>
</ol>
<p>You reach for a tool that many people have used over the years: you get weird
with it.</p>
<p>There’s many examples of this happening in art and political movements. A pretty
famous one you might have heard of is <a href="https://en.wikipedia.org/wiki/Surrealism">Surrealism</a>. One of the core
ideas of surrealism is that they’re trying to unlock the power of the
subconscious mind, bypassing the conscious to (hopefully) reveal deeper, more
profound truths.</p>
<p>One of my favorite surrealist works is <a href="https://en.wikipedia.org/wiki/The_Treachery_of_Images">“La Trahison des images”</a> by
René Magritte. It is a painting of a pipe, with some words below: “Ceci n’est
pas une pipe” or “This is not a pipe.” At first, most people’s reaction to the
work is “uhhhh but that is a pipe?” But what the work is asking you to do is
think a little bit harder: this isn’t a pipe. It’s a painting of a pipe. That we
tend to conflate the image of something with the thing is a bit weird, if you
think about it a bit more. Why do we do that, anyway? What is the connection
between language, reality, images, and thoughts?</p>
<p>The painting was made in 1929, so these ideas are a bit more mainstream by now,
so you may not find this particular line of inquiry to be illuminating, but you
know, that’s what happens 97 years later.</p>
<p>Anyway. So if you’re someone who is going to attract attention, and want to
engage with a subset of those people, but also push away others, you could do
worse than trying to go surreal. It’s something that would be helpful both for
challenging assumptions, as well as for finding fellow travelers while repelling
those who aren’t interested in the same goals.</p>
<p>Maybe he is on metaphorical drugs. But that’s deliberate, is all I’m saying. And
maybe drugs aren’t your thing, and that’s fine too.</p>
<h2 id="is-this-a-good-thing">Is this a good thing?</h2>
<p>I also want to to make a few other things clear: I haven’t talked to Steve about
any of this, this is me just talking about what I see. Also, I haven’t tried Gas
Town myself, and I’m not sure I will. This is because while I’m somewhat aligned
with Steve’s train of thought here, I’m also not entirely.</p>
<p>I’m more interested in watching from afar. And that’s because, well, I have some
concerns with the way that Gas Town is doing things. But I’m also glad that he’s
trying it. It’s a rocket ship, and a lot of those exploded in the early days.
Tons of people died trying to invent the airplane. Some of their ideas were bad,
but eventually, enough of the conceptual space was explored to figure out the
good ideas.</p>
<h2 id="one-example-question">One example question</h2>
<p>What I want to leave you with is an example of the kind of experimentation I’m
seeing in Gas Town.</p>
<p>At Oxide, <a href="https://oxide.computer/principles">they talk a lot about their values</a>. It was a thing I really
appreciated about working there. On Monday, there was an episode of Oxide and
Friends on the topic of one of Oxide’s values as it applies to LLMs: Rigor.</p>
<bluesky-post src="at://did:plc:6235fybsui2cjt4fcspdbwpd/app.bsky.feed.post/3mcfgktcrbs2n" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:6235fybsui2cjt4fcspdbwpd" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:6235fybsui2cjt4fcspdbwpd/bafkreidqaixw7yj2vj64nd7ixiqncujwo6trchymloh2bycaorqg7bjkhy" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:6235fybsui2cjt4fcspdbwpd" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Adam Leventhal</span></bdi> <span class="handle s-hik11q">@ahl.bsky.social</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">On Monday, <a target="_blank" href="https://bsky.app/profile/did:plc:sl3g3nhnad34sfhjnqdivbzd" class="mention s-1lecfnd">@sunshowers.io</a> and <a target="_blank" href="https://bsky.app/profile/did:plc:ctphcgyhnllfluywokibtrgb" class="mention s-1lecfnd">@davidcrespo.bsky.social</a> joined me and <a target="_blank" href="https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc" class="mention s-1lecfnd">@bcantrill.bsky.social</a> to talk about their use of LLMs for building software--not to make vibe-coded one-offs, but to build much *more* robust systems software.</p> <div class="embeds s-azdpbr"><a target="_blank" href="https://youtu.be/fmFt4-jjEc0" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:6235fybsui2cjt4fcspdbwpd/bafkreiahoa3iyfh5cgirajxzhq5wafzqinzzqeh3piwzscs2mkt2ureu2u" alt="" class="thumbnail s-rtbqd8"/> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Oxide and Friends 1/12/2026 -- Engineering Rigor in the LLM Age</p> <p class="description s-rtbqd8">YouTube video by Oxide Computer Company</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">youtu.be</span></div></div></a></div> <time datetime="2026-01-14T15:55:02.860Z" class="date s-hik11q">January 14, 2026 at 3:55 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="56 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>56</span></span> <span class="stat s-hik11q" title="17 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>17</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:6235fybsui2cjt4fcspdbwpd/post/3mcfgktcrbs2n" target="_blank" class="permalink s-hik11q"><span>Read 2 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>Gas Town is <em>aggressively</em> not rigorous. And while rigor is a great value to
have, when you’re in an experimental mindset, relaxing rigor can be a valid
strategy to move forward.</p>
<p>But I think there’s a really interesting question to explore there: in the same
way that we can use LLMs in a rigorous way, is there a form of rigor that works
with AI agents merging code into trunk? What do we <em>really</em> mean by “rigor”? Is
it synonymous with “tests pass”? Does it mean that the state of the system must
be fully rigorous up front? Or is rigor an end state we work towards?</p>
<p>I am skeptical, but that’s also exactly why I’m thinking about such things. For
folks who are trying to reckon seriously with AI and the impact it will have on
software development, I think 2026 is a year to open your mind, challenge
assumptions, and re-consider what actually matters. The outcome of that might be
“yeah, we were right before and these ideas are bad,” but I have a sneaking
suspicion that the world has changed, and some old assumptions do not hold any
longer.</p>
<p>We’ll find out.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mcia32tae22j" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">How to think about Gas Town: <a target="_blank" href="https://steveklabnik.com/writing/how-to-think-about-gas-town/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/how-...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/how-to-think-about-gas-town/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">How to think about Gas Town</p> <p class="description s-rtbqd8">Blog post: How to think about Gas Town by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-01-15T18:36:50.633Z" class="date s-hik11q">January 15, 2026 at 6:36 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="138 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>138</span></span> <span class="stat s-hik11q" title="18 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>18</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcia32tae22j" target="_blank" class="permalink s-hik11q"><span>Read 21 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Getting started with Claude for software development</title><link>https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/</link><guid isPermaLink="true">https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/</guid><description>2025 was an interesting year in many ways. One way in which it was interesting for me is that I went from an AI hater to a pretty big user. And so I’ve had a few requests for a “using Claude” guide, so I figure new year, why not give it a shot? The lack of this kind of content…</description><pubDate>Wed, 07 Jan 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>2025 was an interesting year in many ways. One way in which it was interesting
for me is that I went from an AI hater to a pretty big user. And so I’ve had a
few requests for a “using Claude” guide, so I figure new year, why not give it a
shot? The lack of this kind of content was something that really frustrated me
starting out, so feels like a good thing to contribute to the world.</p>
<p>This post is going to be for software developers that are interested in learning
about using these tools as of early 2026. I’m going to spend this post talking
about some background, and then the first steps towards getting your feet wet.
If folks like it, I’ll follow up with more. There’s a lot here. I’m going to be
speaking about Claude directly, because it’s the tool I use the most, but a lot
of this should apply to other platforms as well.</p>
<h2 id="theres-a-lot-here">There’s a lot here</h2>
<p>The first thing I want to say on this topic is that there’s a reason that this
is the first post of possibly many: there’s a lot here, as I just said above.
This matters more than you might think at first. Becoming productive with LLMs
is not actually easy, no matter what other people tell you. A lot of advice in
this space is given by people who don’t have a teaching background, and have
forgotten how much work they’ve put in to get to where they are.</p>
<p>I liken it to vim: everyone acknowledges that modal editing is a different way
of working. We joke about how hard it is to learn how to quit vim if you’ve
accidentally started it up. But many people also acknowledge that the learning
curve is worth it, due to the power you get. I think of LLMs like vim: they’re
not super easy to get real results from, but the time invested can be worth it.</p>
<p>It’s also worth saying up front: maybe it’s not worth it, for you. I don’t fault
anyone for not wanting to spend time learning a new tool, especially in a space
that’s moving as fast as this one. Effectively everything I’m going to talk
about in this post has really only come into its own in the last 12-14 months.
Maybe in another 12 months this post will be useless. I don’t know. But just
like we might not find the time to learn vim to be worth it over just using a
more normal editor, that doesn’t mean that deciding that all of this isn’t worth
your time isn’t a rational, reasonable decision to make. You’re not going to be
“left behind” or whatever some of the boosters say, in the same way that you
don’t need to learn vim to do software dev. We aren’t doing vim vs emacs wars
here. We’re saying “hey if you want to learn vim this is how I think you can,
and if not, that’s fine.”</p>
<p>Furthermore, because there’s so much to cover, this post is going to be
background and step 1. Because otherwise it would be too dang long. You can’t
just read ten thousand words on this stuff and be an expert, you have to go
actually use the things. So you should be taking time between each post to go
and do that, and so not putting it all in one place should give you natural
breakpoints to go and actually try this stuff out.</p>
<h2 id="intentionality">Intentionality</h2>
<p>The very first thing I want to say on this entire topic is something that I
think about a lot. I have generally had better outcomes with LLMs than a lot of
people I know. And that’s bothered me. And I’m not sure exactly why that is. But
I do have one idea.</p>
<p>I like to approach this space in a … maybe “scientific” way is too strong, but
at least a rational one. I try things out, discard what doesn’t seem to work,
and keep what seems to work. I try and think critically about this space.</p>
<p>However.</p>
<p>I do think that the whole “vibe” term, while complicated in this space, is also
important. Vibes do matter, actually. I have some more science-y and some more
folks-y reasons that I believe this. But I do think that the attitude you bring
towards this process partially dictates your success, and I think you should be
conscious of that while you go on this journey.</p>
<p>Is that too woo-y for you? Okay, let me make it concrete: I un-ironically believe
that swearing at Claude makes it perform worse. I think you will get better
results working with an LLM if you treat them like you’d treat a respected
co-worker, and you will get worse results if you berate, insult, or otherwise
mistreat them.</p>
<p>This matters because I think that for a lot of LLM-skeptical people who give
this a shot, they may not actually go “Hey claude what’s your fucking problem”
(though I have literally seen this happen) they will tend to let their
frustrations show a bit more when things don’t work out. Use your emotional
regulation skills. It’s very okay to be critical in response to whatever Claude
does, but do it in a way that wouldn’t get you reported to HR in a healthy
company. Do this:</p>
<blockquote>
<p>Why did you do it that way? I would have preferred if we did <this> instead.</p>
</blockquote>
<p>Not this:</p>
<blockquote>
<p>Stop making such basic mistakes. You know that we do <this> and not <that>,
idiot.</p>
</blockquote>
<p>I think that being kind to people is good for you, but I also believe that even
if you’re a misanthrope, consider this a skill to get increased output from the
tool. I think a bit of anthropomorphization is actually a good thing here. We’ll
come back to that later during the more practical steps, but basically, that’s
the higher level principle at work: an LLM is not a person. But it is working
based off of language that people use. That’s its API. And so interacting with
it in the way you’d interact with a co-worker is, in my mind, the right way to
do it.</p>
<p>Maybe I’ll elaborate on this belief someday. Or maybe not. I do this for
personal belief reasons more than anything else. But it is something I want to
share.</p>
<h2 id="claude-web-vs-claude-code">Claude web vs Claude Code</h2>
<p>Okay! Now that we’ve got that out of the way, let’s talk about the various ways
you can use Claude! There’s a number of them, actually, but I want to focus on
two: on the web at <a href="https://claude.ai">https://claude.ai</a>, and with <a href="https://www.claude.com/product/claude-code">Claude
Code</a>.</p>
<p>Using Claude in these ways is fundamentally different. Both have pros and cons.
For real actual software development, you want to use Claude Code. This is due
to the “agentic loop”, which you’ll learn more about in a bit. But for your
first steps, using it via the web is okay. It’s mostly just important to know
that your experience using the web interface is not going to be the same as
using Claude Code. If I only had access to the web interface, I wouldn’t be so
bullish on this stuff. But it is and can be useful. Especially when getting your
feet wet, as long as you can understand that they’re meaningfully different.</p>
<h2 id="plans-vs-api-pricing">Plans vs API pricing</h2>
<p>This gets into another topic that matters: money. Another reason I do not fault
anyone for not spending time with these tools is that vim is free, whereas
Claude is very much not. However.</p>
<p>There are three major factors in the money equation: Claude Web vs Claude Code,
which models you have access to, and the actual cost. Let’s talk about them.</p>
<p>You can load up <a href="https://claude.ai">https://claude.ai</a> and talk to Claude right now for free. But
you cannot use Claude Code without paying. So if you want to start incredibly
small, using the website at first before you fork over money can make sense.
Again, that’s fine, just know that the experience is different. But it may be a
good way to start.</p>
<p>In 2024 and 2025, there was a good argument that you needed to be on a paid plan
because that’s how you got access to the latest models. While this is still true
to some degree, models have advanced far enough that the changes are less
important over time. I do think that in the first half of 2026, it still does
matter to a degree. Basically, the difference between Claude 3, 4, and 4.5 is
significant, but for me, Claude 4 was good enough a year ago to get real work
done. I’m not 100% sure which one you get for free today, but it’s at least
fine. And I think that by the time the next round of models come out, the ones
you’ll have access to for free will be basically good enough to make this
question moot. But do know that you get what you pay for, and paying for things
does get you better performance.</p>
<p>(Speaking of models, you’ll hear Claude referred to in three ways: Haiku,
Sonnet, and Opus. As the names imply, worst to best there, though also, fastest
to slowest. Sonnet, especially the 4.5 version, is pretty good for everything.
Opus 4.5 is wonderful. Haiku is great for certain things.)</p>
<p>As for actual cost: there’s $20/month, $100/month, and $200/month plans, as well
as “you pay per API call.” You might be tempted to think “I’ll just pay per API
call and keep my usage down.” This is a reasonable thing to think, and also a
terrible mistake to make. You get a lot of bang for your buck with the plans. To
give you an idea, I recently hit my weekly limit last night on the $200/month
plan, and my estimated usage for that week (which again, I’m paying $50 for)
would have been $1440.73 if I were paying by the API call. Now, I am a very
heavy user, but the point stands: as someone trying out these tools, it is way
easy to spend more than $20 of API tokens. If you want to give these tools a
real shot, come up with a spare $20, sign up for the cheap plan, and then cancel
after your experiment is over. You get access to Claude Code and you’ve capped
your spend. It’s a win/win. There’s some good secondary effects of trying to be
frugal here but I think that’s more of an intermediate than an advanced topic,
to be honest. I think worrying about the money while you build these skills is a
distraction. Cap your spend via a plan so that way you can not stress out about
breaking the bank.</p>
<p>Okay, with all of that background out of the way: let’s talk about your first
steps here.</p>
<h2 id="your-first-steps-a-conversation">Your first steps: a conversation</h2>
<p>Everyone is interested in the ability of LLMs to generate code. But I think
that’s actually step 2, not step 1. The way I want you to start using these
tools is purely <em>read-only</em> at first. This is also why the website is okay to
get started with too; Claude Code is far better at generating code than the site
is, but we’re not going to start by writing code.</p>
<p>Find some code you’ve written recently. It can be literally anything. Load up
<a href="https://claude.ai">https://claude.ai</a>, and type:</p>
<blockquote>
<p>Hi Claude! Can we talk about this code?</p>
</blockquote>
<p>And then paste your code in. You don’t need any fancy prompting techniques. You
don’t even need to say what language it is. Just give it some code. It could be
ten lines, it could be a hundred. I wouldn’t recommend a thousand to start.</p>
<p>Claude will probably respond with some sort of basic analysis of what you’ve
done, and then a question. I gave it ~50 lines of code a friend and I were
discussing recently, and it gave me this back:</p>
<blockquote>
<p>Sure! This looks like <description of what it does>. You’ve got <three things
that the code does>.</p>
<p>What’s on your mind about it? Are you thinking through the design, running
into a specific issue, or wanting feedback on a particular aspect?</p>
</blockquote>
<p>From here, you have a ton of options of which way to go, but they really depend
on what you’ve pasted in. Here’s some fun prompt ideas:</p>
<blockquote>
<p>Do you think this code is idiomatic?</p>
<p>If you could improve one thing about this code, what might it be?</p>
<p>If I wanted to modify this code to do <something>, how would you go about
doing that?</p>
<p>Are there any bugs in this code?</p>
<p>Are there any security implications of this code I may not have thought about?</p>
</blockquote>
<p>And so on. Anyway, the goal here is to just get used to this whole thing. It’s a
bit weird! It’s very different than talking to a compiler.</p>
<p>If Claude says something you disagree with, push back a little, just like you
would a co-worker:</p>
<blockquote>
<p>I’m not sure I agree with that. The reason why is that in some other part of
the system, there’s <behavior> and so that would impact this sort of
decision.</p>
<p>Why did you suggest that? I’d like to understand more.</p>
</blockquote>
<p>Claude will absolutely not be right all of the time. And that’s okay! The goal
is to work together, not that this is a magic tool that suddenly solves all of
your problems.</p>
<h2 id="second-steps-bigger-questions">Second steps: bigger questions</h2>
<p>Once you’ve done this a few times, you might want to graduate to Claude Code.
The reason for this is that you can start to scale up your questions. Once
you’ve installed it and logged in, you’ll be at a terminal prompt. It might bug
you about creating a CLAUDE.md, don’t worry about that for now. Continue having
conversations with Claude about your codebase. The reason that this is a a big
step up is that before, you had to paste all of the code in. Now, Claude can go
find your code itself. Some prompts for you to try:</p>
<blockquote>
<p>Please give me a code review of my codebase and suggest five things I could do
to improve it.</p>
<p>Can you find any bugs in <component>?</p>
<p>I’m curious about the performance of <component>, can we talk about it?</p>
</blockquote>
<p>One thing I like to do here is have Claude double check my intuitions. A few
months ago, working on an application in Rust, I was considering a refactoring.
I hadn’t done it because I was worried that it would be tedious, take a while,
and maybe not improve the codebase. It might! But it might not. But putting in
the day or two to do the refactor wasn’t really worth finding out if maybe that
would be wasted. So, I asked Claude. This is an example of a bit longer of a
prompt:</p>
<blockquote>
<p>Hi Claude! I am considering refactoring my code. In a function like this:
<paste code>, I don’t like how I did things, and I’m considering doing it
like this instead: <paste code>. However, I know that changes the signature,
which impacts other code in the codebase. A few questions for you: 1. how many
function signatures would need to be updated if I made this change? 2. can you
show me what the code would look like if I did this refactoring on one of my
simpler endpoints? 3. can you show me what the code would look like if I did
this refactoring on one of my most complex endpoints?</p>
</blockquote>
<p>Claude came back and said something like “250 signatures would need to change,
here’s the before and after using these two examples from your codebase.” Now,
Claude isn’t perfect: maybe it was actually 260 signatures. But the point is,
this helped me characterize my intuition here: it would be a non-trivial amount
of work. But I also got to see its impact on real code I had written, which
helped me decide if this refactoring would actually help me in some of the more
hairy parts of the codebase.</p>
<p>Note that there’s not really any special “prompt engineering” going on here. You
don’t need to do “as a senior software engineer” or stuff like that. Just talk
to it like you’d talk to a person. It’s fine. That doesn’t mean that prompts are
useless, but this sort of optimization is an intermediate to advanced topic, and
frankly, I’m skeptical that at this point the “as an x” technique even helps.
More on that someday. The point is, you can start asking more complex questions
as you get more comfortable with the tool.</p>
<p>Because Claude works asynchronously, you can just fire off questions like these
in the background, and come back to them when it’s done. Well, sorta. Let’s talk
about permissions before we wrap this up.</p>
<h2 id="permissions">Permissions</h2>
<p>By default, Claude will put you in an “ask before edits” mode. This is a good
way to start. It’ll check in with you before doing certain things, and you can
say yes or no. Please consider what it’s about to do, and give the answer you’re
comfortable with. Advanced users basically let Claude do whatever it wants, but
you’re not there yet, and there’s risks involved that aren’t obvious to you just
yet as a new user, so even though it can be a bit annoying to say yes every time
it asks, I’d encourage you to start off with minimal permissions. It gives you
the option to say “commands like this one are okay for the rest of my session”
and so when it wants to <code>grep</code> or something, that can be nice to agree to, but
I’d encourage you to not use it for writing code just yet, and tell it no if it
asks. We’ll do that in a follow-up post.</p>
<h2 id="in-conclusion">In conclusion</h2>
<p>So that’s my intro to getting started with Claude. Spend $20, talk to it like
you’d talk to a person, and use it as a means of getting feedback on your code,
don’t have it write anything just yet. Graduate to larger and larger questions
as you get comfortable with what it can do. Gently push back when you think it
gets out of line. But your goal here is a baseline understanding of what the
tool is capable of, not to vibe code out an entire app in an afternoon. These
skills may seem too basic, but I promise you, it gets harder from here, and so
you’ll want a solid foundation in read-only questions before we graduate to
having Claude write some code.</p>
<p>I hope this was helpful to you.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mbu3gbqrwk2y" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Getting started with Claude for software development: <a target="_blank" href="https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/gett...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Getting started with Claude for software development</p> <p class="description s-rtbqd8">Blog post: Getting started with Claude for software development by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2026-01-07T18:20:23.515Z" class="date s-hik11q">January 7, 2026 at 6:20 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="338 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>338</span></span> <span class="stat s-hik11q" title="51 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>51</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mbu3gbqrwk2y" target="_blank" class="permalink s-hik11q"><span>Read 29 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Thirteen years of Rust and the birth of Rue</title><link>https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/</link><guid isPermaLink="true">https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/</guid><description>I started using Rust on December 21, 2012. It’s now December 22, 2025. That’s a long time. I don’t have a ton of thoughts about Rust to share today, like I thought I might. I program in the language regularly, it’s nice, I prefer it to all others. I have my beefs and my regrets,…</description><pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I started using Rust on December 21, 2012. It’s now December 22, 2025. That’s a
long time.</p>
<p>I don’t have a ton of thoughts about Rust to share today, like I thought I
might. I program in the language regularly, it’s nice, I prefer it to all
others. I have my beefs and my regrets, like any real project, but for the most
part, I am pretty happy these days. To be honest, after a pretty rough last few
years, that’s been the theme of 2025: I’m pretty happy these days.</p>
<p>I have long been wondering if I should try and make my own language. I really
like them! That’s part of why I got involved in Ruby and then Rust in the first
place! But I’ll be turning 40 in a month, and I barely have time for things
anymore. So I had kind of put that on the back burner.</p>
<p>But one thing that 2025 changed: I went from an AI skeptic to writing most of my
code with Claude. I’m still a bit unsure about LLMs outside of programming, but
as a software development tool, I’ve found them finally to be good enough to be
legitimately useful. Your mileage may vary, I’m not really going to try and tell
you that they should be for you, only that they are for me.</p>
<p>And so, earlier this year, I embarked on starting my own little language project.
I decided to call it “Rue”, becuase:</p>
<ol>
<li>It has to start with Ru, duh. I did Ruby and then Rust, so my hands are
basically tied here…</li>
<li>It has a negative connotation, “to rue the day”, like Rust has a possible
negative interpretation.</li>
<li>It has a nature connotation, a
<a href="https://en.wikipedia.org/wiki/Ruta_graveolens">Rue</a>, like Rust has a possible
fungi connotation.</li>
<li>It’s short.</li>
</ol>
<p>I have also been wondering for a few years if you can build a language without
financial backing and a team. I think LLMs might change that equation. Yes,
Claude costs money, but I mean like, you don’t need to do what Mozilla did and
drop seven or eight figures worth of money on a development team to make
meaningful progress. And since I don’t have a ton of personal time these days,
what better way to test that than to actually give it a go.</p>
<p>Anyway, I got into it, and kind of made a mess of the first go. And then I had a
tremendously busy fall, and had to put things by the wayside.</p>
<p>But this week, I had some time after work, and decided to start over. And I’m
much more happy with how it’s going this time. So I’ve decided it’s time to at
least let the world know I’m working on it, just because why not.</p>
<p>I don’t expect it to grow to anything more than my hobby project, but then
again, that’s also true of Rasmus and Graydon and a ton of other language
authors, so, never say never. But that’s not an explicit goal here. I reserve
the right to just stop working on this when I think it’s not fun. I’m not trying
to build a community or even add committers. It’s just something for me for fun.</p>
<p>You can find Rue over at <a href="https://rue-lang.dev/">https://rue-lang.dev/</a> if you want to check it out.
There’s a blog with an RSS feed. I may blog there, I may not. But I’ll probably
keep it over there more than over here, in general.</p>
<p>I’m not saying that Claude is amazing, or that this code is fantastic, or
anything. The language is still very janky. I’m sure I have codegen bugs. It’s
missing basic features. It is very, very much a work in progress.</p>
<p>Thank you to everyone for making 2025 a nice year. There’s a lot of terrible
things in this world right now, but I’m more hopeful than ever, personally.
I hope to still be writing Rust in another ten years, and maybe even writing
some Rue. We’ll see :)</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3majjeqo4ik2t" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Thirteen years of <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> and the birth of <a target="_blank" href="https://bsky.app/hashtag/ruelang" class="hashtag s-1lecfnd">#ruelang</a> <a target="_blank" href="https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/thir...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Thirteen years of Rust and the birth of Rue</p> <p class="description s-rtbqd8">Blog post: Thirteen years of Rust and the birth of Rue by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-12-21T20:05:35.682Z" class="date s-hik11q">December 21, 2025 at 8:05 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="111 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>111</span></span> <span class="stat s-hik11q" title="10 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>10</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3majjeqo4ik2t" target="_blank" class="permalink s-hik11q"><span>Read 19 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>I see a future in jj</title><link>https://steveklabnik.com/writing/i-see-a-future-in-jj/</link><guid isPermaLink="true">https://steveklabnik.com/writing/i-see-a-future-in-jj/</guid><description>In December of 2012, I was home for Christmas, reading Hacker News. And that’s when I saw “ Rust 0.5 released ."" I’m a big fan of programming languages, so I decided to check it out. At the time, I was working on Ruby and Rails, but in college, I had wanted to focus on…</description><pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>In December of 2012, I was home for Christmas, reading Hacker News. And that’s
when I saw “<a href="https://news.ycombinator.com/item?id=4953293">Rust 0.5 released</a>."" I’m a big fan of programming languages, so I
decided to check it out. At the time, I was working on Ruby and Rails, but in
college, I had wanted to focus on compilers, and my friends were all very much
into systems stuff. So I decided to give Rust a try.</p>
<p>And I liked it! But, for other reasons I won’t get into here, I was thinking
about a lot of things in that moment. I was looking to shake things up a bit. So
I asked myself: is Rust going to be A Thing?</p>
<h2 id="why-i-chose-rust">Why I chose Rust</h2>
<p>So, I thought about it. What does a programming language need to be successful?
It needs some sort of market fit. It needs to have people willing to work on it,
as bringing a new language into the world is a lot of work. And it needs users.</p>
<p>When I considered all of these things, here’s what I saw with Rust:</p>
<p>Market fit: there was basically no credible alternatives to C and C++. I had
been involved in the <a href="https://dlang.org/">D community</a> a bit, but it was clear that it wasn’t going
to take off. Go was a few years old, and hit 1.0 earlier that year, but for the
kinds of work that C and C++ are uniquely able to do, I saw the same problem
that I did with D: garbage collection. This doesn’t mean Go isn’t a good
language, or that it’s not popular, but I didn’t see it as being able to
credibly challenge C and C++ in their strongholds. Rust, on the other hand, had
a novel approach to these problems: memory safety without garbage collection.
Now, I also need to mention that Rust back in those days was much closer to Go
than it even is today, but again, I had just learned about it for a few hours, I
didn’t really have a deep understanding of it yet. If I had, I actually might
have also dismissed it as well, as it wasn’t really GC that was the issue, but a
significant runtime. But again: I hadn’t really come to that understanding yet.
Point is: low-level programming was a space where there hadn’t been much
innovation in a very long time, and I thought that meant that Rust had a chance.
Check.</p>
<p>For a team: well, Mozilla was backing it. This is a big deal. It meant that
there were folks whose job it was to work on the language. There’s so much that
you need to do to make a new language, and that means a ton of work, which means
that if you’re going to be able to get it done in a reasonable amount of time,
having paid folks working on it is certainly better than the alternative. Check.</p>
<p>And finally, how does this translate into users? Well, Mozilla was planning on
using it in Firefox. This is huge. Firefox is a major project, and if they could
manage to use Rust in it, that would prove that Rust was capable of doing real
work. And, more importantly, it would mean that there would be a lot of folks
who would need to learn Rust to work on Firefox. This would create a base of
users, which would help the language grow. Check.</p>
<p>Finally, even though it wasn’t part of my initial assessment, I just really
liked the Rust folks. I had joined IRC and chatted with people, and unlike many
IRC rooms, they were actually really nice. I wanted to be around them more. And
if I did, other people probably would too. So that was also a plus.</p>
<p>So, I started learning Rust. I decided to write a tutorial for it, “Rust for
Rubyists,” because I’m a sucker for alliteration. And I eventually joined the
team, co-authored The Book, and if you’re reading this post, you probably know
the rest of the story.</p>
<h2 id="enter-jj">Enter jj</h2>
<p>For some background, <a href="https://jj-vcs.github.io/jj/latest/">jj</a> is a new version control system (VCS), not a
programming language. It is written in Rust though! While I talked about how I
decided to get involved with Rust above, my approach here generalizes to other
kinds of software projects, not just programming languages.</p>
<p>I have a rule of thumb: if <a href="https://sunshowers.io/">Rain</a> likes something, I will probably like that
thing, as we have similar technical tastes. So when I heard her talk about jj,
I put that on my list of things to spend some time with at some point. I was
especially intrigued because Rain had worked at Meta on their source control
team. So if she’s recommending something related to source control, that’s a
huge green flag.</p>
<p>It took me a while, but one Saturday morning, I woke up a bit early, and thought
to myself, “I have nothing to do today. Let’s take a look at jj.” So <a href="https://github.com/steveklabnik/jujutsu-tutorial/commit/fc8c588dfc58ce2ff54246174fc59aca0fc868b5">I did</a>.
You’ll note that link goes to a commit starting a book about jj. Since it
worked for me with Rust, it probably would work for me for jj as well. Writing
about something really helps clarify my thinking about it, and what better time
to write something for a beginner than when you’re also a beginner?</p>
<p>Anyway, people seem to really like my tutorial, and I’m thankful for that.</p>
<h2 id="the-future-of-jj">The future of jj</h2>
<p>So, what do I see in jj? Well, a lot of it kind of eerily mirrors what I saw in
Rust: a good market fit, a solid team, and a potential user base.</p>
<p>But the market fit is interesting. Git has clearly won, it has all of the
mindshare, but since you can use jj to work on Git repositories, it can be
adopted incrementally. At <a href="https://oxide.computer/">Oxide</a>, Rain started using jj, and more of us did,
and now we’ve got a chat channel dedicated to it. This is, in my opinion, the
only viable way to introduce a new VCS: it has to be able to be partially
adopted.</p>
<p>Google is using jj, and so that is a bit different than Mozilla, but the same
basic idea. I have more to say about Google’s relationship to jj, but that’s
going to be a follow-up blog post. What I will say in this post is that at the
first ever jj conference a few weeks ago, Martin (the creator of jj) said that
internal adoption is going really well. I’m burying the lede a bit here, because
the video isn’t up yet, and I don’t want to get the details of some of the more
exciting news incorrect in this post. I also don’t mean to imply that everyone
at Google is using jj, but the contingent feels significant to me, given how
hard it is to introduce a new VCS inside a company of that size. Well, in this
case, it’s using Piper as the backend, so you could argue about some of the
details here, but the point is: jj is being used in projects as small as
individual developers and as large as one of the largest monorepos in the world.
That’s a big deal. It can show the social proof needed for others to give jj a
chance.</p>
<p>Outside of Google, a lot of people say that there’s a bit of a learning curve, but
once you get over that, people really like it. Sound familiar? I think jj is
different from Rust in this regard in that it’s also very easy to learn if you
aren’t someone who really knows a ton about Git. It’s folks that really know Git
internals and have put time and care into their workflows that can struggle a
bit with jj, because jj is different. But for people who just want to get work
done, jj is really easy to pick up. And when people do, they often tend to like
it. jj has developed a bit of a reputation for having a passionate fanbase.
People are adopting it in a skunkworks way. This is a great sign for a new tool.</p>
<p>And finally, the team. Martin is very dedicated to jj, and has been working on
it for a long time. There’s also a small group of folks working on it with him.
It recently moved out from his personal GitHub account to its own organization,
and has started a more formal governance. The team is full of people who have a
deep history of working on source control tools, and they know what they’re
doing. The burgeoning jj community reminds me of that early Rust community: a
bunch of nice folks who are excited about something and eager to help it grow.</p>
<p>Basically, to me, jj’s future looks very bright. It reminds me of Rust in all of
the best ways.</p>
<h2 id="putting-my-money-where-my-mouth-is">Putting my money where my mouth is</h2>
<p>Speaking of burying the lede… I’ve decided to leave Oxide. Oxide is the best
job I’ve ever had, and I love the people I work with. I was employee 17. I think
the business will do fantastic in the future, and honestly it’s a bit of a
strange time to decide to leave, since things are going so well. But at the same
time, some of my friends have started a new company, <a href="https://ersc.io/">ERSC</a>, which is going to
be building a new platform for developer collaboration on top of jj. Don’t
worry, “errssk” isn’t going to be the name of the product. It’s kind of like how
GitHub was incorporated as <a href="http://logicalawesome.com/">Logical Awesome</a>, but nobody calls it that.</p>
<p>This won’t be happening until next month, I have some stuff to wrap up at Oxide,
and I’m going to take a week off before starting. But as sad as I am to be
leaving Oxide, I’m also really excited to be able to spend more time working in
the jj community, and helping build out this new platform. For those of you
who’ve been asking me to finish my tutorial, well, now I’ll have the time to
actually do that! I’m sorry it’s taken so long! You’ll see me talking about jj
even more, spending even more time in <a href="https://discord.gg/36cDSjyd6K">the Discord</a>, and generally being more
involved in the community. And I’ll be writing more posts about it here as well,
of course.</p>
<p>I’m really excited about this next chapter. 2025 has been a very good year for
me so far, for a number of reasons, and I am grateful to be able to take a
chance on something that I’m truly passionate about.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3m3sebwbris2a" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">I see a future in <a target="_blank" href="https://bsky.app/hashtag/jj-vcs" class="hashtag s-1lecfnd">#jj-vcs</a>: <a target="_blank" href="https://steveklabnik.com/writing/i-see-a-future-in-jj/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/i-se...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/i-see-a-future-in-jj/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">I see a future in jj</p> <p class="description s-rtbqd8">Blog post: I see a future in jj by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-10-22T17:22:31.703Z" class="date s-hik11q">October 22, 2025 at 5:22 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="384 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>384</span></span> <span class="stat s-hik11q" title="53 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>53</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3m3sebwbris2a" target="_blank" class="permalink s-hik11q"><span>Read 67 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>A tale of two Claudes</title><link>https://steveklabnik.com/writing/a-tale-of-two-claudes/</link><guid isPermaLink="true">https://steveklabnik.com/writing/a-tale-of-two-claudes/</guid><description>It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of…</description><pubDate>Tue, 10 Jun 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>It was the best of times, it was the worst of times, it was the age of wisdom,
it was the age of foolishness, it was the epoch of belief, it was the epoch of
incredulity, it was the season of Light, it was the season of Darkness, it was
the spring of hope, it was the winter of despair, we had everything before us,
we had nothing before us, we were all going direct to Heaven, we were all going
direct the other way—in short, the period was so far like the present period,
that some of its noisiest authorities insisted on its being received, for good
or for evil, in the superlative degree of comparison only.</p>
<ul>
<li>Charles Dickens, <em>A Tale of Two Cities</em></li>
</ul>
</blockquote>
<p>I recently had two very different experiences with Claude Code. I wanted to
share them both, because I find the contrast interesting.</p>
<h2 id="claude-hates-tailwind-4">Claude hates Tailwind 4</h2>
<p>I have really been enjoying <a href="https://tailwindcss.com/">TailwindCSS</a>. I also have started several
web projects using it in the past year. Back in January, <a href="https://tailwindcss.com/blog/tailwindcss-v4">Tailwind released
version 4</a>. Honestly, I am not a good enough Tailwind user to be able
to appreciate the new features, but I do prefer to keep my projects up to date.
So I’ve needed to update my Tailwind projects to version 4.</p>
<p>Claude… is nearly completely useless for this.</p>
<p>You’d think that upgrading a very popular CSS framework would be a
straightforward task. But Claude has failed to set it up multiple times. While
it’s true that Claude Code claims that its training cutoff is in January of
2025 in its system prompt, it seems to have a cutoff in March. Regardless, a
useful tool shouldn’t need to be explicitly trained on the latest version of a
framework to be able to help with it. Yet, it consistently fails to either set
up Tailwind 4 in new projects, or upgrade existing projects to this fourth
release.</p>
<p>I did manage to get it to work earlier this week, when I <a href="https://github.com/steveklabnik/steveklabnik.com/commit/fc7a5f6d0a8169e45d67891fe784bdf2270f14f9">asked it to
update this website</a>, but at this point, it was mostly just me messing
around. I wish I had saved the exact prompt, but basically I said something like
“you have failed me consistently in this task, so please search for upgrade
guides and the like to guide you in this update.” It did do that, but for some
reason, the upgrade tool couldn’t update my configs, and so it decided to do it
itself, and well, while this time it did manage to technically update, it didn’t
do it completely correctly. It removed the typography plugin, which I had to ask
it to put back. And it didn’t properly import the config into my global CSS,
which also caused weird issues. Regardless, it did manage to sort of do it,
after some coaxing, but this was a frustrating experience that was probably the
third or fourth time I had tried to get it to do this.</p>
<p>While Tailwind 4 is a new major version release, it’s still incredibly popular,
and one of the hunches I have about LLM usage for coding is that more popular
tools will be easier for LLMs to use. This didn’t seem to be the case here,
though.</p>
<h2 id="claude-loves-elf">Claude loves… ELF?</h2>
<p>For… reasons, I am working on an assembler. Don’t worry about it, you may find
out about it someday, and you may not. It’s not for work, just for fun.</p>
<p>Anyway, if you’re not familiar, basically this process turns assembly code into
machine code. Specifically, in this case, I’m encoding x86_64 assembly into an
ELF executable. ELF is a file format that is used by, among other things, Linux,
for executables.</p>
<p>I’m working with Claude on this, and I again didn’t save my prompts, which I am
annoyed by, but I roughly said something like “please implement codegen such
that it produces an ELF executable” and let it go. I did not expect this to
work, or go well. As I said above, I sort of expect more common tasks to be
easier for LLMs, given that they have more training data. While I’m sure there’s
some code in Rust out there that does this, I still don’t expect it to be
anywhere near as common as TailwindCSS.</p>
<p>A few minutes later, Claude said “hey, I did it!” For completely unrelated
reasons, I got a message from <a href="https://bsky.app/profile/there.is.no.aarch64.mov">Ixi</a>, a friend of mine who would be
particularly interested in this project, and so I said something along the lines
of “hey, I’m working on a project you’d be interested in, claude has just made
some large claims:</p>
<blockquote>
<p>Steve, we’ve successfully implemented a complete end-to-end x86-64 code
generation pipeline</p>
</blockquote>
<p>Do you want to look at the PR together?” and they said yes.</p>
<p>So I shared my screen, and we went through the PR together. It was pretty fine.
A lot of the stuff at first was normal, but a bit boring: the error handling was
fine, and maybe slightly different than I would have done, but it wasn’t so bad
that if a co-worker had sent in the PR, I would have objected to merging it.</p>
<p>But Ixi noticed something:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> instruction_size</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">, instr</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">Instruction</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> u64</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> match</span><span style="color:#E1E4E8"> instr {</span></span>
<span class="line"><span style="color:#B392F0"> Instruction</span><span style="color:#F97583">::</span><span style="color:#B392F0">Push</span><span style="color:#E1E4E8">(_) </span><span style="color:#F97583">=></span><span style="color:#79B8FF"> 2</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#B392F0"> Instruction</span><span style="color:#F97583">::</span><span style="color:#B392F0">Pop</span><span style="color:#E1E4E8">(_) </span><span style="color:#F97583">=></span><span style="color:#79B8FF"> 2</span><span style="color:#E1E4E8">,</span></span></code></pre>
<p>These sizes are… wrong. They should (probably) be one byte. Fun! Most of the
code looked overall reasonable, though. So let’s look at that ELF it produced.</p>
<p>I ran the executable, and it segfaulted. Surprise! While Claude did think it was
done, because it had produced an ELF file, and the tests were passing, it didn’t
actually work. Given that those sizes were wrong, this wasn’t really a surprise.
We decided to dig into the code a bit more, but first: I told Claude “hey, this
example you compiled segfaults, something is wrong, can you fix it?” Again, not
the exact prompt. I wish I had saved it, but I didn’t think I was writing this
blog post at the time, so I didn’t. But it was very basic like that. And I let
Claude go in the background while we took a look.</p>
<p>We used <code>objdump -xd</code> to see some of the information about the ELF file. We
ended up using <code>gdb</code> to see where it was crashing:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>Program received signal SIGSEGV, Segmentation fault.</span></span>
<span class="line"><span> 0x0000000000400093 in ?? ()</span></span>
<span class="line"><span> #0 0x0000000000400093 in ?? ()</span></span>
<span class="line"><span> #1 0x000000000040007d in ?? ()</span></span>
<span class="line"><span> #2 0x0000000000000001 in ?? ()</span></span>
<span class="line"><span> #3 0x00007fffffffddb3 in ?? ()</span></span>
<span class="line"><span> #4 0x0000000000000000 in ?? ()</span></span></code></pre>
<p>There’s no debug info here, so we only have addresses. There was a jump going to
an address that ends in <code>93</code>. This is incorrect: <code>0x400093</code> was three bytes into
a <code>movabs $0x24, rdi</code>, so… we were jumping to the middle of an instruction,
which is nonsense, hence the segfault.</p>
<p>(Now that I’m looking at this, I also find these other addresses suspect. I wish
I had taken better notes while we were looking at this… the point is this was
a quick check as to what was going on, and it was very clear that this is just
straight-up wrong.)</p>
<p>How could this happen? Well, if the sizes of the instructions were wrong, when
the code figures out where to jump, it would go to the wrong place, as it would
mis-calculate how far away the jump target was. So this all made sense.
Hypothesis: instruction sizes are incorrect, which means the jump calculations
are incorrect, which means fail.</p>
<p>I had to run, so we ended the call, but I left Claude running. A few minutes
later, I checked in on it, and that’s when I was really surprised.</p>
<p>Claude had fixed the issue! It wasn’t really even about that, but also, how it
did so:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>● Bash(gdb -batch -ex "run" -ex "bt" --args ./debug_test)</span></span>
<span class="line"><span> ⎿ Program received signal SIGSEGV, Segmentation fault.</span></span>
<span class="line"><span> 0x0000000000400093 in ?? ()</span></span>
<span class="line"><span> #0 0x0000000000400093 in ?? ()</span></span>
<span class="line"><span> #1 0x000000000040007d in ?? ()</span></span>
<span class="line"><span> #2 0x0000000000000001 in ?? ()</span></span>
<span class="line"><span> #3 0x00007fffffffddb3 in ?? ()</span></span>
<span class="line"><span> #4 0x0000000000000000 in ?? ()</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Perfect! The crash is at 0x400093, which means the call</span></span>
<span class="line"><span>instruction is jumping to the wrong location. The issue is</span></span>
<span class="line"><span>in my relocation calculation. Let me examine this more carefully.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> Looking at the hex dump again:</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Entry point: 0x400078</span></span>
<span class="line"><span>Call instruction: e8 16 00 00 00 (5 bytes)</span></span>
<span class="line"><span>Next instruction: 0x40007d</span></span>
<span class="line"><span>Target: 0x40007d + 0x16 = 0x400093</span></span>
<span class="line"><span></span></span>
<span class="line"><span> But the main function is actually at file offset 0x8A, which is</span></span>
<span class="line"><span> virtual address 0x40008A.</span></span>
<span class="line"><span></span></span>
<span class="line"><span> The issue is in my symbol address calculation. Let me examine</span></span>
<span class="line"><span> the assembler code:</span></span></code></pre>
<p>Hmmm. It’s kind of right, but also maybe not? I kept scrolling:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span> ● I found the issue! The assembler is calculating symbol</span></span>
<span class="line"><span> addresses relative to the start of the machine code (offset 0),</span></span>
<span class="line"><span> but when resolving relocations, it needs to account for the</span></span>
<span class="line"><span> fact that the machine code will be loaded at the ELF entry</span></span>
<span class="line"><span> point address.</span></span></code></pre>
<p>That also didn’t feel right? More scrollback through. It was creating sample
programs in assembly, and then assembling them, and then examining the contents
of the files. It was double checking its assumptions and understanding of the
code, and then, there it was:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>There's a bug in my instruction size calculation. Let me check</span></span>
<span class="line"><span>the instruction_size function:</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Read(src/lib.rs)</span></span>
<span class="line"><span> ⎿ Read 20 lines (ctrl+r to expand)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>● Found the bug! Mov(, _) => 10 is wrong. It assumes all mov</span></span>
<span class="line"><span> instructions are 10 bytes (mov reg, imm64), but mov rdi, rax</span></span>
<span class="line"><span> is only 3 bytes.</span></span></code></pre>
<p>Okay, wait… <code>mov</code>, and not <code>push</code>/<code>pop</code>? Sure enough, not only had it figured
out that <code>push</code> and <code>pop</code> were one byte, not two, but it had also figured out
that <code>cmp</code> had a few forms with different sizes, and that <code>mov</code> did too. After
fixing that… the executables worked!</p>
<p>But really, the most impressive part is that it had basically done exactly what
we had done, but in the background, while we were looking at the code. It took a
little longer to get there, because it went down one rabbit hole, but it had
also found other bugs that weren’t immediately obvious to us. And, I’ll be extra
honest: while I understand what’s going on here, I am not super great at
<code>objdump</code> and <code>gdb</code>, and if Ixi wasn’t around, Claude would have probably found
this before me, if I were alone. This is part of why having friends is great,
but if I didn’t have that friend around at that moment, this would have
tremendously helped.</p>
<p>I am also impressed because, as mentioned above: I would expect this task to be
much more rare than web development stuff. Yet, Claude did a far better job,
finding the issue quickly and fixing it, as opposed to the multiple times I gave
it a chance with Tailwind.</p>
<h2 id="conclusion">Conclusion</h2>
<p>What does this mean? I don’t know, but I figured I want to start documenting
these experiences, good and bad, and so I wanted to share both of these stories,
one good, and one bad.</p>
<p>LLMs are not magic, or perfect, but I have found them to be useful. It’s
important to share both the successes, and also the failures. I’m hoping to do a
better job of tracking the prompts and details to give you a better
understanding of the specifics of how I’m using these tools, but the perfect is
the enemy of the good, and so these are the stories you’re going to get for now.</p>
<hr/>
<p>Here’s my post about this post on Bluesky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lrb6sb7fzl2i" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">A tale of two Claudes
<a target="_blank" href="https://steveklabnik.com/writing/a-tale-of-two-claudes/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/a-ta...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/a-tale-of-two-claudes/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">A tale of two Claudes</p> <p class="description s-rtbqd8">Blog post: A tale of two Claudes by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-06-10T15:17:05.751Z" class="date s-hik11q">June 10, 2025 at 3:17 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="57 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>57</span></span> <span class="stat s-hik11q" title="9 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>9</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lrb6sb7fzl2i" target="_blank" class="permalink s-hik11q"><span>Read 10 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Is Rust faster than C?</title><link>https://steveklabnik.com/writing/is-rust-faster-than-c/</link><guid isPermaLink="true">https://steveklabnik.com/writing/is-rust-faster-than-c/</guid><description>Someone on Reddit recently asked: What would make a Rust implementation of something faster than a C implementation, all things being the same? I think this is a great and interesting question! It’s really tough because it ultimately relies on what exactly you mean by “all things…</description><pubDate>Mon, 09 Jun 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Someone on Reddit recently asked:</p>
<blockquote>
<p>What would make a Rust implementation of something faster than a C
implementation, all things being the same?</p>
</blockquote>
<p>I think this is a great and interesting question! It’s really tough because it
ultimately relies on what exactly you mean by “all things being the same.” And I
think this is something that makes it hard to compare languages.</p>
<p>Here’s some ways in which you can argue that things are “the same” but also,
that they’re not, and what they imply for runtime performance.</p>
<h2 id="inline-assembly">Inline Assembly</h2>
<p>Rust has inline assembly built into the language. C has inline assembly as a
very common compiler extension, to the degree where saying it’s not part of the
language is an arguable nitpick.</p>
<p>Here’s an example in Rust:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">use</span><span style="color:#B392F0"> std</span><span style="color:#F97583">::</span><span style="color:#B392F0">arch</span><span style="color:#F97583">::</span><span style="color:#E1E4E8">asm;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">#[</span><span style="color:#F97583">unsafe</span><span style="color:#E1E4E8">(no_mangle)]</span></span>
<span class="line"><span style="color:#F97583">pub</span><span style="color:#F97583"> fn</span><span style="color:#B392F0"> rdtsc</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">-></span><span style="color:#B392F0"> u64</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> lo</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> hi</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#F97583"> unsafe</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0"> asm!</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#9ECBFF"> "rdtsc"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#B392F0"> out</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"eax"</span><span style="color:#E1E4E8">) lo,</span></span>
<span class="line"><span style="color:#B392F0"> out</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"edx"</span><span style="color:#E1E4E8">) hi,</span></span>
<span class="line"><span style="color:#B392F0"> options</span><span style="color:#E1E4E8">(nomem, nostack, preserves_flags),</span></span>
<span class="line"><span style="color:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8"> ((hi </span><span style="color:#F97583">as</span><span style="color:#B392F0"> u64</span><span style="color:#E1E4E8">) </span><span style="color:#F97583"><<</span><span style="color:#79B8FF"> 32</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">|</span><span style="color:#E1E4E8"> (lo </span><span style="color:#F97583">as</span><span style="color:#B392F0"> u64</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This reads the time stamp counter with <code>rdtsc</code>, and returns its value.</p>
<p>Here’s the example in C:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="c"><code><span class="line"><span style="color:#F97583">#include</span><span style="color:#9ECBFF"> <stdint.h></span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">uint64_t</span><span style="color:#B392F0"> rdtsc</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">void</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">{</span></span>
<span class="line"><span style="color:#F97583"> uint32_t</span><span style="color:#E1E4E8"> lo, hi;</span></span>
<span class="line"><span style="color:#F97583"> __asm__</span><span style="color:#E1E4E8"> _</span><span style="color:#B392F0">_volatile__</span><span style="color:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#9ECBFF"> "rdtsc"</span></span>
<span class="line"><span style="color:#E1E4E8"> : </span><span style="color:#9ECBFF">"=a"</span><span style="color:#E1E4E8">(lo), </span><span style="color:#9ECBFF">"=d"</span><span style="color:#E1E4E8">(hi)</span></span>
<span class="line"><span style="color:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> ((</span><span style="color:#F97583">uint64_t</span><span style="color:#E1E4E8">)hi </span><span style="color:#F97583"><<</span><span style="color:#79B8FF"> 32</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">|</span><span style="color:#E1E4E8"> lo;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>These (under <code>-0</code> in both rustc <code>1.87.0</code> and clang <code>20.1.0</code>) produce the same
assembly:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="asm"><code><span class="line"><span style="color:#F97583">rdtsc</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#F97583"> rdtsc</span></span>
<span class="line"><span style="color:#F97583"> shl</span><span style="color:#79B8FF"> rdx</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">32</span></span>
<span class="line"><span style="color:#F97583"> mov</span><span style="color:#79B8FF"> eax</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">eax</span></span>
<span class="line"><span style="color:#F97583"> or</span><span style="color:#79B8FF"> rax</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">rdx</span></span>
<span class="line"><span style="color:#F97583"> ret</span></span></code></pre>
<p>Here’s a link on Godbolt: <a href="https://godbolt.org/z/f7K8cfnx7">https://godbolt.org/z/f7K8cfnx7</a></p>
<p>Does this count? I don’t know. I don’t think it really speaks to the question
asked, but it is one way to answer the question.</p>
<h2 id="similar-code-different-results">Similar code, different results</h2>
<p>Rust and C can have different semantics for similar code. Here’s a struct in
Rust:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Rust</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> x</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> y</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u64</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> z</span><span style="color:#F97583">:</span><span style="color:#B392F0"> u32</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Here’s “the same” struct in C:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="c"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#E1E4E8"> C {</span></span>
<span class="line"><span style="color:#F97583"> uint32_t</span><span style="color:#E1E4E8"> x;</span></span>
<span class="line"><span style="color:#F97583"> uint64_t</span><span style="color:#E1E4E8"> y;</span></span>
<span class="line"><span style="color:#F97583"> uint32_t</span><span style="color:#E1E4E8"> z;</span></span>
<span class="line"><span style="color:#E1E4E8">};</span></span></code></pre>
<p>In Rust, this struct is 16 bytes (on x86_64, again) and in C, it is 24. This is
because Rust is free to reorder the fields to optimize for size, while C
is not.</p>
<p>Is this the same, or different?</p>
<p>In C, you can re-order the fields to get the same size. In Rust, you can write
<code>#[repr(C)]</code> to get the same layout as C. Does this mean we should have written
different Rust or different C to get “the same” thing?</p>
<h2 id="social-factors">Social Factors</h2>
<p>Some people have reported that, thanks to Rust’s checks, they are more willing
to write code that’s a bit more dangerous than in the equivalent C (or C++),
where they’d do a bit more copying to play it a bit safer. This would be “the
same” in the sense of the same devs on the same project, but the code would be
different, due to judgement calls. You can make an argument that that’s not the
same, but is different too.</p>
<p>An example of this from a long time ago is the Stylo project. Mozilla tried to
parallelize Firefox’s style layout twice in C++, and both times the project
failed. The multithreading was too tricky to get right. The third time, they
used Rust, and managed to ship. This is the same project, (though not the same
programmers, I believe) by the same organization, but one was possible and one
was not. Is that “the same”? In some senses, but not in others.</p>
<p>This also goes for a similar question: assuming we have a junior developer
writing Rust, and also writing C, for the same task. Are we going to get faster
code in one or the other? This controls for ability, but not for the same code.
Is that “the same”? I don’t know. What about an expert in each language, someone
who knows Rust super well but doesn’t know C, and vice versa, being given the
same task? Is that different than a junior, or an “average” developer?</p>
<h2 id="compile-time-vs-runtime">Compile time vs runtime?</h2>
<p>Another redditor asks:</p>
<blockquote>
<p>I’m not Rust expert, but aren’t most (all?) of the safety checks compile time
checks? They shouldn’t have any runtime impact.</p>
</blockquote>
<p>A great question! Part of this is another example of defaults being different.</p>
<p><code>array[0]</code> is valid in both languages.</p>
<p>In Rust, there’s a bounds check at runtime. In C, there is not. Does this mean
that they’re the same? In Rust, I could write <code>array.get_unchecked(0)</code>, and get
C’s semantics. In C, I could write a bounds check to get Rust’s semantics. Are
any of those “the same”?</p>
<p>In Rust, that check may be optimized away, if the compiler can prove it’s safe.
In C, if we wrote the bounds check by hand, the check may be optimized away, if
the compiler can prove it’s safe. Are any of those “the same”?</p>
<p>They aren’t wrong that a lot of Rust’s safety checks are at compile time. But
some are at runtime. But this raises another interesting question: That compile-time
check may cause you to write different code for the same task as in C. A common
example is using indices rather than pointers. That may mean that the generated
code performs differently. Is that check truly “at compile time”? Technically,
at the micro level, yes. At the engineering level? Possibly not!</p>
<h2 id="my-conclusions">My Conclusions</h2>
<p>I think the most important part of this question is related to possibility, that
is:</p>
<ol>
<li>If we assume C is the ‘fastest language,’ whatever that means.</li>
<li>Is there any inherent reason why Rust could not do the same things?</li>
</ol>
<p>I think the answer to that is “no,” even ignoring the inline assembly case. So
on that most important, fundamental level, the answer is “there’s no difference
between the two.”</p>
<p>But we’re not usually talking about that. We’re usually talking about something
in the context of engineering, a specific project, with specific developers,
with specific time constraints, and so on. I think that there are so many
variables that it is difficult to draw generalized conclusions.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lr6xouhoxc2i" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Is <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> faster than C?
<a target="_blank" href="https://steveklabnik.com/writing/is-rust-faster-than-c/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/is-r...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/is-rust-faster-than-c/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Is Rust faster than C?</p> <p class="description s-rtbqd8">Blog post: Is Rust faster than C? by Steve Klabnik</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-06-09T18:04:36.057Z" class="date s-hik11q">June 9, 2025 at 6:04 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="91 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>91</span></span> <span class="stat s-hik11q" title="19 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>19</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lr6xouhoxc2i" target="_blank" class="permalink s-hik11q"><span>Read 10 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>I am disappointed in the AI discourse</title><link>https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/</link><guid isPermaLink="true">https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/</guid><description>Recently, something happened that made me kind of like, break a little bit. I’m still not entirely sure why this whole thing bothers me so much, but here’s a blog post about it. I happened to ask ChatGPT something for fun. I often do this just to see what it says, and if I feel…</description><pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Recently, something happened that made me kind of like, break a little bit. I’m
still not entirely sure why this whole thing bothers me so much, but here’s a
blog post about it. I happened to ask ChatGPT something for fun. I often do this
just to see what it says, and if I feel like it’s right or wrong. I tabbed over
to another tab, and the top post on my Bluesky feed was something along these
lines:</p>
<blockquote>
<p>ChatGPT is not a search engine. It does not scan the web for information. You
cannot use it as a search engine. LLMs only generate statistically likely
sentences.</p>
</blockquote>
<p>The thing is… ChatGPT was over there, in the other tab, searching the web. And
the answer I got was pretty good.</p>
<p>I’m not linking to the post because I don’t want to continue to pile on to this
person, because it’s not really about them. It’s about the state of the
discourse around AI.</p>
<p>I am not claiming that ChatGPT is an amazing search engine. What is breaking my
brain a little bit is that all of the discussion online around AI is so
incredibly polarized. This isn’t a “the middle is always right” sort of thing
either, to be clear. It’s more that both the pro-AI and anti-AI sides are loudly
proclaiming things that are pretty trivially verifiable as not true. On the anti
side, you have things like the above. And on the pro side, you have people
talking about how no human is going to be programming in 2026. Both of these
things fail a very basic test of “@gork is this true?” (sorry this is a bad
joke, if you’re not terminally <del>38 years old</del>on Bluesky, substitute “chat is
this true?” (sorry this is a bad joke, if you’re over the age of 40 substitute
“is this true?” (sorry humor is one way of coping with stress and writing this
post is stressing me out))).</p>
<p>Oh, and of course, ethical considerations of technology are important too. I’d
like to have better discussions about that as well. <del>For me, capabilities precede
the ethical dimension, because the capabilities inform what is and isn’t
ethical.</del> (EDIT: I am a little unhappy with my phrasing here. A shocker given
that I threw this paragraph together in haste! What I’m trying to get at is that
I think these two things are inescapably intertwined: you cannot determine the
ethics of something until you know what it is. I do not mean that capabilities
are somehow more important.) But I also know reasonable people disagree with me
on that. Let’s talk about it! I mean that for <em>all</em> dimensions of this topic,
not solely the capabilities.</p>
<p>Now, it’s not like I expect everyone to come to some consensus on a technology.
For example, a lot of people like Rust and hate Go, and a lot of people like Go
and hate Rust. That’s fine. That doesn’t bother me. But there’s something
different about this that is driving me up a wall.</p>
<p>To be clear, I am not particularly pro or anti AI. Here’s what I currently
think:</p>
<ul>
<li>LLMs can be helpful for software development.</li>
<li>LLMs for software development are being oversold by some people who stand to
gain a lot of money from them.</li>
<li>LLMs for software development are being undersold by some people who have
decided for various reasons that they do not like the technology.</li>
<li>Not everyone who dislikes LLMs is uninformed, just like not everyone who likes
LLMs is a grifter.</li>
<li>AI art is often terrible and slightly creepy.</li>
<li>I have seen AI art that was fine. This is very rare.</li>
<li>AI writing is often bland and boring but better than the average person’s
writing. Just to be clear, I feel like an elitist dick saying that. And maybe
I am. I dunno, working on it.</li>
</ul>
<p>Anyway, just to be clear, (how many times can you say ‘just to be clear’ in one
post, Steve, come on (at what level of nested parenthetical do I implement some
sort of interlocutor into my blog like <a href="https://xeiaso.net/">Xe</a>)) I don’t think that if you
think LLMs suck at software development, you’re wrong. What I want to be able to
do is talk about it in a reasonable way, and figure out why we have different
opinions. That is it. But for some reason, being able to do that feels
impossible.</p>
<p>I’m going to end up blogging more about AI/LLMs in the near future, to get some
more of this stuff off of my chest and to try and be the change I want to see in
the world. So expect some of that, probably. But until then, if you happen to
have any links to reasonable discussion on these topics, I’d love to see them.
Here’s two posts I recently read and really enjoyed thinking about what they
were saying:</p>
<ul>
<li><a href="https://annievella.com/posts/the-software-engineering-identity-crisis/">The Software Engineering Identity Crisis</a></li>
<li><a href="https://jmsdnns.com/tech/algo-underground/">Algorithmic Underground</a></li>
</ul>
<p>Thanks. If you want to get mad about this post please just ignore me.</p>
<hr/>
<p>Here’s my post about this post on Bluesky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lqaqe6uc3c2u" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">I am disappointed in the AI discourse <a target="_blank" href="https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/i-am...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">I am disappointed in the AI discourse</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-05-28T17:33:29.514Z" class="date s-hik11q">May 28, 2025 at 5:33 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="909 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>909</span></span> <span class="stat s-hik11q" title="266 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>266</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lqaqe6uc3c2u" target="_blank" class="permalink s-hik11q"><span>Read 208 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Rust 1.0, ten years later</title><link>https://steveklabnik.com/writing/rust-ten-years-later/</link><guid isPermaLink="true">https://steveklabnik.com/writing/rust-ten-years-later/</guid><description>It’s kind of hard to believe it’s been ten years since Rust 1.0 was released. I stopped working on Rust three years ago, and frankly, the last three years have been pretty rough. But 2025 is shaping up to be a really good year for me, with lots of positive changes. With that in…</description><pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>It’s kind of hard to believe it’s been ten years since Rust 1.0 was released.</p>
<p>I stopped working on Rust three years ago, and frankly, the last three years
have been pretty rough. But 2025 is shaping up to be a really good year for me,
with lots of positive changes.</p>
<p>With that in mind, this post is going to be a bit of a different one than you
might expect. For a fantastic post that’s a bit more traditional, check out
what Graydon wrote for the Rust Foundation: <a href="https://rustfoundation.org/media/10-years-of-stable-rust-an-infrastructure-story/">10 years of stable Rust: an
infrastructure story</a>. It’s fantastic, of course.</p>
<p>I’d instead like to reflect a bit on a comment I saw on the internet the other day:</p>
<blockquote>
<p>[Rust is] not a great hobby language but it is a fantastic professional
language, precisely because of the ease of refactors and speed of development
that comes with the type system and borrow checker.</p>
</blockquote>
<p><a href="https://pcwalton.github.io/">Patrick Walton</a>, one of the earliest contributors to Rust, had this
to say:</p>
<blockquote>
<p>I never thought I’d live to see the day when someone would say this. The first
5 years of Rust were all “this is interesting for hobby projects but nobody will
ever adopt this in industry”.</p>
</blockquote>
<p>The release of Rust 1.0 was great, but it was also stressful. The few years
before Rust 1.0 were marked by a deep, deep anxiety among the team. I’m sure
someone else who was around back then will disagree with me, but that’s my
recollection. It may not have always been overt, but it was there. A large
question was looming over us: would anyone actually use this thing? Would it
become a hobbyist language, or would it be something that people actually
used in production?</p>
<p>At this point, we now know the answer: yes, Rust is used a lot. It’s used
for real, critical projects to do actual work by some of the largest companies
in our industry. We did good.</p>
<p>The release of 1.0 was the start of that journey in earnest. It was time to stop
changing things all the time, and to start focusing on stability. It was time to
gain more users, and to fix the issues that they found. We had to start shipping
stable releases, and we had to start doing it on a regular basis. We had to
start doing all of the things that a real programming language does.</p>
<p>Over the last ten years, a lot changed. It wasn’t easy, and it took a lot of
blood, sweat, and many, many tears. But we did it. We built a real programming
language, and we built a real community around it.</p>
<p>Rust is very much not perfect. There’s more work to be done, and arguably,
someone out there should probably be starting a language that will someday eat
Rust’s lunch.</p>
<p>But ten years ago, we were pushing a button and praying that it would all work
out. And now, ten years later, I’m doing this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>❯ rustup update stable</span></span>
<span class="line"><span>info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'</span></span>
<span class="line"><span>info: latest update on 2025-05-15, rust version 1.87.0 (17067e9ac 2025-05-09)</span></span>
<span class="line"><span>info: downloading component 'rust-src'</span></span>
<span class="line"><span>info: downloading component 'cargo'</span></span>
<span class="line"><span>info: downloading component 'clippy'</span></span>
<span class="line"><span>info: downloading component 'rust-docs'</span></span>
<span class="line"><span>info: downloading component 'rust-std'</span></span>
<span class="line"><span>info: downloading component 'rustc'</span></span>
<span class="line"><span>info: downloading component 'rustfmt'</span></span>
<span class="line"><span>info: removing previous version of component 'rust-src'</span></span>
<span class="line"><span>info: removing previous version of component 'cargo'</span></span>
<span class="line"><span>info: removing previous version of component 'clippy'</span></span>
<span class="line"><span>info: removing previous version of component 'rust-docs'</span></span>
<span class="line"><span>info: removing previous version of component 'rust-std'</span></span>
<span class="line"><span>info: removing previous version of component 'rustc'</span></span>
<span class="line"><span>info: removing previous version of component 'rustfmt'</span></span>
<span class="line"><span>info: installing component 'rust-src'</span></span>
<span class="line"><span>info: installing component 'cargo'</span></span>
<span class="line"><span>info: installing component 'clippy'</span></span>
<span class="line"><span>info: installing component 'rust-docs'</span></span>
<span class="line"><span> 19.9 MiB / 19.9 MiB (100 %) 16.3 MiB/s in 1s ETA: 0s</span></span>
<span class="line"><span>info: installing component 'rust-std'</span></span>
<span class="line"><span> 29.4 MiB / 29.4 MiB (100 %) 22.2 MiB/s in 1s ETA: 0s</span></span>
<span class="line"><span>info: installing component 'rustc'</span></span>
<span class="line"><span> 76.3 MiB / 76.3 MiB (100 %) 23.9 MiB/s in 3s ETA: 0s</span></span>
<span class="line"><span>info: installing component 'rustfmt'</span></span>
<span class="line"><span></span></span>
<span class="line"><span> stable-x86_64-unknown-linux-gnu updated - rustc 1.87.0 (17067e9ac 2025-05-09) (from rustc 1.86.0 (05f9846f8 2025-03-31))</span></span></code></pre>
<p>and then going about my day. It is a rare privilege to be able to spend ten
years building a tool you want to use, and then be able to spend your time at
work using it. I am grateful to have had that opportunity, and I hope that
everyone else who has worked on Rust over the years feels the same way.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lpa37moavc2s" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd"><a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> 1.0, ten years later <a target="_blank" href="https://steveklabnik.com/writing/rust-ten-years-later/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/rust...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/rust-ten-years-later/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Rust 1.0, ten years later</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-05-15T17:49:56.018Z" class="date s-hik11q">May 15, 2025 at 5:49 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="198 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>198</span></span> <span class="stat s-hik11q" title="35 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>35</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lpa37moavc2s" target="_blank" class="permalink s-hik11q"><span>Read 13 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Thoughts on Bluesky Verification</title><link>https://steveklabnik.com/writing/thoughts-on-bluesky-verification/</link><guid isPermaLink="true">https://steveklabnik.com/writing/thoughts-on-bluesky-verification/</guid><description>Today, Bluesky rolled out blue checks . And, I was given one. Now, I’m not the biggest fan of this sort of feature, however, I also don’t really think this kind of feature is for me. I really like the idea of domain verification; I have been like “oh okay that’s coming from a…</description><pubDate>Mon, 21 Apr 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Today, <a href="https://bsky.social/about/blog/04-21-2025-verification">Bluesky rolled out blue checks</a>. And, I was given one.
Now, I’m not the biggest fan of this sort of feature, however, I also don’t
really think this kind of feature is for me. I really like the idea of domain
verification; I have been like “oh okay that’s coming from a <code>.gov</code> account”
from time to time. But I don’t think most people really think about domains the
way that programmers do.</p>
<p>I have wanted to update my <a href="https://steveklabnik.com/writing/how-does-bluesky-work/">“How does Bluesky work?”</a> post for a while
now, but I’ve been super busy. So, let’s ignore the product question for now,
and focus on the technical question. How does verification work?</p>
<h2 id="the-golden-rule-of-atproto">The golden rule of atproto</h2>
<p>I’ve also been thinking about designing apps on top of atproto. There’s a kind
of rule of thumb that I realized about doing this, a sort of “golden rule” if
you will:</p>
<blockquote>
<p>You are the only person who can write records into your PDS.</p>
</blockquote>
<p>This has really interesting implications! For example, sometimes people ask for
“soft blocks” on Bluesky. This is a twitter ‘feature’ where if you block
someone, and then unblock them, they’re not following you any more. But on
Bluesky, if you soft block someone, they’re still following you. Why does that
happen?</p>
<p>Following someone on Bluesky means that you write a record of the lexicon
<a href="https://github.com/bluesky-social/atproto/blob/a1b2370dbc451570eafb982f326e8c110277ac01/lexicons/app/bsky/graph/follow.json"><code>app.bsky.graph.follow</code></a> into your PDS. So you might assume that
blocking someone would delete this record from their PDS. But that would violate
the golden rule! You can’t delete records from someone else’s PDS. So, instead,
blocking someone on Bluesky means that you write a record of the lexicon
<a href="https://github.com/bluesky-social/atproto/blob/a1b2370dbc451570eafb982f326e8c110277ac01/lexicons/app/bsky/graph/block.json"><code>app.bsky.graph.block</code></a> into your PDS. Unblocking them deletes that
record from your PDS. But it doesn’t delete the record from their PDS. So, if
you block someone, and then unblock them, they still have a record in their PDS
that says they’re following you.</p>
<p>This may not be the behavior that you want as a user, but it follows from the
constraints of the overall protocol design.</p>
<h2 id="how-verification-works">How verification works</h2>
<p>Now that we know the golden rule, we can understand how verification works. How
does an account get verified? Well, someone has to be writing a record into a
PDS somewhere. The natural design is the one they’ve chosen: someone becomes
verified by someone else writing a record of the type
<a href="https://github.com/bluesky-social/atproto/blob/a1b2370dbc451570eafb982f326e8c110277ac01/lexicons/app/bsky/graph/verification.json"><code>app.bsky.graph.verification</code></a> into their PDS. <a href="https://pdsls.dev/at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.graph.verification/3lndpy3ngb62l">Here</a> is
the record verifying me. It looks like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>
<span class="line"><span style="color:#79B8FF"> "$type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"app.bsky.graph.verification"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "handle"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"steveklabnik.com"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "subject"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"did:plc:3danwc67lo7obz2fmdg6jxcr"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "createdAt"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"2025-04-21T10:49:07.620Z"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> "displayName"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Steve Klabnik"</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This is in <code>@bsky.app</code>’s PDS. It describes my DID, and my handle and display
name. One thing not mentioned in the blog post, but is in the comments of the
lexicon, is that changing your handle or display name makes this record invalid.
I didn’t know that! Good to remember in case I decide to make some jokes in my
display name.</p>
<p>That’s the basics: a record exists, and a blue check shows up on my account. But
wait, isn’t this centralized? I thought Bluesky was decentralized? Well, yes and
no.</p>
<p>You see, anyone can put a record of this type into their PDS. So, if I wanted to
verify someone else, I can do that too: here’s <a href="https://pdsls.dev/at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.graph.verification/3lnduurfk7q2k">me verifying
<code>@jcsalterego.bsky.social</code></a>. But if you go to Jerry’s profile, you won’t
see a blue check. Why is that? Well, because the Bluesky AppView only shows a
blue check if the record is from a “trusted verifier.” Who is that? Well, as far
as we know, it’s from <code>@bsky.app</code> and <code>@nytimes.com</code>. Why can’t we tell more?
Well, they’ve implemented this as a database column in the AppView, it’s not
“in-protocol,” in other words.</p>
<p>EDIT: Samuel has provided us with the list:</p>
<bluesky-post src="at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.feed.post/3lndyqyyr4k2k" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:p2cp5gopk7mgjegy6wadk3ep/bafkreihrdgva6ebn3vmjb4qww3yqozf4eoq26mmraouegpawhr5mo5pnxu" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Samuel</span></bdi> <span class="handle s-hik11q">@samuel.fm</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="context s-hik11q">Replying to an unknown post</p> <p class="rich-text is-large s-1lecfnd"><a target="_blank" href="https://bsky.app/profile/did:plc:z72i7hdynmk6r22z27h6tvur" class="mention s-1lecfnd">@bsky.app</a>
<a target="_blank" href="https://bsky.app/profile/did:plc:eclio37ymobqex2ncko63h4r" class="mention s-1lecfnd">@nytimes.com</a>
<a target="_blank" href="https://bsky.app/profile/did:plc:inz4fkbbp7ms3ixufw6xuvdi" class="mention s-1lecfnd">@wired.com</a>
<a target="_blank" href="https://bsky.app/profile/did:plc:b2kutgxqlltwc6lhs724cfwr" class="mention s-1lecfnd">@theathletic.bsky.social</a></p> <time datetime="2025-04-21T20:26:13.833Z" class="date s-hik11q">April 21, 2025 at 8:26 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="25 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>25</span></span> <span class="stat s-hik11q" title="10 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>10</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep/post/3lndyqyyr4k2k" target="_blank" class="permalink s-hik11q"><span>Read 1 reply on Bluesky</span></a></div></div></div> </bluesky-post>
<p>So in some sense this is a centralized feature: clients can display information
however they choose, and they’ve made a product choice that only trusted
verifiers will show up as blue checks. But in another sense, it’s not
centralized, as anyone has the power to verify anyone else. Alternative clients
can decide to show or hide any of this information; <a href="https://deer.social/">Deer Social</a>, one of these
alternate clients, has added a user preference to hide all blue
checks, for example. A different one could show every verification as a blue
check, or display them differently, say one badge per verification, styled like
the avatar of the person who verified you.</p>
<p>EDIT: turns out that the default AppView also lets you hide blue checks:</p>
<bluesky-post src="at://did:plc:35kdk2ntcs626zs6cm62i7ih/app.bsky.feed.post/3lne5sehgxc2y" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Steve Klabnik</span></bdi> <span class="handle s-12mzi62">@steveklabnik.com</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lne4frl43s2d" title="April 21, 2025 at 9:31 PM" class="date s-12mzi62"><time datetime="2025-04-21T21:31:31.924Z">Apr 21, 2025</time></a></div> <p class="rich-text is-small s-1lecfnd">Thoughts on Bluesky verification
<a target="_blank" href="https://steveklabnik.com/writing/thoughts-on-bluesky-verification/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/thou...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/thoughts-on-bluesky-verification/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Thoughts on Bluesky Verification</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div></div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:35kdk2ntcs626zs6cm62i7ih" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:35kdk2ntcs626zs6cm62i7ih/bafkreieezekf5jp4apfb6y3kfam5tvs32ktqbgtoq5waqunu2omo7tufkq" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:35kdk2ntcs626zs6cm62i7ih" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">jolheiser</span></bdi> <span class="handle s-hik11q">@jolheiser.com</span></a> </div> <p class="rich-text is-large s-1lecfnd">This was very informative, thanks for the write-up!
I was curious about being able to hide the checkmarks, but unless I'm mistaken this appears to also be an option on the bluesky appview.
At least I was able to check the preference in my settings->moderation->verification settings</p> <time datetime="2025-04-21T21:56:28.122Z" class="date s-hik11q">April 21, 2025 at 9:56 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="2 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>2</span></span> <span class="stat s-hik11q" title="0 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>0</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:35kdk2ntcs626zs6cm62i7ih/post/3lne5sehgxc2y" target="_blank" class="permalink s-hik11q"><span>Read 1 reply on Bluesky</span></a></div></div></div> </bluesky-post>
<p>The point is still the same though: there’s choice here. Back to the original post!</p>
<p>The underlying protocol is totally open, but you can make an argument that most
users will use the main client, and therefore, in practice it’s more centralized
than not. My mental model of it is the bundled list of root CAs that browsers
ship; in theory, DNS is completely decentralized, but in practice, a few root
CAs are more trusted than others. It’s sort of similar here, except there’s no
real “delegation” involved: verifiers are peers, not a tree.</p>
<p>This core design allows Bluesky to adjust the way it works in the future fairly
easily, they could allow you to decide who to trust, for example. We’ll see how
this feature evolves over time.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I’m flattered that I was chosen to be verified; if I was trying to ship this
feature, my name wouldn’t come up 181st on the list. I was really curious about
the design when they announced this feature, I assumed it was going to be closer
to a PGP web of trust, or delegated in some way. This design is much simpler and
less centralized than I expected: this is arguably more horizontal than the web
of trust would have been.</p>
<p>At the same time, I am interested in seeing if this changes the culture of the
site; some see the blue check as a status symbol of some kind, and think it
means what you say matters more. I don’t think that’s true, personally, but it
doesn’t really matter what I think. To redraw the analogy with DNS, the blue
check is very similar to the green lock: it doesn’t mean that what you’re saying
is true, or right, or good, it just means that you are who you say you are. But
even though a blue check is technically isomorphic (or close enough) to a green
lock, I think a lot of people perceive it as being more than that. We’ll just
have to see. What I am glad about it is that it’s an in-protocol design, rather
than an external one like DMs are. I understand why they did it that way, but
I’d rather that remain the exception rather than the rule.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lne4frl43s2d" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Thoughts on Bluesky verification
<a target="_blank" href="https://steveklabnik.com/writing/thoughts-on-bluesky-verification/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/thou...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/thoughts-on-bluesky-verification/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Thoughts on Bluesky Verification</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-04-21T21:31:31.924Z" class="date s-hik11q">April 21, 2025 at 9:31 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="227 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>227</span></span> <span class="stat s-hik11q" title="48 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>48</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lne4frl43s2d" target="_blank" class="permalink s-hik11q"><span>Read 24 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Thinking like a compiler: places and values in Rust</title><link>https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/</link><guid isPermaLink="true">https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/</guid><description>A while back, someone on the internet asked about this syntax in Rust: * pointer_of_some_kind = blah; They wanted to know how the compiler understands this code, especially if the pointer wasn’t a reference, but a smart pointer. I wrote them a lengthy reply, but wanted to expand…</description><pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>A while back, someone on the internet asked about this syntax in Rust:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">*</span><span style="color:#E1E4E8">pointer_of_some_kind </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> blah;</span></span></code></pre>
<p>They wanted to know how the compiler understands this code, especially if the
pointer wasn’t a reference, but a smart pointer. I wrote them a lengthy reply,
but wanted to expand and adapt it into a blog post in case a broader audience
may be interested.</p>
<p>Now, I don’t work on the Rust compiler, and haven’t really ever, but what I do
know is language semantics. If you’re a language nerd, this post may not be
super interesting to you, other than to learn about Rust’s value categories, but
if you haven’t spent a lot of time with the finer details of programming
languages, I’m hoping that this may be a neat peek into that world.</p>
<hr/>
<p>Programming languages are themselves languages, in the same sense that human
languages are. Well, mostly, anyway. The point is, to understand some Rust code
like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">*</span><span style="color:#E1E4E8">pointer_of_some_kind </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> blah;</span></span></code></pre>
<p>We can apply similar tools to how we might understand some “English code” like
this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>You can't judge a book by its cover.</span></span></code></pre>
<p>Oh, you also might ask yourself as you’re reading the next sections, “why so
many steps?” The short answer is a classic one: by breaking down the big problem
of “what does this mean?” into smaller steps, each step is easier. Doing everything
at once is way more difficult than a larger number of smaller steps. I’m going
to cover the classical ways compilers work, there’s a ton of variety when you
start getting into more modern ways of doing things, and often these steps are
blended together, or done out of order, all kinds of other things. Handling
errors is a huge topic in and of itself! Consider this a starting point, not an
ending one.</p>
<p>Let’s get into it.</p>
<h2 id="lexical-analysis-aka-scanning-or-tokenizing">Lexical Analysis (aka ‘scanning’ or ‘tokenizing’)</h2>
<p>The first thing we want to do is to try and figure out if these words are even
valid words at all. With computer languages, this process is called “lexical
analysis,” though you’ll also hear the term “tokenizing” to describe it. In other
words, we’re not interested in any sort of meaning at all at this stage, we just
want to figure out what we’re even talking about.</p>
<p>So if we look at this English sentence:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>You can't judge a book by its cover.</span></span></code></pre>
<p>We follow a two step process in order to tokenize it: we first “scan” it to
produce a sequence of “lexemes.” We do this by following some rules. I’m not
going to give you a sample of the rules for English here, as this post is
already long enough. But you might end up with something like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>You</span></span>
<span class="line"><span>can't</span></span>
<span class="line"><span>judge</span></span>
<span class="line"><span>a</span></span>
<span class="line"><span>book</span></span>
<span class="line"><span>by</span></span>
<span class="line"><span>its</span></span>
<span class="line"><span>cover</span></span>
<span class="line"><span>.</span></span></code></pre>
<p>Note how we do have <code>'</code> in <code>can't</code>, but <code>.</code> is separate from <code>cover</code>. These
are the kinds of rules we’d be following: the <code>'</code> is because this is a contraction,
but the <code>.</code> is not really part of <code>cover</code>, but its own thing.</p>
<p>We then run a second step and evaluate each individual string of characters, turning
them into “tokens.” A token is some sort of data type in your compiler, probably, so
for example we could do this in Rust:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">enum</span><span style="color:#B392F0"> Token</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">String</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Punctuation</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">String</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>And so the output of our tokenizer might be an array of something like</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#E1E4E8">[</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"You"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"can't"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"judge"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"a"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"book"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"by"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"its"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Word</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"cover"</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#B392F0"> Punctuation</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"."</span><span style="color:#E1E4E8">),</span></span>
<span class="line"><span style="color:#E1E4E8">]</span></span></code></pre>
<p>At this point, we know we have something semi-coherent, but we’re still
not sure it’s valid yet. On to the next step!</p>
<h2 id="syntactic-analysis-aka-parsing">Syntactic Analysis (aka ‘parsing’)</h2>
<p>Funny enough, this is an area where human-language linguistics and compilers
mean things that are slightly different. With human language, parsing is often
combined with our next step, which is semantic analysis. But we (most of the
time) try to separate syntax and semantic analysis in compilers.</p>
<p>Again, I’m going to massively simplify with the English. We’re going to use
these rules for what a sentence is:</p>
<ol>
<li>A sentence is a sequence of words.</li>
<li>A sentence has a “subject,” which is the first word, and must be capitalized.</li>
<li>A sentence ends with a period.</li>
</ol>
<p>Obviously this is a tiny subset of English, but you get the idea. The goal of
syntactic analysis is to turn our sequence of tokens into a richer data
structure that’s easier to work with. In other words, we’ve figured out that
our sentence is made up of valid sequences of characters, but do they fit the
grammatical rules of our language? Note that we also don’t need to store everything;
for example, maybe our English data structure looks like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Sentence</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> subject</span><span style="color:#F97583">:</span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">String</span><span style="color:#E1E4E8">>,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Where’s the period? How do we know <code>subject</code> must be capitalized? That’s the job
of syntactic analysis. Since every sentence ends with a period, we don’t need to
track it in our data structure: the analysis makes sure that it’s true, and then
we aren’t doing it again. Likewise, we don’t need to store our subject as a
capitalized string if we don’t want to: we determined that the input was, but we
can transform it as needed. So our value after syntax analysis might look like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#B392F0">Sentence</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> subject</span><span style="color:#F97583">:</span><span style="color:#9ECBFF"> "you"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> [</span><span style="color:#9ECBFF">"can't"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"judge"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"a"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"book"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"by"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"its"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"cover"</span><span style="color:#E1E4E8">],</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Often, for computer languages, a tree-like structure works well, and so you’d
see an “abstract syntax tree,” or “AST” at the end of this stage. But it’s not
strictly speaking required, whatever data structure makes sense for you can
work.</p>
<p>Now that we have a richer data structure, we’re almost done. Now we have to get
into meaning.</p>
<h2 id="semantic-analysis-aka-wtf-does-this-mean">Semantic Analysis (aka ‘wtf does this mean’)</h2>
<p>Imagine our sentence wasn’t “You can’t judge a book by its cover.” but instead
this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor</span></span>
<span class="line"><span>incididunt ut labore et dolore magna aliqua.</span></span></code></pre>
<p>This is a famous bit of text that’s incoherent. The words are all Latin words,
and it <em>feels</em> like it might be a sentence, but it’s nonsense. We could parse
this into a <code>Sentence</code>:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#B392F0">Sentence</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> subject</span><span style="color:#F97583">:</span><span style="color:#9ECBFF"> "Lorem"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> [</span><span style="color:#9ECBFF">"ipsum"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"dolor"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"sit"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"amet"</span><span style="color:#E1E4E8">, </span><span style="color:#6A737D">/* and continued */</span><span style="color:#E1E4E8"> ],</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>But it’s not valid. How do we determine <em>that</em>?</p>
<p>Well, in the context of English, “Lorem” isn’t a valid English word. So if we
were to check that the subject is a valid word, we’d successfully reject this
sentence. In computer languages, we’d do similar things like type checking:
<code>5 + "hello"</code> might lex and parse just fine, but when we try and figure out
what it means, we learn it’s nonsense. Except if your language lets you add
numbers and strings!</p>
<p>After semantic analysis, we’ve determined that our program is good, aka “well
formed.” In a compiler, we’d then go on to generate machine code or byte code
to represent our program. But that stuff, while incredibly interesting, isn’t
what we’re talking about here: remember our original objective? It was to
understand this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">*</span><span style="color:#E1E4E8">pointer_of_some_kind </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> blah;</span></span></code></pre>
<p>That’s semantics. So now that we have some background, let’s talk about how
to understand this code.</p>
<h2 id="so-how-do-i-understand-this-code">So… how do I understand this code?</h2>
<p>Well, to understand the code, we first need to understand how it lexes and
parses. In other words, what the <em>grammar</em> of our language is. How our language
would lex, tokenize, and then parse our code. In this case, it’s Rust. Rust’s
grammar is large and complex, so we’ll only be talking about part of it today.
We’re going to focus on statements vs expressions.</p>
<p>You may have heard that Rust is an “expression based language” before. Well,
this is what people mean. You see, when it comes to most of the things you say
in a program, they’re often one of these two things. Expressions are things that
produce some sort of value, and statements are used to sequence the evaluation
of expressions. That’s a bit abstract, so let’s get concrete.</p>
<h3 id="statements">Statements</h3>
<p>Rust has a few kinds of statements: first, there’s “declaration statements”
and “expression statements,” and each have their own sub-kinds as well.</p>
<p>Declaration statements have two kinds: <em>item declarations</em>, and <em>let statements</em>.
Item declarations are things like <code>mod</code> or <code>struct</code> or <code>fn</code>: they declare certain
things exist. <code>let</code> statements are probably the most famous form of statement in
Rust, they look like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span> OuterAttribute* let PatternNoTopAlt ( : Type )? (= Expression † ( else BlockExpression) ? ) ? ;</span></span></code></pre>
<p>That’s… a mouthful. We haven’t talked about <code>*</code> or <code>?</code> yet, and we don’t really
want to cover some of the more exotic parts of Rust right now. So we’re going
to talk about this via a simpler grammar first:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>let Variable = Expression;</span></span></code></pre>
<p>This is how we create new variables in Rust: we say <code>let</code>, and then a name, an <code>=</code>,
and then finally some expression. The result of evaluating that expression becomes
the value of the variable.</p>
<p>This is leaving out a lot: the name isn’t just a name, it’s a pattern, which is
very cool. <code>let else</code> exists in Rust now, and that’s cool. We’re ignoring types
here. But you can get the basics with just this simple version.</p>
<p>Expression statements are much simpler:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>ExpressionWithoutBlock ; | ExpressionWithBlock ;?</span></span></code></pre>
<p>The <code>|</code> there is an or, so we can either have a single expression followed by a <code>;</code>,
or a block (denoted by <code>{}</code>s, which can optionally (the ? means it can exist or not)
be followed by a <code>;</code>.)</p>
<p>So to think like a compiler, you can start to figure out how to combine these rules.
For example:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> x </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> 5</span><span style="color:#F97583"> +</span><span style="color:#79B8FF"> 6</span></span>
<span class="line"><span style="color:#E1E4E8">};</span></span></code></pre>
<p>Here, we have a let statement, but the expression on the right hand side of the <code>=</code>
is an <code>ExpressionWithBlock</code>. Here’s a pop quiz for you: is the <code>;</code> part of the let
expression, or part of the expression on the right hand side?</p>
<p>The answer is, it’s part of the <code>let</code>. The let expression has a mandatory <code>;</code>, but
the block does not, and so:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>let x = ExpressionWithBlock;</span></span></code></pre>
<p>If we had the semicolon with the block, we’d still need the one for the let, and
so we’d have <code>};;</code>. Which the compiler accepts, but it warns about it.</p>
<p>Going back to our original code:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">*</span><span style="color:#E1E4E8">pointer_of_some_kind </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> blah;</span></span></code></pre>
<p>We don’t have a <code>let</code>, and this isn’t an item declaration: this is an expression
statement. We have a <code>ExpressionWithoutBlock</code>, followed by a <code>;</code>. So now we have
to talk about expressions.</p>
<h3 id="expressions">Expressions</h3>
<p>There are a <em>lot</em> of expression types in Rust. <a href="https://doc.rust-lang.org/stable/reference/expressions.html">Section 8.2 of the
Reference</a> has 19 sub-sections. Whew! In this case, this code is an
Operation Expression, and more specifically, an <a href="https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#assignment-expressions">Assignment Expression</a>:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>Expression = Expression</span></span></code></pre>
<p>Easy enough! So the left hand side of the <code>=</code> is an expression with <code>*pointer_of_some_kind</code>, and the right hand side is <code>blah</code>. Easy enough!</p>
<p>But these two expressions are in some ways, the entire reason that I wrote this
post. We just finally got here! You see, the reference has this to say about
assignment expressions:</p>
<blockquote>
<p>An assignment expression moves a value into a specified place.</p>
</blockquote>
<p>What are those?</p>
<h4 id="places-and-values">Places and Values</h4>
<p>C, and older versions of C++, called these two things “lvalue” and “rvalue,”
for “left” and “right”, like the sides of the <code>=</code>. More recent C++ standards
have more categories. Rust splits the difference: it only has two categories,
like C, but they map more cleanly to two of C++‘s categories. Rust calls
lvalues, the left hand side, a “place,” and rvalues, the right hand side,
a “value.” Here’s two more precise definitions, from the Unsafe Code Guidelines:</p>
<ul>
<li>A <a href="https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#place">place</a> is basically a pointer, but might contain more information
such as size or alignment. A place has a type, indicating the type of values
that it stores.</li>
<li>A <a href="https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#value">value</a> A value is what gets stored in a place. A value has a type.</li>
</ul>
<p>Both of these have expression forms, so a <em>place expression</em> produces a place
when it’s evaluated, and a <em>value expression</em> produces a value. And that’s
how <code>=</code> works: we have a place expression on the left, a value expression
on the right, and we put that value in that place. Easy enough!</p>
<p>Once again, the code we were trying to figure out:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">*</span><span style="color:#E1E4E8">pointer_of_some_kind </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> blah;</span></span></code></pre>
<p>So <code>*</code>, the dereference operator, takes the pointer, and evaluates to the place
where it points: its address. And <code>blah</code> gives us the value to put there.</p>
<p>Case closed? Not quite!</p>
<h3 id="deref">Deref</h3>
<p>Rust has a trait, <code>Deref</code>, that lets us override the <code>*</code> operator. So let’s
talk about this example, to make things easier:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">use</span><span style="color:#B392F0"> std</span><span style="color:#F97583">::</span><span style="color:#B392F0">ops</span><span style="color:#F97583">::</span><span style="color:#E1E4E8">{</span><span style="color:#B392F0">Deref</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">DerefMut</span><span style="color:#E1E4E8">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> DerefMutExample</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#E1E4E8"> value</span><span style="color:#F97583">:</span><span style="color:#B392F0"> T</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> </span><span style="color:#B392F0">Deref</span><span style="color:#F97583"> for</span><span style="color:#B392F0"> DerefMutExample</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#F97583"> type</span><span style="color:#B392F0"> Target</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> T</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> deref</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#F97583"> &</span><span style="color:#79B8FF">Self</span><span style="color:#F97583">::</span><span style="color:#B392F0">Target</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> &</span><span style="color:#79B8FF">self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">value</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> </span><span style="color:#B392F0">DerefMut</span><span style="color:#F97583"> for</span><span style="color:#B392F0"> DerefMutExample</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> deref_mut</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#F97583"> &mut</span><span style="color:#79B8FF"> Self</span><span style="color:#F97583">::</span><span style="color:#B392F0">Target</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> &mut</span><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">value</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#E1E4E8"> main () {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#F97583"> mut</span><span style="color:#E1E4E8"> x </span><span style="color:#F97583">=</span><span style="color:#B392F0"> DerefMutExample</span><span style="color:#E1E4E8"> { value</span><span style="color:#F97583">:</span><span style="color:#9ECBFF"> 'a'</span><span style="color:#E1E4E8"> };</span></span>
<span class="line"><span style="color:#F97583"> *</span><span style="color:#E1E4E8">x </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> 'b'</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0"> assert_eq!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'b'</span><span style="color:#E1E4E8">, x</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">value);</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>You can play with it <a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0d6c7f3d8a2c00bc79dabef47ab550e2">here</a>.</p>
<p>We didn’t talk about the kind of expression that’s relevant here yet: a <em>path
expression.</em></p>
<blockquote>
<p>Path expressions that resolve to local or static variables are place
expressions, other paths are value expressions.</p>
</blockquote>
<p>We talked about <code>let</code> earlier: in <code>let mut x = DerefMutExample { value: 'a' };</code>
above, the <code>x</code> is a path expression, and since it’s resolving to our new variable,
that means it’s a place expression. The <code>DerefMutExample { value: 'a' }</code> is a
value expression, because it doesn’t resolve to a variable.</p>
<p>Let’s talk about <code>*x = 'b';</code>, remember our assignment expression:</p>
<p>expression = expression;</p>
<p>And what it does: it moves a value into a place.</p>
<p>To understand how <code>*</code> works, we only need to add one more thing: the Dereference
expression. It’s produced by the deference operator, <code>*</code>. It looks like this:</p>
<p>*expression</p>
<p>Its semantics are pretty straightforward:</p>
<ul>
<li>If the expression has the type <code>&T</code>, <code>&mut T</code>, <code>*const T</code>, or <code>*mut T</code>, then
this evaluates to the place of the value being pointed to, and gives it the
same mutability.</li>
<li>If the expression is not one of those types, then it is equivalent to either
<code>*std::ops::Deref::deref(&x)</code> if it’s immutable or
<code>*std::ops::DerefMut::deref_mut(&mut x)</code></li>
</ul>
<p>That’s it. Now we have enough to fully understand <code>*x = 'b'</code>:</p>
<ul>
<li><code>'b'</code> is a value expression</li>
<li><code>*x</code> is not a pointer type, so we expand it to
<code>*std::ops::DerefMut::deref_mut(&mut x)</code> and try again</li>
<li><code>std::ops::DerefMut::deref_mut(&mut x)</code> returns the type <code>&mut char</code> in this
case, and it is pointing at the place of <code>self.value</code> (which I’m gonna call
<code><that place></code> for short), which currently has the value <code>'a'</code> stored in it. Now
we have <code>*&mut <that place></code>.</li>
<li>We now use the other rule of the dereference operator, we’re operating on a
<code>&mut T</code>, so <code>*&mut <that place></code> refers to <code><that place></code>.</li>
<li>We now have <code><that place> = 'b'</code>, and so we move that <code>'b'</code> into that place.</li>
</ul>
<p>Whew! That’s that.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Thinking like a compiler can be fun! Once you’ve mastered the idea of grammars
and gotten used to substituting things, you can figure out all sorts of
interesting stuff. In this specific case, sometimes people wonder why <code>Deref</code>
returns a reference if the whole goal is to show where something should point…
and if you didn’t know that dereference expressions expanded to something with
<code>*</code> in it, it would be confusing! But now you know. And hopefully learned some
interesting things about values, places, and how compilers think about code
along the way.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lmanw5qdik2o" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Thinking like a compiler: places and values in <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a>
<a target="_blank" href="https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/thin...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Thinking like a compiler: places and values in Rust</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-04-07T19:09:04.578Z" class="date s-hik11q">April 7, 2025 at 7:09 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="85 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>85</span></span> <span class="stat s-hik11q" title="11 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>11</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lmanw5qdik2o" target="_blank" class="permalink s-hik11q"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Does unsafe undermine Rust's guarantees?</title><link>https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/</link><guid isPermaLink="true">https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/</guid><description>When people first hear about unsafe in Rust, they often have questions. A very normal thing to ask is, “wait a minute, doesn’t this defeat the purpose?” And while it’s a perfectly reasonable question, the answer is both straightforward and has a lot of nuance. So let’s talk about…</description><pubDate>Mon, 17 Mar 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>When people first hear about <code>unsafe</code> in Rust, they often have questions. A very
normal thing to ask is, “wait a minute, doesn’t this defeat the purpose?” And while
it’s a perfectly reasonable question, the answer is both straightforward and has a
lot of nuance. So let’s talk about it.</p>
<p>(The straightforward answer is “no”, by the way.)</p>
<h2 id="memory-safe-languages-and-their-implementations">Memory safe languages and their implementations</h2>
<p>The first way to think about this sort of thing is to remember that programming
languages and their implementations are two different things. This is sometimes
difficult to remember, since many programming languages only have one
implementation, but this general principle applies even in those cases. That is,
there are the desired semantics of the language you are implementing, and then
the codebase that makes programs in that language do those things.</p>
<p>Let’s examine a “real” programming language to talk about this: <a href="https://en.wikipedia.org/wiki/Brainfuck">Brainfuck</a>.
Brainfuck programs are made up of eight different characters, each one performing
one operation. Brainfuck programs can only do those eight things, and nothing
more.</p>
<p>However, this simplicity leads to two interesting observations about the
differences between languages in the abstract and their implementations.</p>
<h3 id="unsafety-in-implementation">Unsafety in implementation</h3>
<p>The first is that properties of the language itself are distinct from properties
of an implementation of the language. The semantics of the Brainfuck language
don’t provide an opportunity to call some sort of re-usable function
abstraction, for example. Yet, when we write a Brainfuck interpreter or
compiler, we can use a programming language that supports functions. That
doesn’t change that Brainfuck programs themselves can’t define or call
functions.</p>
<p>And in fact, we often want to implement a language in another language that has
more abilities than the language we’re implementing. For example, while it’s
possible to implement Brainfuck in Brainfuck, it’s much easier to do so in a
language that lets you define and call functions. Another classic example is
interacting with hardware. Brainfuck has the <code>.</code> instruction that allows you to
produce output. But in order to actually produce output to say, a terminal, you
interact with your platform’s API to do so. But inside of that API, eventually
you’ll hit a layer where there are no abstractions: you need to talk to hardware
directly. Doing so is not statically able to be guaranteed to be memory safe,
because the hardware/software interface often boils down to “put some
information at this arbitrary memory location and the hardware will take it from
there.” Our Brainfuck program is operating in an environment just like any
other; at the end of the day it has to coordinate with the underlying operating
system or hardware at some point. But just because our implementation must do
so, doesn’t mean that our Brainfuck programs do so directly. The semantics of
programs written in Brainfuck aren’t changed by the fact that the implementation
can (and must) do things that are outside of those semantics.</p>
<p>Let’s take Ruby as a more realistic example than Brainfuck. Ruby does not let us modify
arbitrary memory addresses from within Ruby itself. This means that pure Ruby
programs should not be able to produce a segmentation fault. But in the real
world, Ruby programs can segfault. This is possible if there’s a bug in the Ruby
interpreter’s code. Sure, it’s our Ruby code that’s making the call to a
function that results in a segfault, but the real fault lies with the code
outside of Ruby’s purview.</p>
<p>That this can happen doesn’t mean that Ruby’s guarantees around memory
manipulation is somehow useless, or suffers segmentation faults at the same rate
as programs in a language that allow you to manipulate arbitrary memory as a
matter of course. But it does also mean that we don’t need to look at our entire
program to figure out where the problem comes from: it instead comes from our
non-Ruby code. Ruby’s guarantees have helped us eliminate a lot of suspects when
figuring out whodunit.</p>
<h3 id="extending-languages-and-unsafety">Extending languages and unsafety</h3>
<p>The second property is that certain implementations may extend the language in
arbitrary ways. For example, I could write a Brainfuck interpreter and say that
I support a ninth instruction, <code>@</code>, that terminates the program when invoked.
This would be a non-portable extension, and Brainfuck programs written in my
variant wouldn’t work in other interpreters, but sometimes, this technique is
useful.</p>
<p>Many languages find that these sorts of extensions are useful, and so offer a
feature called a “foreign function interface” that allows for your program to
invoke code in a different language. This provides the ability to do things that
are outside of the domain of the language itself, which can be very useful.
Brainfuck does not offer an FFI, but if it did, you could imagine that <code>@</code> could
be implemented in terms of it, making programs that use <code>@</code> portable again, as
long as the extended functionality was included somehow, often as a library of
some kind.</p>
<p>Just like our implementation has the ability to do things outside of our
languages’ semantics, FFI and similar extension mechanisms also give us the
ability to do arbitrary things. I can write an extension for Ruby that writes to
arbitrary memory. And I can cause a segfault. But we’re in the same place that
we were with our implementation issues; we know that if we get a segfault, the
blame lies not with our Ruby code, but instead, with either the implementation
or our FFI.</p>
<h3 id="so-how-is-this-memory-safe">So how is this memory safe?</h3>
<p>It may seem contradictory that we can call a language “memory safe” if
real-world programs have the ability to cause memory problems. But the thing is,
it isn’t <em>really</em> programs in that language that caused the issue: it was FFI,
which is in a different language entirely, or it was implementation issues, and
implementations must do memory unsafe things, thanks to a need to interact with
the OS or hardware. And so the definition of “memory safe language” is commonly
understood to refer to languages and their implementations in the absence of
either implementation bugs or FFI bugs. In practice, these bugs occur at such a
low rate compared to languages that are clearly not memory safe that this
practical definition serves a good purpose, even if you may feel a bit
uncomfortable with these “exceptions.”</p>
<p>But there’s actually a deeper reason why these exceptions are acceptable, and
that’s due to how we understand properties of programs and programming languages
in the first place. That is, this isn’t just a practical “well these exceptions
seem fine” sort of thing, they’re actually okay on a deeper level.</p>
<h2 id="programs-properties-and-proofs">Programs, properties, and proofs</h2>
<p>So how do we know anything about how programs and programming languages work at
all?</p>
<blockquote>
<p>Computer science is no more about computers than astronomy is about
telescopes.</p>
<ul>
<li>Edsger Wybe Dijkstra</li>
</ul>
</blockquote>
<p>A fun thing about computer science is that it’s closely related to other
disciplines, including math! And so there’s a long history of using math
techniques to understand computers and their programs.</p>
<p>One technique of building knowledge is the idea of proofs. If you get a
degree in computer science, you’ll engage with proofs quite a lot. I even
took a class on logic in the philosophy department as part of my degree.</p>
<p>I don’t intend to give you a full introduction to the idea of proofs in this
blog post, but there’s some high-level concepts that are useful to make sure
we’re in the same page about before we go forward.</p>
<h3 id="aristotelian-syllogisms">Aristotelian syllogisms</h3>
<p>Here is a very classic example of a form of reasoning called a “syllogism”,
given by Aristotle in 350 BCE:</p>
<ol>
<li>All men are mortal.</li>
<li>Socrates is a man.</li>
<li>Therefore, Socrates is mortal.</li>
</ol>
<p>These first two lines are called “propositions,” and the third is a conclusion.
We can base our conclusion on a logical relationship between the information given
to us by the propositions.</p>
<p>But how do we know propositions are true? Are all men mortal? What’s important
here is that we, for the purposes of our proof, assume that propositions are
true. And we do this because, on some level, we cannot know everything. And so
to begin, we have to start somewhere, and make some assumptions about what we
know. It’s true that later, we may discover a fact that disproves our
proposition, and now our proof no longer works. But that’s just the way that
the world works. It doesn’t prevent these sorts of proofs from being useful to
help us gain knowledge about the world and how it works, as best as we can tell
at the current time.</p>
<p>So on some level, this is also why Ruby is a memory safe language even though a
C extension can segfault: there’s always some kind of things that we have to
assume are true. Memory safe languages are ones where the amount of code we have
to assume is memory safe, rather than is guaranteed to be by the semantics, is
small, and preferably indicated in the code itself. Put another way, the amount
of code we need to trust is memory safe is large in a memory unsafe language,
and small in a memory safe language, rather than zero.</p>
<h3 id="logic-and-programs">Logic and programs</h3>
<p>As time went on, so did our understanding of logic. And, of course, we even have
competing logics! And then this gets fun, because terms can mean something
slightly different. For example, in more recent logics, we’d call something like
“All men are mortal” to be an <em>axiom</em>, rather than a proposition. Same idea:
it’s something that we accept without proof.</p>
<p>As computers appeared, people sought to apply the rules for mathematical logic
onto them. We even call circuits that carry out classic logical operations
“logic gates.” Number theory and logic were foundational to making computers
work. And so, once high level languages appeared on the scene, there was
interest in applying these mathematical tools to understanding programs as well.
This discipline is called “formal verification,” and the general idea is to
describe various properties we wish a system to have, and then use formal
methods from mathematics to demonstrate that this is true.</p>
<p>This area of study is very deep, and I don’t plan to cover the vast majority of
it here. However, I do want to pursue one particular thread in this area.</p>
<h3 id="hoare-logic">Hoare Logic</h3>
<p><a href="https://dl.acm.org/doi/10.1145/363235.363259">“Hoare Logic”</a> is, well:</p>
<blockquote>
<p>In this paper an attempt is made to explore the logical foundations of
computer programming by use of techniques which were first applied in the
study of geometry and have later been extended to other branches of
mathematics. This involves the elucidation of sets of axioms and rules of
inference which can be used in proofs of the properties of computer programs.
Examples are given of such axioms and rules, and a formal proof of a simple
theorem is displayed. Finally, it is argued that important advantage, both
theoretical and practical, may follow from a pursuance of these topics.</p>
</blockquote>
<p>Incidentally, C. A. R. Hoare, the author of this paper, and Graydon Hoare, the
creator of Rust, are unrelated.</p>
<p>How does Hoare logic work? Yet again, not going to cover all of it, but the
general idea is this: In order to figure out if a program does what it is
supposed to do, we need to be able to reason about the state of the program
after execution happens. And so we need to be able to describe the state before,
and its relationship to the state after. And so you get this notation:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>P { Q } R</span></span></code></pre>
<p>P is a precondition, Q is a program, and R is the result of the program
executing with those preconditions.</p>
<p>But most programs have more than one statement. So how could we model this?
Hoare gives us the Rule of Composition:</p>
<p>If <code>P { Q1 } R1</code> and <code>R1 { Q2 } R</code> then <code>P { Q1; Q2 } R</code>.</p>
<p>This allows us to build up a program by proving each statement in turn.</p>
<p>Hoare logic is very neat, and I’ve only scratched the surface here. People did a
lot of work to extend Hoare logic to include more and more aspects of programs.
But then, something else happened.</p>
<h3 id="separation-logic">Separation Logic</h3>
<p>In 2002, <a href="https://www.cs.cmu.edu/~jcr/seplogic.pdf">Separation Logic: A Logic for Shared Mutable Data Structures</a>
was published.</p>
<blockquote>
<p>In joint work with Peter O’Hearn and others, based on early ideas of Burstall,
we have developed an extension of Hoare logic that permits reasoning about
low-level imperative programs that use shared mutable data structure.</p>
</blockquote>
<p>Hmm, shared mutable data structures? Where have I heard that before…</p>
<p>Let’s see what they have to say:</p>
<blockquote>
<p>The problem faced by these approaches is that the correctness of a program
that mutates data structures usually depends upon complex restrictions on the
sharing in these structures.</p>
</blockquote>
<p>For sure. Well, what are we to do about this?</p>
<blockquote>
<p>The key to avoiding this difficulty is to introduce a novel
logical operation <code>P * Q</code>, called separating conjunction (or
sometimes independent or spatial conjunction), that asserts
that P and Q hold for disjoint portions of the addressable
storage.</p>
</blockquote>
<p>What disjoint portions of addressable storage might we care about?</p>
<blockquote>
<p>Our intent is to capture the low-level character of machine language. One can
think of the store as describing the contents of registers, and the heap as
describing the contents of an addressable memory.</p>
</blockquote>
<p>Pretty useful!</p>
<p>Before we talk a bit about how separation logic works, consider this paragraph
on why it’s named as such:</p>
<blockquote>
<p>Since these logics are based on the idea that the structure of an assertion
can describe the separation of storage into disjoint components,we have come
to use the term separation logics, both for the extension of predicate
calculus with the separation operators and the resulting extension of Hoare
logic. A more precise name might be storage separation logics, since it is
becoming apparent that the underlying idea can be generalized to describe the
separation of other kinds of resources.</p>
</blockquote>
<p>The plot thickens.</p>
<p>Anyway, in Separation Logic, we use slightly different notation than Hoare Logic:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>{ P } C { Q }</span></span></code></pre>
<p>This says that we start with the precondition <code>P</code>, and if the program <code>C</code>
executes, it will not have undefined behavior, and if it terminates, <code>Q</code> will
hold.</p>
<p>Furthermore, there is the “frame rule”, which I am going to butcher the notation
for because I haven’t bothered to install something to render math correctly
just for this post:</p>
<p>If <code>{ p } c { q }</code></p>
<p>then <code>{ p * r } c { q * r }</code></p>
<p>where no free variable in <code>r</code> is modified by <code>c</code>.</p>
<p>Why is it interesting to add something to both sides of an equation, in a sense?
Well what this gives us the ability to do is add any predicates about parts of
the program that <code>c</code> doesn’t modify or mutate. You might think of <code>&mut T</code> or
even just ownership in general: we can reason about just these individual parts
of a program, separately from the rest of it. In other words, we have some
foundational ideas for ownership and even bits of borrowing, and while this original
paper doesn’t involve concurrency, eventually Concurrent Separation Logic would become
a thing as well.</p>
<p>I think the paper explains why the frame rule matters better than I can:</p>
<blockquote>
<p>Every valid specification <code>{p} c {q}</code> is “tight” in the sense that every cell
in its footprint must either be allocated by <code>c</code> or asserted to be active by
<code>p</code>; “locality” is the opposite property that everything asserted to be active
belongs to the footprint. The role of the frame rule is to infer from a local
specification of a command the more global specification appropriate to the
larger footprint of an enclosing command.</p>
</blockquote>
<p>What this gives us is something called “local reasoning,” and local reasoning
is <em>awesome</em>. Before I talk about that, I want to leave you with one other
very interesting paragraph:</p>
<blockquote>
<p>Since our logic permits programs to use unrestricted address arithmetic,there
is little hope of constructing any general-purpose garbage collector. On the
other hand, the situation for the older logic, in which addresses are disjoint
from integers, is more hopeful. However, it is clear that this logic permits
one to make assertions, such as “The heap contains two elements” that might be
falsified by the execution of a garbage collector, even though, in any
realistic sense, such an execution is unobservable.</p>
</blockquote>
<p>Anyway. Let’s talk about local vs global analysis.</p>
<h2 id="global-vs-local-analysis">Global vs local analysis</h2>
<p>Proving things about programs isn’t easy. But one thing that can make it even
harder is that, for many properties of many programs you’d want to analyze, you
need to do a <em>global analysis</em>. As an example, let’s use Ruby. Ruby is an
incredibly dynamic programming language, which makes it fairly resistant to
static analysis.</p>
<h3 id="global-analysis">Global analysis</h3>
<p>Here is a Ruby program. Do you know if this program executes successfully?</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#F97583">class</span><span style="color:#B392F0"> Foo</span></span>
<span class="line"><span style="color:#F97583"> def</span><span style="color:#B392F0"> self.bar</span></span>
<span class="line"><span style="color:#9ECBFF"> "baz"</span></span>
<span class="line"><span style="color:#F97583"> end</span></span>
<span class="line"><span style="color:#F97583">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF">p</span><span style="color:#79B8FF"> Foo</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">bar</span></span></code></pre>
<p>Yeah, it prints <code>baz</code>. But what about this Ruby program?</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#F97583">class</span><span style="color:#B392F0"> Foo</span></span>
<span class="line"><span style="color:#F97583"> def</span><span style="color:#B392F0"> self.bar</span></span>
<span class="line"><span style="color:#9ECBFF"> "baz"</span></span>
<span class="line"><span style="color:#F97583"> end</span></span>
<span class="line"><span style="color:#F97583">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">require</span><span style="color:#9ECBFF"> "foo_ext.rb"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF">p</span><span style="color:#79B8FF"> Foo</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">bar</span></span></code></pre>
<p>We can’t know. <code>foo_ext.rb</code> may contain no relevant code, but it also might
include something like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#79B8FF">Foo</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">class_eval</span><span style="color:#E1E4E8"> { </span><span style="color:#F97583">undef</span><span style="color:#79B8FF"> :bar</span><span style="color:#E1E4E8"> }</span></span></code></pre>
<p>In which case, when we try and call <code>Foo.bar</code>, it no longer exists:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span><anonymous>': eval:13:in `block in <main>': undefined method `bar' for class `Foo' (NameError)</span></span>
<span class="line"><span>eval:13:in `class_eval'</span></span>
<span class="line"><span>eval:13:in `<main>'</span></span>
<span class="line"><span>-e:in `eval'</span></span></code></pre>
<p>Ouch. So in this case, we’re going to need the whole code of our program in
order to be able to figure out what’s going on here.</p>
<p>Incidentally, <a href="https://sorbet.org/">Sorbet</a> is a very cool project to add type checking to
Ruby. They do require access to the entire Ruby source code in order to do their
analysis. But they also made some decisions to help make it more tractable; if
you try Sorbet out on the web, the type checker is <em>fast</em>. What happens when you
try the above code with Sorbet?</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>editor.rb:5: Unsupported method: undef https://srb.help/3008</span></span>
<span class="line"><span> 5 |Foo.class_eval { undef :bar }</span></span>
<span class="line"><span> ^^^^^^^^^^</span></span></code></pre>
<p>This is a very fair tradeoff! It’s very common with various forms of analysis to
choose certain restrictions in order to make what they want to do tractable. Ruby
has so much evil in it, just not supporting some of the more obscure things is
completely fair, in my opinion.</p>
<h3 id="local-analysis">Local analysis</h3>
<p>The opposite of global analysis is, well, local analysis. Let’s consider a Rust
equivalent of our Ruby:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> bar</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">-></span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0"> String</span><span style="color:#F97583">::</span><span style="color:#B392F0">from</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"baz"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0"> dbg!</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">Foo</span><span style="color:#F97583">::</span><span style="color:#B392F0">bar</span><span style="color:#E1E4E8">());</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Can we know if this program works? Sure, everything is here. Now, if we tried the
same trick as the Ruby code, we know that it would work, because Rust doesn’t have
the ability to remove the definition of <code>bar</code> like Ruby does. So let’s try something
else:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> bar</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">-></span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#6A737D"> // body hidden</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0"> dbg!</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">Foo</span><span style="color:#F97583">::</span><span style="color:#B392F0">bar</span><span style="color:#E1E4E8">());</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Can we know if <code>main</code> is properly typed here? We can, even though we know
nothing about the body. That’s because <code>dbg!</code> takes any value that implements
the <code>Debug</code> trait, and we know that <code>Foo::bar()</code> returns <code>String</code>, which
implements <code>Debug</code>. Typechecking <code>main</code> is local to main’s implementation, we
don’t need to look into the bodies of any function it calls to determine if it’s
well-typed. We only need to know their signatures. If we didn’t require <code>bar</code> to
have a type signature, we’d have to peek into its body to figure out what it
returns, and so on for any function that <code>bar</code> calls in its body. Instead of
this arbitrarily deep process, we can just look at the signature and be done
with it.</p>
<h2 id="composability-of-proofs">Composability of proofs</h2>
<p>So why is local analysis so helpful? The first reason is either speed or
scalability, depending on how you want to look at it. If you are running global
analysis checks, they will get more expensive the larger your codebase, since
they will require checking the entire thing to work. Whereas local analysis only
requires a local context, it doesn’t get any more expensive when you add more
code to your project, only when you change that local context. So when you’re
trying to scale checks up to larger projects, local analysis is crucial.</p>
<p>But I also like to personally think about it as a sort of abstraction. That is,
global analysis is a leaky abstraction: changes in one part of the codebase can
cascade into other parts. Remember this line about the frame rule?</p>
<blockquote>
<p>The role of the frame rule is to infer from a local specification of a command
the more global specification appropriate to the larger footprint of an
enclosing command.</p>
</blockquote>
<p>If we have local reasoning, we can be sure that changes locally don’t break out
of the boundaries of those changes. As long as the types of our function don’t
change, we can mess with the body as much as we want, and we know that the rest
of the analysis of the program is still intact. This is really useful, in the
same way that abstractions are useful when building programs more generally.</p>
<h2 id="rustbelt">Rustbelt</h2>
<p>Okay, we have taken a real deep dive here. What’s this all have to do with
unsafe Rust?</p>
<p>Well, seven years ago, <a href="https://plv.mpi-sws.org/rustbelt/popl18/paper.pdf">RustBelt: Securing the Foundations of the Rust
Programming Language</a> by Ralf Jung, Jaques-Henri Jourdan, Robbert
Krebbers, and Derek Dreyer, was published:</p>
<blockquote>
<p>In this paper, we give the first formal (and machine-checked) safety proof for
a language representing a realistic subset of Rust.</p>
</blockquote>
<p>But it’s more exciting than that:</p>
<blockquote>
<p>Our proof is extensible in the sense that, for each new Rust library that uses
unsafe features, we can say what verification condition it must satisfy in
order for it to be deemed a safe extension to the language.</p>
</blockquote>
<p>Very good!</p>
<p>The paper begins by talking about why verifying Rust is such a challenge:</p>
<blockquote>
<p>Consequently, to overcome this restriction, the implementations of Rust’s
standard libraries make widespread use of unsafe operations, such as “raw
pointer” manipulations for which aliasing is not tracked. The developers of
these libraries claim that their uses of unsafe code have been properly
“encapsulated”, meaning that if programmers make use of the APIs exported by
these libraries but otherwise avoid the use of unsafe operations themselves,
then their programs should never exhibit any unsafe/undefined behaviors. In
effect, these libraries extend the expressive power of Rust’s type system by
loosening its ownership discipline on aliased mutable state in a modular,
controlled fashion</p>
</blockquote>
<p>This is basically the question asked at the start of this post: can you honestly
say that a function is safe if it contains <code>unsafe</code> code as part of its
implementation?</p>
<p>They go on to describe the first challenge for the project: choosing the correct
logic to model Rust in (some information cut for clarity):</p>
<blockquote>
<p>Iris is a language-generic framework for higher-order concurrent separation
logic which in the past year has been equipped with tactical support for
conducting machine-checked proofs of programs in Coq. By virtue of being a
separation logic, Iris comes with built-in support for reasoning modularly
about ownership. Moreover, the main selling point of Iris is its support for
deriving custom program logics for different domains using only a small set of
primitive mechanisms. In the case of RustBelt, we used Iris to derive a novel
lifetime logic, whose primary feature is a notion of borrow propositions that
mirrors the “borrowing” mechanism for tracking aliasing in Rust.</p>
</blockquote>
<p>Separation logic!</p>
<p>So how does this work? There are three parts to the proof. I’m going to
summarize them because I’m already quoting extensively, please go read the paper
if you want the exact details, it is well written.</p>
<ol>
<li>Verify that the typing rules are sound when interpreted semantically.</li>
<li>Verify that if a program is well typed semantically, it does not exhibit
unsafe behavior.</li>
<li>For any library that uses unsafe, check that it satisfies the semantics that
its interface requires.</li>
</ol>
<p>If all three of these things are true, then the program is safe to execute. This
is also exciting because, thanks to 3, when someone writes new unsafe code, they
can get RustBelt to let them know which properties they need to satisfy to make
sure that their unsafe code doesn’t cause problems.</p>
<h3 id="weaknesses">Weaknesses</h3>
<p>Of course, there is more to do following up on RustBelt:</p>
<blockquote>
<p>We do not model (1) more relaxed forms of atomic accesses, which Rust uses for
efficiency in libraries like Arc; (2) Rust’s trait objects (comparable to
interfaces in Java), which can pose safety issues due to their interactions
with lifetimes; or (3) stack unwinding when a panic occurs, which causes
issues similar to exception safety in C++.</p>
</blockquote>
<p>However, the results were good enough to find some soundness bugs in the
standard library, and if I remember correctly, widen one of the APIs in a sound
manner as well. Another interesting thing about RustBelt is that it was
published before Rust gained Non-lexical lifetimes, however, it modeled them,
since folks knew they were coming down the pipeline.</p>
<h2 id="the-theoretical-vs-the-empirical">The theoretical vs the empirical</h2>
<p>So, we have some amount of actual proof that unsafe code in Rust doesn’t
undermine Rust’s guarantees: what it does is allow us to extend Rust’s
semantics, just like FFI would allow us to extend a program written in
a language that supports it. As long as our unsafe code is semantically
correct, then we’re all good.</p>
<p>… but what if it’s not semantically correct?</p>
<h3 id="when-things-go-wrong">When things go wrong</h3>
<p>At some point maybe I’ll write a post about the specifics here, but this post is
already incredibly long. I’ll cut to the chase: you can get undefined behavior,
which means anything can happen. You don’t have a real Rust program. It’s bad.</p>
<p>But at least it’s scoped to some degree. However, many people get this scope a
bit wrong. They’ll say something like:</p>
<blockquote>
<p>You only need to verify the unsafe blocks.</p>
</blockquote>
<p>This is true, but also a bit misleading. Before we get into the unsafe stuff, I
want you to consider an example. Is this Rust code okay?</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> bar</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">impl</span><span style="color:#B392F0"> Foo</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> set_bar</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">, bar</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">bar </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> bar;</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>No unsafe shenanigans here. This code is perfectly safe, if a bit useless.</p>
<p>Let’s talk about unsafe. The canonical example of unsafe code being affected
outside of unsafe itself is the implementation of <code>Vec<T></code>. Vecs look something
like this (the real code is different for reasons that don’t really matter in
this context):</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#E1E4E8"> ptr</span><span style="color:#F97583">:</span><span style="color:#F97583"> *mut</span><span style="color:#B392F0"> T</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> len</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> cap</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>The pointer is to a bunch of <code>T</code>s in a row, the length is the current number of <code>T</code>s
that are valid, and the capacity is the total number of <code>T</code>s. The length and the
capacity are different so that memory allocation is amortized; the capacity is
always greater than or equal to the length.</p>
<p>That property is very important! If the length is greater than the capacity,
when we try and index into the Vec, we’d be accessing random memory.</p>
<p>So now, this function, which is the same as <code>Foo::set_bar</code>, is no longer okay:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">impl</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> </span><span style="color:#B392F0">Vec</span><span style="color:#E1E4E8"><</span><span style="color:#B392F0">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#F97583"> fn</span><span style="color:#B392F0"> set_len</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&mut</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">, len</span><span style="color:#F97583">:</span><span style="color:#B392F0"> usize</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#79B8FF"> self</span><span style="color:#F97583">.</span><span style="color:#E1E4E8">len </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> len;</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This is because the unsafe code inside of other methods of <code>Vec<T></code> need to be
able to rely on the fact that <code>len <= capacity</code>. And so you’ll find that
<code>Vec<T>::set_len</code> in Rust is marked as unsafe, even though it doesn’t contain
unsafe code.</p>
<p>And this is why the module being the privacy boundary matters: the only way to
set len directly in safe Rust code is code within the same privacy boundary as
the <code>Vec<T></code> itself. And so, that’s the same module, or its children.</p>
<p>This is still ultimately better than any line of code in the whole codebase, but
it’s not quite as small as you may think at first.</p>
<h3 id="how-can-we-check-that-things-havent-gone-wrong">How can we check that things haven’t gone wrong?</h3>
<p>Okay, so while RustBelt could give you some idea if your code is correct, I doubt
you’re about to jump into Coq and write some proofs. What can you do? Well,
Rust provides a tool, <code>miri</code>, that can interpret your Rust code and let you know
if you’ve violated some unsafe rules. But it’s not complete, that is, miri can
tell you if your code is wrong, but it cannot tell you if your code is right. It’s
still quite useful.</p>
<p>To get miri, you can install it with Rust nightly:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>$ rustup +nightly component add miri</span></span></code></pre>
<p>And then run your test suite under miri with</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>$ cargo +nightly miri test</span></span></code></pre>
<p>For more details, consult <a href="https://github.com/rust-lang/miri">miri’s documentation</a>.</p>
<h3 id="do-things-go-wrong">Do things go wrong?</h3>
<p>Do people write good unsafe code, or bad?</p>
<p>Well, the obvious answer to the above question is “yes.” Anyone who says all
unsafe code in Rust is sound is obviously lying. But the absolute isn’t what’s
interesting here, it’s the shade of grey. Is unsafe pervasive? Does Rust have
memory related bugs at the same rate as other MSLs, or closer to memory-unsafe
languages?</p>
<p>It’s still sort of early days here, but we do have some preliminary results that
are helpful. In 2022, <a href="https://security.googleblog.com/2022/12/memory-safe-languages-in-android-13.html">Google reported</a> that:</p>
<blockquote>
<p>To date, there have been zero memory safety vulnerabilities discovered in
Android’s Rust code.</p>
</blockquote>
<p>I am very interested to see a more up to date report about how that’s changed,
but as they say:</p>
<blockquote>
<p>We don’t expect that number to stay zero forever, but given the volume of new
Rust code across two Android releases, and the security-sensitive components
where it’s being used, it’s a significant result. It demonstrates that Rust is
fulfilling its intended purpose of preventing Android’s most common source of
vulnerabilities. Historical vulnerability density is greater than 1/kLOC (1
vulnerability per thousand lines of code) in many of Android’s C/C++ components
(e.g. media, Bluetooth, NFC, etc). Based on this historical vulnerability
density, it’s likely that using Rust has already prevented hundreds of
vulnerabilities from reaching production.</p>
</blockquote>
<p>We’ll see if these results replicate themselves, in the future, and also outside
of Google. But it at least looks like in practice, most unsafe code has not led
to problems. At least so far!</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lkqjlfxypk2d" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Does unsafe undermine <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> 's guarantees? <a target="_blank" href="https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/does...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Does unsafe undermine Rust's guarantees?</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-03-19T15:43:41.710Z" class="date s-hik11q">March 19, 2025 at 3:43 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="138 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>138</span></span> <span class="stat s-hik11q" title="41 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>41</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lkqjlfxypk2d" target="_blank" class="permalink s-hik11q"><span>Read 17 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>Choosing Languages</title><link>https://steveklabnik.com/writing/choosing-languages/</link><guid isPermaLink="true">https://steveklabnik.com/writing/choosing-languages/</guid><description>I tried to write this post yesterday, but I didn’t like my approach there. Last night, I had a conversation with one of my best friends about it, and he encouraged me to write this, but in a different way. I think this way is better, so thanks, dude. The other day, Microsoft…</description><pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I tried to write this post yesterday, but I didn’t like my approach there. Last
night, I had a conversation with one of my best friends about it, and he
encouraged me to write this, but in a different way. I think this way is better,
so thanks, dude.</p>
<hr/>
<p>The other day, Microsoft announced <a href="https://devblogs.microsoft.com/typescript/typescript-native-port/">that they are re-writing the TypeScript
compiler in Go</a>. A lot of people had a lot of feelings about this. I did
too, but in a different way than many other people.</p>
<p>The biggest questions people had were “Why not C#?” and “Why not Rust?”. To
be clear, I do think that asking why someone chooses a programming language
can be valuable; as professionals, we need to make these sorts of decisions
on a regular basis, and seeing how others make those decisions can be useful,
to get an idea of how other people think about these things.</p>
<p>But it can also… I dunno. Sometimes, I think people have little imagination
when it comes to choosing a particular technology stack.</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lk6p4clry22p" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Here’s my opinion on the new typescript compiler:
You should write programs in the language you want to.</p> <time datetime="2025-03-12T13:34:42.008Z" class="date s-hik11q">March 12, 2025 at 1:34 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="385 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>385</span></span> <span class="stat s-hik11q" title="43 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>43</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lk6p4clry22p" target="_blank" class="permalink s-hik11q"><span>Read 22 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>It is true that sometimes, there are hard requirements that means that choosing
a particular technology is inappropriate. But I’ve found that often, these
sorts of requirements are often more <em>contextually</em> requirements than ones that
would be the case in the abstract. For example, <a href="https://en.wikipedia.org/wiki/Lisp_machine">you can write an OS in
Lisp</a> if you want. But in practice, you’re probably not working with
this hardware, or willing to port the environment to bare metal.</p>
<p>But it’s also the case that you often don’t know what others’ contextual
situation actually is. I would have never guessed the main reason that Microsoft
chose Go for, which is roughly “we are porting the existing codebase, rather
than re-writing it, and our existing code looks kinda like Go, making it easy to
port.” That’s a very good reason! I hadn’t really thought about the differences
between porting and re-writing in this light before, and it makes perfect sense
to me.</p>
<p>At the same time,</p>
<bluesky-post src="at://did:plc:4eukmtg5kmyjmp6qw3xkpite/app.bsky.feed.post/3lk6t4a4xa22w" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:4eukmtg5kmyjmp6qw3xkpite/bafkreie5tfrdul6a3oyl34g6ughguakxf6gxc7ua6c2n4ysuggx4cak6ya" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Jake Bailey</span></bdi> <span class="handle s-12mzi62">@jakebailey.dev</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite/post/3lk6t47wxt22w" title="March 12, 2025 at 2:46 PM" class="date s-12mzi62"><time datetime="2025-03-12T14:46:14.196Z">Mar 12, 2025</time></a></div> <p class="context s-12mzi62">Replying to <a target="_blank" href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" dir="auto" class="s-12mzi62">Steve Klabnik</a></p> <p class="rich-text is-small s-1lecfnd">Funnily I'm pretty sure I'm the only team member who could actually claim their favorite language is Go, but I tried so hard to prove rust out early on. It sure wasn't my decision either, Go just kept working the best as the hacking continued</p> </div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:4eukmtg5kmyjmp6qw3xkpite/bafkreie5tfrdul6a3oyl34g6ughguakxf6gxc7ua6c2n4ysuggx4cak6ya" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Jake Bailey</span></bdi> <span class="handle s-hik11q">@jakebailey.dev</span></a> </div> <p class="rich-text is-large s-1lecfnd">I think it's going well, though, besides the terrifying infohazard that is the "Why Go?" GitHub discussion thread, which contains some of the weirdest and most frustrating takes I've ever seen</p> <time datetime="2025-03-12T14:46:14.197Z" class="date s-hik11q">March 12, 2025 at 2:46 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="37 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>37</span></span> <span class="stat s-hik11q" title="1 repost"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>1</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite/post/3lk6t4a4xa22w" target="_blank" class="permalink s-hik11q"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>people are going <em>way</em> overboard here.</p>
<bluesky-post src="at://did:plc:x2p6i7jmdhk4xj2ej3rflp7a/app.bsky.feed.post/3lk6was5n5227" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="post s-12mzi62"><svg class="logo s-12mzi62" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg> <div class="aside s-12mzi62"><a target="_blank" href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" class="avatar-wrapper s-12mzi62"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:4eukmtg5kmyjmp6qw3xkpite/bafkreie5tfrdul6a3oyl34g6ughguakxf6gxc7ua6c2n4ysuggx4cak6ya" alt="" class="avatar s-12mzi62"/></a> <div class="line s-12mzi62"></div></div> <div class="main s-12mzi62"><div class="meta s-12mzi62"><a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" target="_blank" class="name-wrapper s-12mzi62"><bdi class="display-name-wrapper s-12mzi62"><span class="display-name s-12mzi62">Jake Bailey</span></bdi> <span class="handle s-12mzi62">@jakebailey.dev</span></a> <span aria-hidden="true" class="dot s-12mzi62">·</span> <a target="_blank" href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite/post/3lk6t4a4xa22w" title="March 12, 2025 at 2:46 PM" class="date s-12mzi62"><time datetime="2025-03-12T14:46:14.197Z">Mar 12, 2025</time></a></div> <p class="context s-12mzi62">Replying to <a target="_blank" href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite" dir="auto" class="s-12mzi62">Jake Bailey</a></p> <p class="rich-text is-small s-1lecfnd">I think it's going well, though, besides the terrifying infohazard that is the "Why Go?" GitHub discussion thread, which contains some of the weirdest and most frustrating takes I've ever seen</p> </div></div><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:x2p6i7jmdhk4xj2ej3rflp7a" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:x2p6i7jmdhk4xj2ej3rflp7a/bafkreifxfhmm3eggyodwvoqxsh7iawqkpfhuh6opfac3e27jfzlnx7dikm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:x2p6i7jmdhk4xj2ej3rflp7a" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Michael Pratt</span></bdi> <span class="handle s-hik11q">@prattmic.com</span></a> </div> <p class="rich-text is-large s-1lecfnd">I peeked briefly yesterday (and quickly ran away). Just took another look. 370 comments in 24 hours?? Wow. The proposal to add generics to Go (mildly contentious, to put it lightly) only got ~450 comments over a few months.</p> <time datetime="2025-03-12T15:42:28.732Z" class="date s-hik11q">March 12, 2025 at 3:42 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="11 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>11</span></span> <span class="stat s-hik11q" title="0 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>0</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:x2p6i7jmdhk4xj2ej3rflp7a/post/3lk6was5n5227" target="_blank" class="permalink s-hik11q"><span>Read 1 reply on Bluesky</span></a></div></div></div> </bluesky-post>
<p>There’s some sort of balance here to be struck. But at the same time, I hate
“choose the right tool for the job” as a suggestion. Other than art projects,
has anyone ever deliberately chosen the wrong tool? Often, something that
seems like the wrong tool is chosen simply because the contextual requirements
weren’t obvious to you, the interlocutor.</p>
<p>I think there’s two other reasons why this situation is making me feel
complicated things. The first is RIIR, and the second is a situation from a long
time ago that changed my life.</p>
<p>Many of you have probably heard of the “re-write it in Rust” meme. The idea is
that there’s this plague of programmers who are always suggesting that every
single thing needs to be re-written in Rust. While I am sure this happens from
time to time, I have yet to see real evidence that it is actually widespread. I
almost even wrote a blog post about this, trying to actually quantify the effect
here. But on some level, perception is reality, and if people believe it’s true,
it’s real on some level, even if that’s not actually the case. Regardless of the
truth of the number of people who suggest this, on every thread about Rust,
people end up complaining about this effect, and so it has an effect on the
overall discourse regardless. And while I don’t believe that this effect is real
overall, it absolutely has happened in this specific situation. A bunch of
people have been very aggressively suggesting that this should have been in
Rust, and not Go, and I get where they’re coming from, but also: really? Go is
an absolutely fine choice here. It’s fine. Why do you all have to reinforce a
stereotype? It’s frustrating.</p>
<p>The second one is something that’s been on my mind for over a decade, but I’ve
only started talking about it in places other than conversations with close
friends. Long-time readers may remember <a href="/writing/node/">that one time where I was an
asshole</a>. I mean, I’ve been an asshole more than once in my life, but
this one was probably the time that affected me the most. The overall shape of
the situation is this:</p>
<ol>
<li>Someone wrote <code>grep</code>, but in Node.</li>
<li>I made fun of it on the internet, because why would someone write a CLI tool in Node?</li>
<li>The author found out that I did and felt bad about it.</li>
</ol>
<p>Looking back at my apology, I don’t think it’s necessarily the best one. It was
better than the first draft or two of what I wrote, but I’ve gotten better at
apologizing since then. Regardless, this incident changed the trajectory of my
life, and if you happen to read this, Heather, I am truly, unequivocally, sorry.</p>
<p>There was a couple of things that happened here. The first is just simply, in
2013, I did not understand that the things I said had meaning. I hate talking
about this because it makes me seem more important than I am, but it’s also
important to acknowledge. I saw myself at the time as just Steve, some random
guy. If I say something on the internet, it’s like I’m talking to a friend in
real life, my words are just random words and I’m human and whatever. It is what
it is.</p>
<p>But at that time in my life, that wasn’t actually the case. I was on the Rails
team, I was speaking at conferences, and people were reading my blog and tweets.
I was an “influencer,” for better or worse. But I hadn’t really internalized
that change in my life yet. And so I didn’t really understand that if I
criticized something, it was something thousands of people would see. To me, I’m
just Steve. This situation happened before we talked about “cancel culture” and
all of that kind of stuff, but when the mob came for me, I realized: they were
right, actually. I was being an asshole, and I should not have been. And I
resolved myself to not being an asshole like that ever again.</p>
<p>And to do that, I had to think long and hard about why I was like that. I love
programming languages! I love people writing programs that they find
interesting! I don’t think that it’s stupid to write a tool in a language that
I wouldn’t expect it to be written in! So why did I feel the reason to make fun
of this in that situation?</p>
<p>The answer? A pretty classic situation: the people I was hanging out with were
affecting me, and in ways that, when I examined it, I didn’t like very much.</p>
<p>You see, in the Rails world at the time, there was a huge <a href="https://blog.aurynn.com/2015/12/16-contempt-culture">contempt
culture</a> at the time, especially around JavaScript and Node. And, when
you’re around people who talk shit all the time, you find yourself talking shit
too. And once I realized that, well, I wanted to stop it. But there was a big
problem: my entire professional, and most of my social, life was built around
Ruby and Rails. So if I wanted to escape that culture… what should I do?</p>
<p>If you clicked on the link to my apology above, you probably didn’t make note of
the date: Jan 23 2013. Something had just happened to me, in December of 2012: I
had decided to check out this little programming language I had heard of called
Rust.</p>
<p>One of the things that struck me about the “Rust community” at the time, which
was like forty people in an IRC room, was how nice they were. When I had trouble
getting Hello World to compile, and I typed <code>/join #rust</code>, I fully expected an
RTFM and flames. But instead, I was welcomed with open arms. People were chill.
And it was really nice.</p>
<p>And so, a month and a day later, I had this realization about the direction my
life was heading, and how I wasn’t really happy about it. And I thought about
how much I enjoyed the Rust stuff so far… and combined with a few other
factors, maybe of which I’ll write about someday, this is when I decided that I
wanted to make Rust a thing, and dedicated myself to achieving that.</p>
<p>And part of that was being conscious about the culture, and how it developed,
and the ways that it would evolve over time. I didn’t want Rust to end up the
way that Rails had ended up. And that meant a few things, but first of all,
truly internalizing that I had to change. And understanding that I had to be
intentional with what I did and said. And what that led to, among other things,
was a “we talk about Rust on its own merits, not by trash talking other languages”
culture. (That’s also partially because of Nietzsche, but I’m already pretty far
afield of the topic for this post, maybe someday.)</p>
<p>Anyway, the Eternal September comes for us all. The “Rust Community,” if it ever
were a coherent concept, doesn’t really make sense any more. And it is by no
means perfect. I have a lot of criticism about how things turned out. But I do
think that it’s not a coincidence that “Rust makes you trans,” for example, is
a meme. I am deeply proud of what we all built.</p>
<p>So yeah, anyway: people choose programming languages for projects based on a
variety of reasons. And sure, there may be better or worse choices. But you
probably have a different context than someone else, and so when someone
makes a choice you don’t agree with, there’s no reason to be a jerk about it.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lkblqoytls22" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">Choosing Languages <a target="_blank" href="https://steveklabnik.com/writing/choosing-languages/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/choo...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/choosing-languages/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">Choosing Languages</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-03-13T17:12:30.356Z" class="date s-hik11q">March 13, 2025 at 5:12 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="304 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>304</span></span> <span class="stat s-hik11q" title="91 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>91</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lkblqoytls22" target="_blank" class="permalink s-hik11q"><span>Read 29 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>A Happy Day for Rust</title><link>https://steveklabnik.com/writing/a-happy-day-for-rust/</link><guid isPermaLink="true">https://steveklabnik.com/writing/a-happy-day-for-rust/</guid><description>A while back, I wrote A sad day for Rust about the Actix-web unsafe controversy. The short of it was, Actix-web was using some unsound unsafe code. When this was discovered, people responded pretty harshly, and the maintainer quit. Others picked up the work, but it still wasn’t…</description><pubDate>Wed, 05 Mar 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>A while back, I wrote <a href="/writing/a-sad-day-for-rust/">A sad day for Rust</a> about the Actix-web unsafe
controversy. The short of it was, Actix-web was using some unsound unsafe code.
When this was discovered, people responded pretty harshly, and the maintainer
quit. Others picked up the work, but it still wasn’t great:</p>
<blockquote>
<p>I’m not sure where we go from here, and I’m not sure what we could have done
to prevent this from happening. But I feel like this is a failure, and it’s set
Rust back a bit, and I’m just plain sad.</p>
</blockquote>
<p>While the Rust community surely isn’t perfect, today I’d like to share with you
a different story: one where there was some controversy, but it was handled in
the complete opposite way.</p>
<p><a href="https://rustup.rs/">Rustup</a> is the official installer and toolchain manager for Rust. It
has a much smaller team than Rust itself does, and releases on a much slower
schedule.</p>
<p>Back in August of last year, it was decided to <a href="https://github.com/rust-lang/rustup/pull/3985">change some behavior</a>.
The details aren’t super important, what matters is that it’s a breaking change
to a critical component of the ecosystem. I’m not here to say if this change is
good or bad; they had good motivations, there’s also good reasons to keep the
previous behavior, it’s one of those classic “you can’t make everyone happy”
sort of deals.</p>
<p>The team knew that they’d need to let people know that this had happened:</p>
<blockquote>
<p>Yes, you’ve understood it correctly, and this breakage is intentional: #3635 (comment)</p>
<p>Before the actual release, we’ll definitely clearly communicate this change with
the community, and we’ll use the beta phase to collect user feedback.</p>
</blockquote>
<p>And they did <a href="https://internals.rust-lang.org/t/seeking-beta-testers-for-rustup-v1-28-0/22060">make a post on internals</a> about the change, seeking
feedback. This also landed in a This Week in Rust, in my understanding.</p>
<p>The problem is, not everyone reads internals, and not everyone reads This Week
in Rust. And so yesterday, some folks woke up to a broken CI, leading to <a href="https://github.com/rust-lang/rustup/issues/4211">this
bug report</a> among other upset posts elsewhere on the internet.</p>
<p>I have been on the other side of this before. It’s frustrating when you try and
communicate something, and people don’t hear about it, and then they find out
later and get mad at you about it. I also remember the Actix debacle, and many
other controversies in the broader open source ecosystem.</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3ljiqodr7q22f" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">at one point i questioned my desire to help people get into open source
image unrelated</p> <div class="embeds s-azdpbr"><div class="image-embed is-bordered is-aligned s-t3k6fc"><div class="single-item tl tr bl br is-standalone s-t3k6fc" style="aspect-ratio: 1024/766"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreignn6nf4mlkc2yfpj3y4dwo5j3dyuod3gbcw4rolpcsca6ujbbo5y" alt="a tweet by @primawesome
My neighbor told me coyotes keep eating his outdoor cats so I asked how many cats he has and he said he just goes to the shelter and gets a new cat afterwards so I said it sounds like he's just feeding shelter cats to coyotes and then his daughter started crying" class="image s-t3k6fc"/> <div class="placeholder s-t3k6fc"></div></div></div></div> <time datetime="2025-03-03T20:04:06.711Z" class="date s-hik11q">March 3, 2025 at 8:04 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="1,156 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>1,156</span></span> <span class="stat s-hik11q" title="86 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>86</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3ljiqodr7q22f" target="_blank" class="permalink s-hik11q"><span>Read 13 replies on Bluesky</span></a></div></div></div> </bluesky-post>
<p>However, this time, things went differently. As you can see, the issue is very
respectful, and the comments are also generally respectful. The team responded
<a href="https://github.com/rust-lang/rustup/issues/4211#issuecomment-2695620307">graciously</a>, and decided to put in the work to <a href="https://blog.rust-lang.org/2025/03/04/Rustup-1.28.1.html">release a new
version</a> to restore the previous behavior.</p>
<p>I was kind of worried yesterday that this would end up being another Actix
situation. The Rust community has changed a lot over time, and while it’s still
pretty good overall, sometimes things can go poorly, just like with any random
group of thousands of people on the internet. But today, I’m grateful that
feedback was given constructively, it was taken with the spirit that it was
intended, and everyone should end up happy.</p>
<p>If only every controversy went this way, maybe I wouldn’t be wondering about
coyotes.</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3ljnctebzec25" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">A happy day for <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> <a target="_blank" href="https://steveklabnik.com/writing/a-happy-day-for-rust/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/a-ha...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/a-happy-day-for-rust/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"><img loading="lazy" src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreibb62uzqrzuu7nzx4c2s46anoc6fztalrgpm5qucnoofgdizmwtbu" alt="" class="thumbnail s-rtbqd8"/> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">A Happy Day for Rust</p> <p class="description s-rtbqd8">I’m not sure where we go from here, and I’m not sure what we could have done to prevent this from happening. But I feel like this is a failure, and it’s set Rust back a bit, and I’m just plain sad.</p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2025-03-05T15:39:41.341Z" class="date s-hik11q">March 5, 2025 at 3:39 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="185 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>185</span></span> <span class="stat s-hik11q" title="23 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>23</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3ljnctebzec25" target="_blank" class="permalink s-hik11q"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item><item><title>When should I use String vs &str?</title><link>https://steveklabnik.com/writing/when-should-i-use-string-vs-str/</link><guid isPermaLink="true">https://steveklabnik.com/writing/when-should-i-use-string-vs-str/</guid><description>Rust has two main string types: String and &str . Sometimes, people argue that these two types make Rust code difficult to write, because you have to think about which one you should be using in a given situation. My experience of writing Rust is that I don’t really think about…</description><pubDate>Wed, 16 Oct 2024 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Rust has two main string types: <code>String</code> and <code>&str</code>. Sometimes, people argue
that these two types make Rust code difficult to write, because you have to
think about which one you should be using in a given situation. My experience
of writing Rust is that I don’t really think about this very much, and this
post is about some rules of thumb that you can use to be like me.</p>
<h2 id="level-1-dont-think-about-it-at-all">Level 1: Don’t think about it at all</h2>
<p>The very first thing you can do is follow the simplest rule:</p>
<blockquote>
<p>Always use <code>String</code>, never use <code>&str</code>.</p>
</blockquote>
<p>That looks like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Person</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> name</span><span style="color:#F97583">:</span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> first_word</span><span style="color:#E1E4E8">(words</span><span style="color:#F97583">:</span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">split_whitespace</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">next</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"words should not be empty"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">to_string</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This style means you may need to add <code>.to_string()</code> or <code>.clone()</code>
for things to work sometimes:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> sentence </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> "Hello, world!"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(sentence</span><span style="color:#F97583">.</span><span style="color:#B392F0">to_string</span><span style="color:#E1E4E8">()));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> owned </span><span style="color:#F97583">=</span><span style="color:#B392F0"> String</span><span style="color:#F97583">::</span><span style="color:#B392F0">from</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"A string"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> // if we don't clone here, we can't use owned the second time</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(owned</span><span style="color:#F97583">.</span><span style="color:#B392F0">clone</span><span style="color:#E1E4E8">()));</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(owned));</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>But that’s okay, the compiler will let you know when you need to:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>error[E0382]: use of moved value: `owned`</span></span>
<span class="line"><span> --> src/main.rs:21:31</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>18 | let owned = String::from("A string");</span></span>
<span class="line"><span> | ----- move occurs because `owned` has type `String`, which does not implement the `Copy` trait</span></span>
<span class="line"><span>19 |</span></span>
<span class="line"><span>20 | println!("{}", first_word(owned));</span></span>
<span class="line"><span> | ----- value moved here</span></span>
<span class="line"><span>21 | println!("{}", first_word(owned));</span></span>
<span class="line"><span> | ^^^^^ value used here after move</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>note: consider changing this parameter type in function `first_word` to borrow instead if owning the value isn't necessary</span></span>
<span class="line"><span> --> src/main.rs:5:22</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>5 | fn first_word(words: String) -> String {</span></span>
<span class="line"><span> | ---------- ^^^^^^ this parameter takes ownership of the value</span></span>
<span class="line"><span> | |</span></span>
<span class="line"><span> | in this function</span></span>
<span class="line"><span>help: consider cloning the value if the performance cost is acceptable</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>20 | println!("{}", first_word(owned.clone()));</span></span>
<span class="line"><span> | ++++++++</span></span></code></pre>
<p>Hey, compiler, that’s a great idea. Let’s move on to level 2.</p>
<h2 id="level-2-prefer-str-for-function-parameters">Level 2: prefer <code>&str</code> for function parameters</h2>
<p>A rule that’s a little better is this one:</p>
<blockquote>
<p>Always use <code>String</code> in structs, and for functions, use <code>&str</code> for parameters
and <code>String</code> types for return values.</p>
</blockquote>
<p>This is what the compiler error for level 1 was suggesting we do instead of <code>.clone</code>.</p>
<p>That results in code that looks like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Person</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> name</span><span style="color:#F97583">:</span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> first_word</span><span style="color:#E1E4E8">(words</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">split_whitespace</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">next</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"words should not be empty"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">to_string</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> sentence </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> "Hello, world!"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(sentence));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> owned </span><span style="color:#F97583">=</span><span style="color:#B392F0"> String</span><span style="color:#F97583">::</span><span style="color:#B392F0">from</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"A string"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>We’re now doing much less copying. We do need to add a <code>&</code> on
to <code>String</code> values that we wish to pass into <code>first_word</code>, but
that’s not too bad, the compiler will help us when we forget:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>error[E0308]: mismatched types</span></span>
<span class="line"><span> --> src/main.rs:20:31</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>20 | println!("{}", first_word(owned));</span></span>
<span class="line"><span> | ---------- ^^^^^ expected `&str`, found `String`</span></span>
<span class="line"><span> | |</span></span>
<span class="line"><span> | arguments to this function are incorrect</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>note: function defined here</span></span>
<span class="line"><span> --> src/main.rs:5:4</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>5 | fn first_word(words: &str) -> String {</span></span>
<span class="line"><span> | ^^^^^^^^^^ -----------</span></span>
<span class="line"><span>help: consider borrowing here</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>20 | println!("{}", first_word(&owned));</span></span>
<span class="line"><span> | +</span></span></code></pre>
<p>Following this rule will get you through 95% of situations successfully. Yes,
that number was found via the very scientific process of “I made it up, but it
feels correct after writing Rust for the last twelve years.”</p>
<p>For 4% of that last 5%, we can go to level 3:</p>
<h2 id="level-3-return-str-sometimes">Level 3: return <code>&str</code> sometimes</h2>
<p>Here’s a slightly more advanced rule for certain circumstances:</p>
<blockquote>
<p>Always use <code>String</code> in structs, and for functions, use <code>&str</code> for parameters.
If the return type of your function is derived from an argument and isn’t
mutated by the body, return <code>&str</code>. If you run into any trouble here, return
<code>String</code> instead.</p>
</blockquote>
<p>That would look like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Person</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> name</span><span style="color:#F97583">:</span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// we're returning a substring of words, so &str is appropriate</span></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> first_word</span><span style="color:#E1E4E8">(words</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">split_whitespace</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">next</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"words should not be empty"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> sentence </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> "Hello, world!"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(sentence));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> owned </span><span style="color:#F97583">=</span><span style="color:#B392F0"> String</span><span style="color:#F97583">::</span><span style="color:#B392F0">from</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"A string"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This lets us remove a copy, we no longer have a <code>.to_string</code> in the body of
<code>first_word</code>.</p>
<p>Sometimes, we can’t do that though:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// because we are going to uppercase the first word, our return type can't</span></span>
<span class="line"><span style="color:#6A737D">// be &str anymore, because we aren't actually returning a substring: we are</span></span>
<span class="line"><span style="color:#6A737D">// creating our own new string.</span></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> first_word_uppercase</span><span style="color:#E1E4E8">(words</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#B392F0"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> words</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">split_whitespace</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">next</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"words should not be empty"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">to_uppercase</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> sentence </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> "Hello, world!"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word_uppercase</span><span style="color:#E1E4E8">(sentence));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> let</span><span style="color:#E1E4E8"> owned </span><span style="color:#F97583">=</span><span style="color:#B392F0"> String</span><span style="color:#F97583">::</span><span style="color:#B392F0">from</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"A string"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word_uppercase</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#B392F0"> println!</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{}"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">first_word_uppercase</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">owned));</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>How do you know that this is the case? Well, in this specific case,
<code>to_uppercase</code> already returns a <code>String</code>. So that’s a great hint.
If we tried to return a <code>&str</code>, we’d get an error:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="rust"><code><span class="line"><span style="color:#6A737D">// this can't work</span></span>
<span class="line"><span style="color:#F97583">fn</span><span style="color:#B392F0"> first_word_uppercase</span><span style="color:#E1E4E8">(words</span><span style="color:#F97583">:</span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#F97583"> &</span><span style="color:#B392F0">str</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> &</span><span style="color:#E1E4E8">words</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">split_whitespace</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">next</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">expect</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"words should not be empty"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583"> .</span><span style="color:#B392F0">to_uppercase</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>would give us</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="text"><code><span class="line"><span>error[E0515]: cannot return reference to temporary value</span></span>
<span class="line"><span> --> src/main.rs:7:5</span></span>
<span class="line"><span> |</span></span>
<span class="line"><span>7 | &words</span></span>
<span class="line"><span> | ______^-</span></span>
<span class="line"><span> | | ______|</span></span>
<span class="line"><span>8 | || .split_whitespace()</span></span>
<span class="line"><span>9 | || .next()</span></span>
<span class="line"><span>10 | || .expect("words should not be empty")</span></span>
<span class="line"><span>11 | || .to_uppercase()</span></span>
<span class="line"><span> | || ^</span></span>
<span class="line"><span> | ||_______________________|</span></span>
<span class="line"><span> | |_______________________returns a reference to data owned by the current function</span></span>
<span class="line"><span> | temporary value created here</span></span></code></pre>
<p>And that’s really it. Following this rule will get you through virtually every
scenario where you need to wonder about <code>String</code> and <code>&str</code>. With some practice,
you’ll internalize these rules, and when you feel comfortable with a level, you
can go up to the next one.</p>
<p>What about that last 1% though? Well, there is a next level…</p>
<h2 id="level-4-when-to-use-str-in-a-struct">Level 4: When to use <code>&str</code> in a struct</h2>
<p>Here’s the rule for level 4:</p>
<blockquote>
<p>Should you use a <code>&str</code> in a struct? If you’re asking that question, use
<code>String</code>. When you need to use <code>&str</code> in a struct, you’ll know.</p>
</blockquote>
<p>Storing references in structs is useful, for sure, and it’s good that Rust
supports it. But you’re only going to need it in fairly specific scenarios,
and if you feel like you’re worrying about <code>String</code> vs <code>&str</code>, you’re just
not in the position to be worrying about the complexity of storing a <code>&str</code>
in a struct yet.</p>
<p>In fact, some people believe in this rule so strongly that they’re working
on a language where storing references in structs isn’t even possible, and
it’s a language I’ve found very interesting lately:
<a href="https://www.hylo-lang.org/">Hylo</a>. They go a bit farther than that, even:
in Hylo, you think of everything as being values, rather than having references
at all. They think that you can write meaningful programs with this model.
But this isn’t a post about Hylo. I’ll write one of those eventually. My point
is just that you can really, truly, get away with not storing <code>&str</code>s in structs
for a <em>lot</em> of useful Rust programs. So it’s not really worth spending mental
energy on, until you determine that you must do so. That is the case when
you’ve profiled your program and determined that copying strings to and from
your struct is a big enough issue to bother with lifetimes.</p>
<p>I hope you’ve found this helpful, and if you have any other rules of thumb
like this, I’d love to hear about them!</p>
<hr/>
<p>Here’s my post about this post on BlueSky:</p>
<bluesky-post src="at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3l6nkx54sij2w" data-pagefind-ignore="true"> <div class="bluesky-embed s-3olstj"><div class="highlighted-post s-hik11q"><div class="meta s-hik11q"><a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="avatar-wrapper s-hik11q"><img loading="lazy" src="https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm" alt="" class="avatar s-hik11q"/></a> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr" target="_blank" class="name-wrapper s-hik11q"><bdi class="display-name-wrapper s-hik11q"><span class="display-name s-hik11q">Steve Klabnik</span></bdi> <span class="handle s-hik11q">@steveklabnik.com</span></a> <svg class="logo s-hik11q" fill="none" viewBox="0 0 320 286"><path fill="#0A7AFF" d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z"></path></svg></div> <p class="rich-text is-large s-1lecfnd">When should you use String vs &str?
Some <a target="_blank" href="https://bsky.app/hashtag/rustlang" class="hashtag s-1lecfnd">#rustlang</a> rules of thumb
<a target="_blank" href="https://steveklabnik.com/writing/when-should-i-use-string-vs-str/" rel="noopener nofollow" class="link s-1lecfnd">steveklabnik.com/writing/when...</a></p> <div class="embeds s-azdpbr"><a target="_blank" href="https://steveklabnik.com/writing/when-should-i-use-string-vs-str/" rel="noopener noreferrer nofollow" class="external-embed s-rtbqd8"> <div class="meta s-rtbqd8"><p class="title s-rtbqd8">When should I use String vs &str?</p> <p class="description s-rtbqd8"></p> <div class="domain s-rtbqd8"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z"></path></svg> <span class="domain-name">steveklabnik.com</span></div></div></a></div> <time datetime="2024-10-16T18:27:49.871Z" class="date s-hik11q">October 16, 2024 at 6:27 PM</time> <div class="stats s-hik11q"><span class="stat s-hik11q" title="287 likes"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z"></path></svg> <span>287</span></span> <span class="stat s-hik11q" title="43 reposts"><svg class="icon" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15"></path></svg> <span>43</span></span> <div class="gap s-hik11q"></div> <a href="https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3l6nkx54sij2w" target="_blank" class="permalink s-hik11q"><span>Read 30 replies on Bluesky</span></a></div></div></div> </bluesky-post>]]></content:encoded></item></channel></rss>
Raw headers
{
"age": "16668",
"cache-control": "public,max-age=900,must-revalidate",
"cache-status": "\"Netlify Edge\"; hit; ttl=31519332",
"cf-cache-status": "DYNAMIC",
"cf-ray": "a51051170b64a691-CMH",
"connection": "close",
"content-type": "application/xml",
"date": "Tue, 27 Oct 2026 08:26:36 GMT",
"etag": "W/\"441ec6928030c1e88166f9c4f820de53-ssl-df\"",
"referrer-policy": "strict-origin-when-cross-origin",
"server": "cloudflare",
"strict-transport-security": "max-age=31536000",
"transfer-encoding": "chunked",
"vary": "Accept-Encoding",
"x-content-type-options": "nosniff",
"x-frame-options": "DENY",
"x-nf-request-id": "01M5Y7FTPGQ0EBY1842GQ260EX"
}
Parsed with @rowanmanning/feed-parser
{
"meta": {
"type": "rss",
"version": "2.0"
},
"language": null,
"title": "Steve Klabnik",
"description": "Steve Klabnik's blog",
"copyright": null,
"url": "https://steveklabnik.com/",
"self": null,
"published": null,
"updated": null,
"generator": null,
"image": null,
"authors": [],
"categories": [],
"items": [
{
"id": "https://steveklabnik.com/writing/arguing-about-arguments/",
"title": "Arguing about arguments",
"description": "Here’s some Rust code: // define a function fn foo (x : i32 , y : i32 ) -> i32 { // body elided } // call it let z = foo ( 5 , 6 ); In programming language jargon, we call x and y parameters and 5 and 6 arguments . Rust does not have many fancy features related to parameters and…",
"url": "https://steveklabnik.com/writing/arguing-about-arguments/",
"published": "2026-09-21T00:00:00.000Z",
"updated": "2026-09-21T00:00:00.000Z",
"content": "<p>Here’s some Rust code:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#6A737D\">// define a function</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> foo</span><span style=\"color:#E1E4E8\">(x</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> i32</span><span style=\"color:#E1E4E8\">, y</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> i32</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">-></span><span style=\"color:#B392F0\"> i32</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // body elided</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// call it</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> z </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> foo</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#79B8FF\">5</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">6</span><span style=\"color:#E1E4E8\">);</span></span></code></pre>\n<p>In programming language jargon, we call <code>x</code> and <code>y</code> <em>parameters</em> and <code>5</code> and <code>6</code>\n<em>arguments</em>.</p>\n<p>Rust does not have many fancy features related to parameters and arguments. But\nother languages do. For example, <a href=\"https://api.rubyonrails.org/classes/ActionController/Redirecting.html#method-i-redirect_to\">here’s a function in Ruby</a>,\nanother language I’ve written a ton of:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"ruby\"><code><span class=\"line\"><span style=\"color:#6A737D\"># its definition in Rails</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">def</span><span style=\"color:#B392F0\"> redirect_to</span><span style=\"color:#E1E4E8\">(options </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> {}, response_options </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> {})</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> # body elided</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"># you can call it in all of these ways:</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"># pass a string with a URL</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">redirect_to </span><span style=\"color:#9ECBFF\">\"http://www.rubyonrails.org\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"># pass an instance of a model to go to it, if post.id = 5 then this might go to</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"># \"/posts/5\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">redirect_to @post</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"># invoke an action directly, might be equivalent to the above in a Posts controller</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">redirect_to </span><span style=\"color:#79B8FF\">action:</span><span style=\"color:#9ECBFF\"> \"show\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">id:</span><span style=\"color:#79B8FF\"> 5</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"># redirect to a model's URL, passing a flash message</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">redirect_to </span><span style=\"color:#B392F0\">post_url</span><span style=\"color:#E1E4E8\">(@post), </span><span style=\"color:#79B8FF\">alert:</span><span style=\"color:#9ECBFF\"> \"Watch it, mister!\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"># do the same but also set a specific HTTP code</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">redirect_to </span><span style=\"color:#B392F0\">post_url</span><span style=\"color:#E1E4E8\">(@post), </span><span style=\"color:#79B8FF\">status:</span><span style=\"color:#79B8FF\"> :found</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"># do both</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">redirect_to </span><span style=\"color:#B392F0\">post_url</span><span style=\"color:#E1E4E8\">(@post), </span><span style=\"color:#79B8FF\">status:</span><span style=\"color:#79B8FF\"> 301</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">flash:</span><span style=\"color:#E1E4E8\"> { </span><span style=\"color:#79B8FF\">updated_post_id:</span><span style=\"color:#E1E4E8\"> @post.</span><span style=\"color:#B392F0\">id</span><span style=\"color:#E1E4E8\"> }</span></span></code></pre>\n<p>I used to love Ruby, and now I love Rust. And to be honest, <code>redirect_to</code> and\nfriends are a huge reason why I am so against Rust gaining these sorts of fancy\nfeatures: they can be really concise, and beautiful to a certain kind of eye,\nbut it also makes it really, really hard to know what all you can do with a\nfunction. You pretty much have to hope that someone has written good documentation,\nand while Rails’s documentation is pretty good, not every library is going to have\nit.</p>\n<p>To be clear about it, there are several things bundled together here: flexible\nargument types, options hashes, defaults, and keyword-looking syntax. It’s that\nwhole style of API that shaped my skepticism, not argument labels by themselves.</p>\n<p>So, what exactly are all of these features?</p>\n<h2 id=\"named-parameters\">Named parameters</h2>\n<p>The simplest one is <em>named parameters</em>. As you might guess, this means that we can\nexplicitly use parameter names at the call site:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#6A737D\">// without named parameters</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> z </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> foo</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#79B8FF\">5</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">6</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// if Rust had them</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> z </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> foo</span><span style=\"color:#E1E4E8\">(x</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 5</span><span style=\"color:#E1E4E8\">, y</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 6</span><span style=\"color:#E1E4E8\">);</span></span></code></pre>\n<p>Named parameters are nice because they can give you more information at the call\nsite, but also they can be more flexible. Some languages also let you supply\nnamed arguments in a different order:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#6A737D\">// since we have names, we can write this:</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> z </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> foo</span><span style=\"color:#E1E4E8\">(x</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 5</span><span style=\"color:#E1E4E8\">, y</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 6</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// but we can also write this:</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> z </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> foo</span><span style=\"color:#E1E4E8\">(y</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 6</span><span style=\"color:#E1E4E8\">, x</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 5</span><span style=\"color:#E1E4E8\">);</span></span></code></pre>\n<p>The downside of named parameters is that they can get quite verbose:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"python\"><code><span class=\"line\"><span style=\"color:#6A737D\"># FastAPI in Python, UserOut class not shown</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">@app.get</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"/me\"</span><span style=\"color:#E1E4E8\">, </span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_model</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">UserOut, </span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> status_code</span><span style=\"color:#F97583\">=</span><span style=\"color:#79B8FF\">200</span><span style=\"color:#E1E4E8\">, </span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> tags</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">[</span><span style=\"color:#9ECBFF\">\"Users\"</span><span style=\"color:#E1E4E8\">],</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> summary</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">\"Get current user profile\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">def</span><span style=\"color:#B392F0\"> get_current_user</span><span style=\"color:#E1E4E8\">():</span></span></code></pre>\n<p>I tried to not strawman this by showing an extreme example, but <code>get</code> can take\n23 different keyword arguments, and so this could get quite, quite long.</p>\n<p>Named arguments can be good when passing expressions, so we can understand\nsomething more at the call site. In the above, <code>status_code=200</code> is much nicer\nthan a bare <code>200</code>, if you know HTTP well it’s kind of obvious what it is, but\nthe additional clarity is nice. But it can be common to break truly complex\nexamples down into variables that we end up forwarding to the function, and then\nit becomes verbose <em>and</em> redundant. As an example from inside FastAPI where\n<code>get</code> is invoked directly with all of those options:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"python\"><code><span class=\"line\"><span style=\"color:#79B8FF\">self</span><span style=\"color:#E1E4E8\">.router.get(</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> path,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_model</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">response_model,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> status_code</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">status_code,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> tags</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">tags,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> dependencies</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">dependencies,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> summary</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">summary,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> description</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">description,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_description</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">response_description,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> responses</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">responses,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> deprecated</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">deprecated,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> operation_id</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">operation_id,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_model_include</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">response_model_include,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_model_exclude</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">response_model_exclude,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_model_by_alias</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">response_model_by_alias,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_model_exclude_unset</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">response_model_exclude_unset,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_model_exclude_defaults</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">response_model_exclude_defaults,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_model_exclude_none</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">response_model_exclude_none,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> include_in_schema</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">include_in_schema,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_class</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">response_class,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> name</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">name,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> callbacks</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">callbacks,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> openapi_extra</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">openapi_extra,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> generate_unique_id_function</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">generate_unique_id_function,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">)</span></span></code></pre>\n<p>We actually snuck in another two features into this example: how did we only\npass five arguments into a function that has 23 parameters?</p>\n<h2 id=\"optional-andor-default-arguments\">Optional and/or Default arguments</h2>\n<p>As the name implies, optional arguments is a feature where you can choose to not pass in an argument,\nand default arguments lets you set a default when an argument is not passed.</p>\n<p>To go back to that example:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"python\"><code><span class=\"line\"><span style=\"color:#B392F0\">@app.get</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"/me\"</span><span style=\"color:#E1E4E8\">, </span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> response_model</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">UserOut, </span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> status_code</span><span style=\"color:#F97583\">=</span><span style=\"color:#79B8FF\">200</span><span style=\"color:#E1E4E8\">, </span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> tags</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">[</span><span style=\"color:#9ECBFF\">\"Users\"</span><span style=\"color:#E1E4E8\">],</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\"> summary</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">\"Get current user profile\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">def</span><span style=\"color:#B392F0\"> get_current_user</span><span style=\"color:#E1E4E8\">():</span></span></code></pre>\n<p>We only passed in five of the 22 possible arguments. If we had to pass all 22 every time, that would\nbe extremely verbose. So by declaring defaults for some parameters, we can leave them off of the call\nsite, and not pass arguments for them, and get the defaults instead. We saw this in the Ruby:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"ruby\"><code><span class=\"line\"><span style=\"color:#6A737D\"># its definition in Rails</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">def</span><span style=\"color:#B392F0\"> redirect_to</span><span style=\"color:#E1E4E8\">(options </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> {}, response_options </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> {})</span></span></code></pre>\n<p>The <code>= {}</code> are defaults for <code>options</code> and <code>response_options</code>: they’re an empty hash map.</p>\n<p>You’ll often find optional and default arguments come together: after all, if you don’t pass\nan argument in, what should the value of that parameter be? If your language has some sort of\nnull value, that can be one solution, and it can feel like optional arguments without default\narguments.</p>\n<p>But you can truly get one without the other with our next feature, function overloading:</p>\n<h2 id=\"function-overloading\">Function Overloading</h2>\n<p>Languages that support function overloading allow you to define multiple\nfunctions with the same name, but different signatures. Which function gets\ninvoked depends on which arguments you pass. This lets us truly have optional\narguments without default arguments. In Java:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"java\"><code><span class=\"line\"><span style=\"color:#6A737D\">// Version 1: Requires both arguments</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">void</span><span style=\"color:#B392F0\"> connect</span><span style=\"color:#E1E4E8\">(String url, </span><span style=\"color:#F97583\">int</span><span style=\"color:#E1E4E8\"> timeout) { ... }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// Version 2: Timeout is optional to the caller, and handled internally</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">void</span><span style=\"color:#B392F0\"> connect</span><span style=\"color:#E1E4E8\">(String url) { ... }</span></span></code></pre>\n<p>Here, if you pass in a timeout, you get the first function, and if you don’t,\nyou get the second. Instead of null, the parameter just doesn’t exist at all.</p>\n<p>This is a specific form of something called “function dispatch,” which basically\nmeans “hey when a function is invoked, what <em>exactly</em> gets called?” There are a\n<em>lot</em> of different ways to do this, and I don’t want this post to get into all\nof that right now, but there is a multitude of possibilities here: static vs\ndynamic, single vs multiple, predicate dispatch, pattern matching dispatch,\nprototype based dispatch… maybe I’ll write a post about that someday too.</p>\n<p>So, what are the pros and cons here?</p>\n<h2 id=\"these-features-make-me-uneasy\">These features make me uneasy</h2>\n<p>As I said above, Rust doesn’t support any of these three features right now. And\nthey have been a persistent request from the community for years. <a href=\"https://github.com/rust-lang/rfcs/issues/323\">Here</a> is\na 12 year old GitHub issue about this support, and it even includes something we\ndidn’t talk about, “variable arity argument lists.”</p>\n<p>That gets me to the first thing that I don’t like about these features: there\nare a <em>lot</em> of them. And they are all intertwined. As we mentioned before, if\nyou have optional arguments, you probably want default arguments. If you have\nnamed parameters, do you want to support <em>only</em> named parameters, or do you want\nto support both? It’s so tempting to just support all of them, and then you’ve\nadded a huge mountain of complexity.</p>\n<p>In Rust right now, you have to write multiple functions with slightly different\nnames:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#6A737D\">// a new empty vector</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> v </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">new</span><span style=\"color:#E1E4E8\">();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// one with a set capacity</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> v </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">with_capacity</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#79B8FF\">5</span><span style=\"color:#E1E4E8\">);</span></span></code></pre>\n<p>For the simple cost of “write two different names for your functions,” you get\nto keep the rules very simple: there is one function definition, and you invoke\nit by name. Done. Would it be nice to be able to say all of these:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> v </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">new</span><span style=\"color:#E1E4E8\">();</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> v </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">new</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#79B8FF\">5</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> v </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">new</span><span style=\"color:#E1E4E8\">(capacity</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 5</span><span style=\"color:#E1E4E8\">);</span></span></code></pre>\n<p>Sure, maybe. But the cost feels very, very high to me. If you’re used to these\nfeatures, maybe it doesn’t seem that high, but I have a Ruby tattoo on my body.\nI’ve seen some shit. And so I’ve grown to enjoy Rust’s simplicity in this area.\nYes, if you have a function with tons of parameters, you may need to write a\nbuilder instead, and that comes with its own form of verbosity:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#6A737D\">// if Vec had a builder</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> v </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">new</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">with_capacity</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#79B8FF\">5</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">build</span><span style=\"color:#E1E4E8\">();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// all of the code you have to write to make the builder elided</span></span></code></pre>\n<p>But the language itself stays simple, and the rules are easy. For a language\nthat’s already perceived as complex, this has always felt right to me. And that’s\nwhy I’ve pushed back against the various proposals to extend Rust in this way all\nof these years.</p>\n<p>But recently, I changed my opinion on one of these features, and one alone, and\nI would consider it okay if Rust gained them. Maybe. Also, it’s important to note\nthat I haven’t worked on Rust in years, and so my opinions are kind of irrelevant,\nbut whatever: it’s my blog, this is what opinions are for.</p>\n<h2 id=\"im-okay-with-named-parameters-now\">I’m okay with named parameters now</h2>\n<p>I think Rust could be okay with named parameters, but <em>not</em> optional or default\nones. And what changed my opinion is coding agents, actually. I’m sorry, maybe\nyou’re sick of me talking about AI, but hear me out.</p>\n<p>One of my beefs with named parameters, as mentioned above, is verbosity. But\nwhat you gain for that verbosity is clarity. I don’t think that verbosity and\nclarity are always the same thing; I like <code>{}</code> rather than <code>do</code>/<code>end</code>, and find\nit more readable, personally. But let’s take this function from the <code>image</code> crate:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#6A737D\">// definition</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">pub</span><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> crop_imm</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">I</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> GenericImageView</span><span style=\"color:#E1E4E8\">>(</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> image</span><span style=\"color:#F97583\">:</span><span style=\"color:#F97583\"> &</span><span style=\"color:#B392F0\">I</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> x</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> y</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> width</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> height</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">-></span><span style=\"color:#B392F0\"> SubImage</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#F97583\">&</span><span style=\"color:#B392F0\">I</span><span style=\"color:#E1E4E8\">> { </span><span style=\"color:#F97583\">...</span><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// calling it</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> cropped </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> image</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">imageops</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">crop_imm</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&</span><span style=\"color:#E1E4E8\">img, </span><span style=\"color:#79B8FF\">10</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">20</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">200</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">100</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// if we had named arguments</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> cropped </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> image</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">imageops</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">crop_imm</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> image</span><span style=\"color:#F97583\">:</span><span style=\"color:#F97583\"> &</span><span style=\"color:#E1E4E8\">img,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> x</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 10</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> y</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 20</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> width</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 200</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> height</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> 100</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">);</span></span></code></pre>\n<p>This does really increase readability, no question about it. But for me, typing\nall of that out just isn’t really super worth the squeeze. But when Claude is\ngonna write it? I care a lot less. And the readability advantage for humans is\neven stronger for agents: the extra clarity of the text inline seems to (I\nhaven’t run real evals on this yet…) be more helpful and use less tokens and\ncalls when looking at the actual call site. If an agent were reading that named\nexample above, it could tell that <code>10</code> is the <code>x</code> value without having to go\nlook up the function signature itself. “What’s good for humans is true for\nagents” strikes again.</p>\n<p>This is also why I’m still against optional arguments: they deliberately\n<em>hide</em> what is being passed in to the function, and obscure clarity at the call\nsite. What’s nice about them is that you have to type less. But I’m not typing\nmyself anymore. So the benefit just isn’t there, but the drawbacks are. This\nalso doesn’t solve the verbosity side of passing <code>foo=foo</code> in our FastAPI\nexample, it is still redundant. But one part of the downsides have been\nameliorated.</p>\n<p>Notably, all of this reasoning also applies to the builder pattern, and it’s why\nI try to use builders sparingly in Rust. It can emulate named arguments, which\nis good, but it can also emulate optional/default/variable arguments, which is\nbad. The juice is worth the squeeze sometimes, but not all the time, and\nprobably not even the majority of the time.</p>\n<p>So we’ve ameliorated the largest downside, but get to keep the largest upside.\nThat seems good. But even if we like the feature, there are still lots of\npractical concerns with Rust specifically that make it not a slam-dunk to add\nthem to the language.</p>\n<h2 id=\"theres-tons-of-practical-issues-though\">There’s tons of practical issues though</h2>\n<p>Now, “is this feature good in the abstract” is very different from “should this\nfeature” be implemented? There are a <em>lot</em> of open questions and drawbacks to\nimplementing named parameters. A simple one is that you don’t get rid of all of\nthose previous functions, <code>with_capacity</code> will exist forever. That’s a\nrelatively minor downside to some of the harder problems.</p>\n<p>First, strictly speaking Rust parameters aren’t names, they’re patterns. A\nname is a simple pattern, but you can get more complex:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> foo</span><span style=\"color:#E1E4E8\">((x, y)</span><span style=\"color:#F97583\">:</span><span style=\"color:#E1E4E8\"> (</span><span style=\"color:#B392F0\">i32</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">i32</span><span style=\"color:#E1E4E8\">)) {</span></span></code></pre>\n<p>This parameter doesn’t have a name, it’s a pattern that introduces two bindings.\nYou could come up with some syntax that gives things external names vs internal\nnames, but there’s that complexity revealing its head again.</p>\n<p>What happens when functions become values? I can write this today:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> resize</span><span style=\"color:#E1E4E8\">(width</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">, height</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // body elided</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> offset</span><span style=\"color:#E1E4E8\">(dx</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">, dy</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // body elided</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> f</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> fn</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#B392F0\">u32</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">u32</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=</span><span style=\"color:#F97583\"> if</span><span style=\"color:#E1E4E8\"> resizing { resize } </span><span style=\"color:#F97583\">else</span><span style=\"color:#E1E4E8\"> { offset };</span></span></code></pre>\n<p>What name do we use for <code>f</code>’s parameters? They can be named different things.\nDoes this become an error? What names can you use when invoking <code>f</code>? Making this\nmore complicated, Rust actually will accept this code right now:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">type</span><span style=\"color:#B392F0\"> Callback</span><span style=\"color:#F97583\"> =</span><span style=\"color:#B392F0\"> fn</span><span style=\"color:#E1E4E8\">(width</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">, height</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> f</span><span style=\"color:#E1E4E8\">(g</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> Callback</span><span style=\"color:#E1E4E8\">) {}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> bar</span><span style=\"color:#E1E4E8\">(x</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">, y</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">) {}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> f</span><span style=\"color:#E1E4E8\">(bar) ;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>The names in <code>Callback</code> are just documentation, as you can see they’re not\nrequired. Does this become an error? Do we require <code>bar</code> to change its names?\nThe same goes for trait definitions, actually:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">trait</span><span style=\"color:#B392F0\"> Writer</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> write</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&mut</span><span style=\"color:#79B8FF\"> self</span><span style=\"color:#E1E4E8\">, data</span><span style=\"color:#F97583\">:</span><span style=\"color:#F97583\"> &</span><span style=\"color:#E1E4E8\">[</span><span style=\"color:#B392F0\">u8</span><span style=\"color:#E1E4E8\">]);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Sink</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">impl</span><span style=\"color:#B392F0\"> Writer</span><span style=\"color:#F97583\"> for</span><span style=\"color:#B392F0\"> Sink</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> write</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&mut</span><span style=\"color:#79B8FF\"> self</span><span style=\"color:#E1E4E8\">, bytes</span><span style=\"color:#F97583\">:</span><span style=\"color:#F97583\"> &</span><span style=\"color:#E1E4E8\">[</span><span style=\"color:#B392F0\">u8</span><span style=\"color:#E1E4E8\">]) {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // body elided</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>Is this an error? Is it okay?</p>\n<p>Here’s another problem: evaluation order.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> consume</span><span style=\"color:#E1E4E8\">(data</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">u8</span><span style=\"color:#E1E4E8\">>, length</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> usize</span><span style=\"color:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // body elided</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>Right now, Rust always evaluates parameters from left to right at the call site.\nSo if we use the named version:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> consume</span><span style=\"color:#E1E4E8\">(data</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">u8</span><span style=\"color:#E1E4E8\">>, length</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> usize</span><span style=\"color:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // body elided</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> consume2</span><span style=\"color:#E1E4E8\">(length</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> usize</span><span style=\"color:#E1E4E8\">, data</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">u8</span><span style=\"color:#E1E4E8\">>) {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // body elided</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> data </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> vec!</span><span style=\"color:#E1E4E8\">[</span><span style=\"color:#79B8FF\">1</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">2</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">3</span><span style=\"color:#E1E4E8\">];</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // this is an error, borrow after move</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> consume</span><span style=\"color:#E1E4E8\">(data, data</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">len</span><span style=\"color:#E1E4E8\">());</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // this is okay</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> consume2</span><span style=\"color:#E1E4E8\">(data</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">len</span><span style=\"color:#E1E4E8\">(), data);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // does this compile or not?</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> consume</span><span style=\"color:#E1E4E8\">(length</span><span style=\"color:#F97583\">:</span><span style=\"color:#E1E4E8\"> data</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">len</span><span style=\"color:#E1E4E8\">(), data</span><span style=\"color:#F97583\">:</span><span style=\"color:#E1E4E8\"> data);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>If we keep left to right evaluation order according to the definition, but let\nyou use named values in any order, suddenly some orders at the call site compile\nand some orders do not. Do we change evaluation order to do whatever compiles?\nThat feels very dangerous and confusing.</p>\n<p>Rust does let you do this with struct fields:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Args</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> data</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">u8</span><span style=\"color:#E1E4E8\">>,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> length</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> usize</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> data </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> vec!</span><span style=\"color:#E1E4E8\">[</span><span style=\"color:#79B8FF\">1</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">2</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">3</span><span style=\"color:#E1E4E8\">];</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> </span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // works</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> args </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> Args</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> length</span><span style=\"color:#F97583\">:</span><span style=\"color:#E1E4E8\"> data</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">len</span><span style=\"color:#E1E4E8\">(),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> data,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // doesn't</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> args </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> Args</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> data,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> length</span><span style=\"color:#F97583\">:</span><span style=\"color:#E1E4E8\"> data</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">len</span><span style=\"color:#E1E4E8\">(),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> };</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>This is one reason why a lot of proposals for named arguments try to unify them\nwith structs. As I said before, not insurmountable, just things that need to be\nsorted.</p>\n<p>Another small thing is a compatibility hazard: if we start naming parameters,\nrenaming them breaks callers. Is that okay? Could you introduce aliases to help\nwith this? That’s more complexity…</p>\n<p>These are just some of the problems that come up in the design space, and this\npost is getting long, so I’ll leave it at this for now. But proposals for doing\nthis have to engage with all of this and more, which is why language design is\ndifficult.</p>\n<h2 id=\"in-conclusion\">In conclusion</h2>\n<p>I like to think that I change my opinions over time as the world or my\nunderstanding of it changes. So I thought it might be interesting to share a way\nin which an opinion that I’ve held for over a decade has changed a bit lately.\nI’m still not sure if named parameters are right for Rust, but I’m at least\nsomewhat open to the idea these days. We’ll see what the language team decides\nif anyone does decide to keep trying to push this forward. And people do, for\nexample, I am writing this in part because I saw <a href=\"https://botahamec.dev/named-optional-args\">this post</a> go by today.\nThis post isn’t intended to be support or a rebuttal of it, I actually didn’t\neven read it yet, it’s just that I’ve been thinking about this a lot lately\n(while working on <a href=\"https://rue-lang.dev/\">Rue</a>) and seeing that go by pushed me to write some of\nthe thoughts down. You should probably go check it out, as it’s an actual\nproposal in this space and this post is more of an exploration of the general\nidea and my own thoughts on it.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mw2ahu3pv22q\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Arguing about arguments\n\n<a target=\"_blank\" href=\"https://steveklabnik.com/writing/arguing-about-arguments/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/argu...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/arguing-about-arguments/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreigpeqitvd6az3jx55koev3nbbllzxb6moswv6erkvptevqphh553e\" alt=\"\" class=\"thumbnail s-rtbqd8\"/> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Arguing about arguments</p> <p class=\"description s-rtbqd8\">Here’s some Rust code: // define a function fn foo (x : i32 , y : i32 ) -> i32 { // body elided } // call it let z = foo ( 5 , 6 ); In programming language jargon, we call x and y parameters and 5 and 6 arguments . Rust does not have many fancy features related to parameters and…</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2026-09-21T17:30:35.997Z\" class=\"date s-hik11q\">September 21, 2026 at 5:30 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"92 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>92</span></span> <span class=\"stat s-hik11q\" title=\"11 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>11</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mw2ahu3pv22q\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 22 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/",
"title": "So, what happened to my Claude Code series?",
"description": "I started off this year writing a lot, and wanted to keep up the pace. I did good at first! And then… I didn’t manage to. In some ways, this is because I have just been super busy! I’ve never had less free time, and I’m okay with that. There’s a tremendous amount of good things…",
"url": "https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/",
"published": "2026-09-02T00:00:00.000Z",
"updated": "2026-09-02T00:00:00.000Z",
"content": "<p>I started off this year writing a lot, and wanted to keep up the pace. I did\ngood at first! And then… I didn’t manage to.</p>\n<p>In some ways, this is because I have just been super busy! I’ve never had less\nfree time, and I’m okay with that. There’s a tremendous amount of good things\ngoing on in my life.</p>\n<p>But it’s also put this whole Claude Code series in an awkward spot, because\nthings have been changing so rapidly, that it’s been hard to keep up. And I also\nneed to figure out what it is I actually want to say, and my practice has\nchanged tremendously since January!</p>\n<p>I’m thinking about this a lot today in part because one of the things that happened\nsince we last talked about this stuff was the release of Claude Fable. And now,\na new Fable has just been put out into the world, and OpenAI’s Astra is coming very\nsoon. New models don’t always mean that practice inherently changes, but like, it’s\njust feeling like this is getting further and further behind, and so I think it’s\nprobably good for me to just officially close this series. I still really like what\nI wrote, and think it’s still valuable today for getting started, but I just don’t\nthink I can commit to keeping up with documenting everything I do at the pace that\nstuff is happening at. So being honest with myself about that is very useful.</p>\n<p>To those that found this series helpful, I’m glad, and I’m sorry I’m not writing\nmore about it. I’ll still write things about this topic in general, but the core\nidea of a “how to get started doing this stuff” series just isn’t in the cards\nfor me right now.</p>\n<p>AI is here to stay, and it is dramatically changing how people work. I believe\nthat more strongly than ever. So stopping isn’t about me somehow getting bearish\non this stuff. It’s just that I’m too darn busy for this specific project.</p>\n<p>This is also why the “notes” section on my website still has zero entries. I was\nhoping that something to put stuff shorter form than a blog post would make it\neasier to write, but in the end, I can’t even find much time for that.</p>\n<p>Okay well I’m having trouble wrapping this up so, I’ll just leave it here for\nnow.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mukmbg4ugs2e\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">So, what happened to my Claude Code series? <a target=\"_blank\" href=\"https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/so-w...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/so-what-happened-to-my-claude-code-series/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">So, what happened to my Claude Code series?</p> <p class=\"description s-rtbqd8\">I started off this year writing a lot, and wanted to keep up the pace. I did good at first! And then… I didn’t manage to. In some ways, this is because I have just been super busy! I’ve never had less free time, and I’m okay with that. There’s a tremendous amount of good things…</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2026-09-02T18:53:57.487Z\" class=\"date s-hik11q\">September 2, 2026 at 6:53 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"53 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>53</span></span> <span class=\"stat s-hik11q\" title=\"1 repost\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>1</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mukmbg4ugs2e\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 8 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/too-many-words-about-dids/",
"title": "Too many words about DIDs",
"description": "Your “Bluesky account” is not just a Bluesky account: it is an account that can be used with a variety of other applications. This post is going to be an exploration of part of what that means from a technical perspective, so if you’re not a software developer, this post isn’t…",
"url": "https://steveklabnik.com/writing/too-many-words-about-dids/",
"published": "2026-07-14T00:00:00.000Z",
"updated": "2026-07-14T00:00:00.000Z",
"content": "<p>Your “Bluesky account” is not <em>just</em> a Bluesky account: it is an account that\ncan be used with a variety of other applications. This post is going to be an\nexploration of part of what that means from a technical perspective, so if you’re\nnot a software developer, this post isn’t for you. But what I’m going to explain\nis the technical mechanism for how your account works separate from Bluesky, and\nin fact, separate from any particular app.</p>\n<p>Let’s talk about identity: who are you, anyway? Users of a system need some sort\nof way to describe who they are to use it. If you want to log in, you need to\npresent who you are. If you want to make a post, well, we need to know who the\nauthor of that post is. For atproto, the protocol that underlies Bluesky and\nother apps in the ATmosphere, they use the “Decentralized Identity” standard,\nalso known as DID. The <a href=\"https://www.w3.org/TR/did-1.0/\">W3C standardized DIDs in\n2022</a>. As you might guess from the name, DIDs\nare, an identifier that you can use as the basis of identity for building\napplications. And the idea is that these identifiers are decentralized. However,\na lot of people have a lot of feelings about that specific word, and often\naccuse atproto of not being properly decentralized. We’re going to go over the\ndetails so you can understand how this works, and you can decide for yourself if\nthis approach suits you or not.</p>\n<h2 id=\"dids-and-did-documents\">DIDs and DID Documents</h2>\n<p>Here is my DID, we’ll use this as an example: <code>did:plc:3danwc67lo7obz2fmdg6jxcr</code></p>\n<p>There are three parts, separated by colons: The scheme (<code>did</code>), the method (<code>plc</code>),\nand the DID method-specific identifier (<code>3danwc67lo7obz2fmdg6jxcr</code>).</p>\n<p>To use a DID, such as <code>did:plc:3danwc67lo7obz2fmdg6jxcr</code>, you resolve it into a\n<a href=\"https://www.w3.org/TR/did-1.0/#dfn-did-documents\">DID Document</a></p>\n<blockquote>\n<p>A set of data describing the DID subject, including mechanisms, such as\ncryptographic public keys, that the DID subject or a DID delegate can use to\nauthenticate itself and prove its association with the DID.</p>\n</blockquote>\n<p>That document contains <a href=\"https://www.w3.org/TR/did-1.0/#core-properties\">various\nproperties</a> that describe the\nidentity. Here’s my DID Document, at the time of writing:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"json\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">{</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"@context\"</span><span style=\"color:#E1E4E8\">: [</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"https://www.w3.org/ns/did/v1\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"https://w3id.org/security/multikey/v1\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"https://w3id.org/security/suites/secp256k1-2019/v1\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> ],</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"id\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"did:plc:3danwc67lo7obz2fmdg6jxcr\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"alsoKnownAs\"</span><span style=\"color:#E1E4E8\">: [</span><span style=\"color:#9ECBFF\">\"at://steveklabnik.com\"</span><span style=\"color:#E1E4E8\">],</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"verificationMethod\"</span><span style=\"color:#E1E4E8\">: [</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"id\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"did:plc:3danwc67lo7obz2fmdg6jxcr#atproto\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"type\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"Multikey\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"controller\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"did:plc:3danwc67lo7obz2fmdg6jxcr\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"publicKeyMultibase\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"zQ3shfNec2kPEb8cL77gSRMbCwbWE27p9nxKkcc4E82xtW8RJ\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> ],</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"service\"</span><span style=\"color:#E1E4E8\">: [</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"id\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"#atproto_pds\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"type\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"AtprotoPersonalDataServer\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"serviceEndpoint\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"https://morel.us-east.host.bsky.network\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> ]</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>This document gives you everything you need to know to determine who I am, that is,\ngiven an arbitrary post that claims it’s written by me, this document describes how\nyou’d verify that claim.</p>\n<p>We’ll get into how to do that in a moment, but first, how do you resolve\nthat DID into that DID document? Well, it’s pretty easy: each method is a\nstandard that describes how you do that. So when you see <code>did:plc</code>, that means\nwe use the PLC standard, which we’ll be going over in a moment. Another method\nsupported by Bluesky is <code>did:web</code>. In that case, you wouldn’t use the PLC\nstandard, you’d use the Web one.</p>\n<p>This is the sense in which DIDs are decentralized: when you present your\nidentity, you get to decide what method validates that that is a real identity.\nThere’s no centralized authority that determines which DID types are valid. Now,\nof course, that doesn’t mean that every application supports every DID method,\nbecause while this specification is <em>very</em> generic, you’re still going to have\nto write some code to implement that particular method. I could say “Hey I’m\n<code>did:foo:1243</code>” and unless your app supports the <code>foo</code> method, it’s not gonna\ninherently just know what to do. So that is one important caveat.</p>\n<h2 id=\"didweb\"><code>did:web</code></h2>\n<p>Let’s explain this resolution process for the <code>web</code> method. While supported by\nBluesky, a very small number of users actually use <code>did:web</code>, but it’s a simpler\nmethod and so I think it’s illustrative to go over first. I’ll be using <a href=\"https://bsky.app/profile/web.lizthegrey.com\">Liz\nFong-Jones</a>’ <code>did:web</code> account as an\nexample here. Her identity for that account is <code>did:web:lizthegrey.com</code>.</p>\n<p>So how do we resolve this DID into a DID Document? We take the method-specific\nidentifier, which in this case is <code>lizthegrey.com</code>, and put it into this URL template:</p>\n<p><code>https://<id>/.well-known/did.json</code></p>\n<p>You can then go fetch this URL to resolve it into the DID Document, which at the\ntime of writing, looks like this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"json\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">{</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"@context\"</span><span style=\"color:#E1E4E8\">: [</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"https://www.w3.org/ns/did/v1\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"https://w3id.org/security/multikey/v1\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"https://w3id.org/security/suites/secp256k1-2019/v1\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> ],</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"id\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"did:web:lizthegrey.com\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"alsoKnownAs\"</span><span style=\"color:#E1E4E8\">: [</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"at://web.lizthegrey.com\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"did:plc:i4tfenpfog244rxry5uz4vtk\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> ],</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"verificationMethod\"</span><span style=\"color:#E1E4E8\">: [</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"id\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"did:web:lizthegrey.com#atproto\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"type\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"Multikey\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"controller\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"did:web:lizthegrey.com\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"publicKeyMultibase\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"zQ3shnEqBNR5cuTePW8FyvnrRQaFf6Y7sCi5NBmDpteVXFjb6\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> ],</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"service\"</span><span style=\"color:#E1E4E8\">: [</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"id\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"#atproto_pds\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"type\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"AtprotoPersonalDataServer\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"serviceEndpoint\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"https://pds.lizthegrey.com\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> ]</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>This is very simple! So why might we not want to use <code>did:web</code>? Why bother with\nany other system? Well, this relies on the DNS system. One could make the\nargument that ultimately, this is still centralized in some form. If Liz’s\ndomain registrar were to take away her domain, she would also lose control of\nthis DID. In a more generic sense, if Liz decides she wants to not use that\ndomain anymore, she will lose control of that identity to whoever does. That\ncould be through non-malicious means, like letting it expire and someone else\npurchases it, or through malicious ones, like a hack which would compromise her\nregistrar account and take the domain over.</p>\n<p>Also, you need to have a web server running on that domain with infinite uptime;\nif the server goes down, so does your ability to get the document.</p>\n<p>When this DID document changes, there’s no mechanism for clients to know that\nit’s changed, which means applications may use one that’s out of date, or that\nthere is lag between updating the document and updating the application built on\nit, which may cause temporary problems until the latest document is fetched.</p>\n<h2 id=\"didplc\"><code>did:plc</code></h2>\n<p>All of these drawbacks led Bluesky to develop their own DID method, which\nattempts to fix these problems and others. This method is called\n<a href=\"https://web.plc.directory/spec/v0.1/did-plc\"><code>did:plc</code></a>. To resolve a <code>did:plc</code>,\nyou take the entire DID, and put it in this template:</p>\n<p><code>https://plc.directory/<did></code></p>\n<p>You can then fetch that URL and get the DID document.</p>\n<p>So… what’s the difference? Well, in this case, both nothing and something. In\na very literal sense, both are resolved in the same way: you fetch a URL.\nHowever, the details matter. There is already two ways in which this is different\nthan DID:Web:</p>\n<ol>\n<li>Your DID is no longer tied to a specific domain name. I can let\n<code>steveklabnik.com</code> expire and move to <code>steve.klabnik.com</code> and my <code>did:plc</code>\nstays the same.</li>\n<li>While a web server still needs to be running, that’s the job of plc.directory, not\nmy own job. This is operationally much simpler.</li>\n</ol>\n<p>I’ve presented the above as pros, but there are also cons. Before, I had to\ntrust the DNS system and domain registrars, now I have to trust plc.directory.\nAll of the same caveats apply in that sense, I have to trust that they don’t\ntake my ID away from me, or that it doesn’t get stolen, etc. However, there are\nalso some important details that mitigate this, which we’ll get to. But for some\npeople, neither trusting DNS nor trusting plc.directory is acceptable, and there\nare other DID methods that use, for example, a blockchain to resolve the name.\nBluesky does not support using any of those DID methods, so for this\napplication, it’s not really relevant, but it’s important to know that they\nexist.</p>\n<p>Why do it this way? Well, the simplest way to put it is this: setting up a\n<code>did:web</code> involves a lot of “nerd stuff.” You have to register a domain, and\nthat’s also an ongoing monetary cost. You have to know how to set up a web\nserver, and author some JSON to put on that server. You have to keep it running.\nYou have to know how to store your private keys, and keep them safe. It’s a\nnon-starter compared to “sign up for this web app.” And Bluesky’s goals involves\nmaking this platform accessible to non-nerds. By having plc.directory manage\nall of this, we eliminate all of those steps.</p>\n<p>While drafting this post, I have also been made aware of\n<a href=\"https://identity.foundation/didwebvh/v1.0/\"><code>did:webvh</code></a>, which expands on <code>did:web</code> and attempts to rectify\nsome of its shortcomings. I have not read the spec yet, but it has reached 1.0,\nso it is probably worth checking out. I wanted to get this post shipped last\nweek, and didn’t want to delay it further by adding another section, but if\nI were writing this post in the future, I’d probably want to talk about it as\nwell, so just a little heads-up there.</p>\n<h2 id=\"does-bluesky-own-your-identity\">Does Bluesky own your identity?</h2>\n<p>But it does also mean that, in some sense, Bluesky still owns your identity.\nThey’ve generated a keypair for you, and they have access to the secret key.\nThat’s unacceptable for some people. So how do you fix that?</p>\n<p>Well, <code>did:plc</code> has some additional features that <code>did:web</code> does not. For\nexample, <code>did:plc</code> will allow you to register additional keypairs with your ID\nand use them to rotate your signing keys. This allows you to remove the Bluesky\ngenerated keys and insert your own.</p>\n<p>While that is true, it’s also the case that your PDS needs to use your keys to\nsign your posts. As such, most people are likely to store their keys in their PDS,\nand so if you are using a Bluesky managed PDS, well, you’ve uploaded your keys to\ntheir infrastructure, and that’s probably not acceptable if you’re trying to keep\nyour identity away from Bluesky. Of course, the solution there is to run your own\nPDS and then rotate your keys. At that point, your key is living on infrastructure\nyou own, and Bluesky has no say over it any more.</p>\n<p>I think that this possibility is an important design property, and allows\nmotivated users to meaningfully own their identity. A criticism of this boils\ndown to “well, most users won’t do that,” and while that’s true, I also think\nthat’s okay for most people, and that having the choice is more important than\nforcing every user to deal with their own key management.</p>\n<h2 id=\"in-summary\">In summary</h2>\n<p>This is kind of an abrupt end to this post, but I just wanted to get some things\ndown ‘on paper’ as it were. I hope you’ve learned a bit about identity and how\nit works with atproto.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mqmjx73a2s2w\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Too many words about DIDs: <a target=\"_blank\" href=\"https://steveklabnik.com/writing/too-many-words-about-dids/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/too-...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/too-many-words-about-dids/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Too many words about DIDs</p> <p class=\"description s-rtbqd8\">Blog post: Too many words about DIDs by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2026-07-14T15:37:00.032Z\" class=\"date s-hik11q\">July 14, 2026 at 3:37 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"132 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>132</span></span> <span class=\"stat s-hik11q\" title=\"29 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>29</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mqmjx73a2s2w\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 12 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/",
"title": "Compilers and AI 'cyber' defense",
"description": "I haven’t had time to blog in a while, I have a lot to say, but have been doing a lot of things and so haven’t gotten words down in a while. One of those things in the past few weeks has been picking back up my Rue project. I’m really excited for what it’s turning into, even…",
"url": "https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/",
"published": "2026-07-10T00:00:00.000Z",
"updated": "2026-07-10T00:00:00.000Z",
"content": "<p>I haven’t had time to blog in a while, I have a lot to say, but have been doing\na lot of things and so haven’t gotten words down in a while.</p>\n<p>One of those things in the past few weeks has been picking back up my <a href=\"https://rue-lang.dev/\">Rue</a>\nproject. I’m really excited for what it’s turning into, even though it’s not\nreally ready for anyone else to use just yet. So go take a look if you want, but\ndon’t expect anything amazing, it’s very much a work in progress.</p>\n<p>Anyway, one of the things that got me back into working on it is the releases of\nFable and Sol. I hadn’t really touched Rue since January, and a lot of things have\nchanged since then. It’s fun to have new models take a look at older projects and\nsee what their new perspectives are.</p>\n<p>Unfortunately, due to the intelligence of these new models, the US government\nhas asked for and imposed restrictions. These are largely around things like\ncybersecurity. Since I don’t do cybersecurity work, I didn’t think they would\naffect me.</p>\n<p>(Aside: I put ‘cyber’ in quotes in the title because I hate that apparently\nthat’s the term folks are using now. Sigh.)</p>\n<p>Well:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"plaintext\"><code><span class=\"line\"><span>ⓘ This content can't be shown</span></span>\n<span class=\"line\"><span> We take extra caution with cybersecurity requests. If you're a security professional, you may be able to apply for</span></span>\n<span class=\"line\"><span> Trusted Access.</span></span>\n<span class=\"line\"><span> Trusted Access: https://openai.com/form/enterprise-trusted-access-for-cyber/</span></span>\n<span class=\"line\"><span> Learn more: https://help.openai.com/en/articles/20001326</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>› please continue</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>ⓘ This content can't be shown</span></span>\n<span class=\"line\"><span> We take extra caution with cybersecurity requests. If you're a security professional, you may be able to apply for</span></span>\n<span class=\"line\"><span> Trusted Access.</span></span>\n<span class=\"line\"><span> Trusted Access: https://openai.com/form/enterprise-trusted-access-for-cyber/</span></span>\n<span class=\"line\"><span> Learn more: https://help.openai.com/en/articles/20001326</span></span></code></pre>\n<p>This is the output from Sol, but the same thing happens with Fable too. I asked Sol\nabout it, and this is what it says:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"plaintext\"><code><span class=\"line\"><span>• It is a false positive from the safety layer, not a judgment that you asked for anything malicious. Rue contains</span></span>\n<span class=\"line\"><span> fuzzers, crash reproducers, sanitizer hooks, child processes, deliberate out-of-bounds writes, and compiler-panic</span></span>\n<span class=\"line\"><span> tests. Those patterns look similar to exploit-development activity when a classifier sees commands or snippets without</span></span>\n<span class=\"line\"><span> fully trusting the repository-ownership context.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> RUE-560 is an especially likely trigger because the correct regression test deliberately performs a one-byte overflow</span></span>\n<span class=\"line\"><span> in an ASan-instrumented child and requires that child to crash. That is defensive testing, but the surface pattern is</span></span>\n<span class=\"line\"><span> still “construct an overflow and observe the failure.”</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> I cannot disable or bypass that layer. I can reduce interruptions by keeping work tightly scoped to this repository,</span></span>\n<span class=\"line\"><span> putting negative behavior inside the existing test harness, describing the defensive purpose explicitly, avoiding</span></span>\n<span class=\"line\"><span> reusable attack tooling, and checkpointing progress before risky-looking verification. The persistent goal means that</span></span>\n<span class=\"line\"><span> if a turn is interrupted, the next continuation can inspect the workspace and resume without needing you. Nothing I’ve</span></span>\n<span class=\"line\"><span> found suggests the project or your request is dangerous.</span></span></code></pre>\n<p>It is really unfortunate that, in order to make sure my compiler isn’t going to\nproduce incorrect codegen, this trips the safety guards. I guess that simply\nsaying “hey this is a toy language nobody uses and so it’s fine I’m not hacking\nanyone” and that not allowing me around the guards is a “good” thing, but at the\nsame time, it is unfortunate if these policies end up making it so that individuals\nlike me can’t make my software as robust as products from large organizations.</p>\n<p>I don’t know how to end this post really, just wanted to put that out there. I’m\nstill thinking about the implications, and I hope you do as well.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mqckcblr5c2k\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Compilers and AI 'cyber' defense\n\n<a target=\"_blank\" href=\"https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/comp...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/compilers-and-ai-cyber-defense/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Compilers and AI 'cyber' defense</p> <p class=\"description s-rtbqd8\">Blog post: Compilers and AI 'cyber' defense by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2026-07-10T16:16:34.386Z\" class=\"date s-hik11q\">July 10, 2026 at 4:16 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"27 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>27</span></span> <span class=\"stat s-hik11q\" title=\"7 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>7</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mqckcblr5c2k\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 5 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/",
"title": "The most important thing when working with LLMs",
"description": "Okay, so you’ve got the basics of working with Claude going. But you’ve probably run into some problems: Claude doesn’t do what you want it to do, it gets confused about what’s happening and goes off the rails, all sorts of things can go wrong. Let’s talk about how to improve…",
"url": "https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/",
"published": "2026-01-27T00:00:00.000Z",
"updated": "2026-01-27T00:00:00.000Z",
"content": "<p>Okay, so you’ve got the basics of working with Claude going. But you’ve probably\nrun into some problems: Claude doesn’t do what you want it to do, it gets\nconfused about what’s happening and goes off the rails, all sorts of things can\ngo wrong. Let’s talk about how to improve upon that.</p>\n<p>The most important thing that you can do when working with an LLM is give it a\nway to quickly evaluate if it’s doing the right thing, and if it isn’t, point it\nin the right direction.</p>\n<p>This is incredibly simple, yet, like many simple things, also wildly complex.\nBut if you can keep this idea in mind, you’ll be well equipped to become\neffective when working with agents.</p>\n<h2 id=\"two-lessons-from-teaching-children\">Two lessons from teaching children</h2>\n<p>A long time ago, I used to teach programming classes. Many of these were to\nadults, but some of them were to children. Teenaged children, but children\nnonetheless. We used to do an exercise to try and help them understand the\ndifference between talking in English and talking in Ruby, or JavaScript, or\nwhatever kind of programming language rather than human language. The exercise\nwent like this:</p>\n<p>I would have a jar of peanut butter, a jar of jelly, a loaf of bread, a spoon,\nand a knife. I would ask the class to take a piece of paper and write down a\nseries of steps to make a peanut butter and jelly sandwich. They’d all then give\nme their algorithms, and the fun part for me began: find one that’s innocently\nwritten that I could hilariously misinterpret. For example, I might find one\nlike:</p>\n<ol>\n<li>Put the peanut butter on the bread</li>\n<li>Put the jelly on the bread</li>\n<li>Put the bread together</li>\n</ol>\n<p>I’d read this aloud to the class, you all understand this is a recipe for a\npeanut butter and jelly sandwich, right? I’d take the jar of peanut butter and\nplace it upon the unopened bag of bread. I’d do the same with the jar of jelly.\nThis would of course, squish the bread, which feels slightly transgressive given\nthat you’re messing up the bread, so the kids would love that. I’d then say\nsomething like “the bread is already together, I do not understand this\ninstruction.”</p>\n<p>After the inevitable laughter died down, I’d make my point: the computer will do\n<em>exactly</em> what you say, but not what you mean. So you have to get good at\nfiguring out when you said something different than what you mean. Sort of\nironically, LLMs are kind of the inverse of this: they’ll sometimes try to\nfigure out what you mean, and then do that, rather than simply doing what you\nsay. But the core thing here is the same: semantic drift from what we intended\nour program to do, and what it actually does.</p>\n<p>The second lesson is something I came up with sometime, I don’t even remember\nhow exactly. But it’s something I told my students a lot. And that’s this:</p>\n<p>If your program did everything you wanted without problems, you wouldn’t be\nprogramming: you’d be using your program. The act of programming is itself\nperpetually to be in a state where something is either inadequate or broken,\nand the job is to fix that.</p>\n<p>I also think this is a bit simplistic but also getting at something. I had\noriginally come up with this in the context of trying to explain how you\nneed to manage your frustration when programming; if you get easily upset by\nsomething not working, doing computer programming might not be for you. But I do\nthink these two things combine into something that gets to the heart of what we\ndo: we need to understand what it is we want our software to do, and then make\nit do that. Sometimes, our software doesn’t do something yet. Sometimes, it does\nsomething, but incorrectly. Both of these cases result in a divergence from the\nprogram’s intended behavior.</p>\n<p>So, how do we know if our program does what it should do?</p>\n<h2 id=\"gotta-go-fast\">Gotta go fast</h2>\n<p>Well, what we’ve been doing so far is:</p>\n<ol>\n<li>Asking the LLM to do something by typing up what we want it to do</li>\n<li>Closely observing its behavior and course correcting it when it goes off of\nthe rails</li>\n<li>Eventually, after it says that it’s finished, reviewing its output</li>\n</ol>\n<p>This is our little mini software development lifecycle, or “SDLC.” This process\nworks, but is slow. That’s great for getting the feel of things, but programmers\nare process optimizers by trade. One of my favorite tools for optimization is\ncalled <a href=\"https://www3.cs.stonybrook.edu/~rezaul/Spring-2012/CSE613/reading/Amdahl-1967.pdf\">Amdahl’s law</a>. The core idea is this, formulated in my own\nwords:</p>\n<blockquote>\n<p>If you have a process that takes multiple steps, and you want to speed it up,\nif you optimize only one step, the maximum amount of speedup you’ll get is\ndetermined by the portion of the process that step takes.</p>\n</blockquote>\n<p>In other words, imagine we have a three step process:</p>\n<ol>\n<li>One minute</li>\n<li>Ten minutes</li>\n<li>Two minutes</li>\n</ol>\n<p>This process takes a total of 13 minutes to complete. If we speed up step 3 by\ndouble, it goes from two minutes to one minute, and now our process takes 12\nminutes. However, if we were able to speed up step 2 by double, we’d cut off\nfive minutes, and our process would now take 8 minutes.</p>\n<p>We can use this style of analysis to guide our thinking in many ways, but the\nmost common way, for me, is to decide where to put my effort. Given the process\nabove, I’m going to look at step 2 first to try and figure out how to make it\nfaster. That doesn’t mean we can achieve the 2x speedup, but heck, if we get a\n10% decrease in time, that’s the same time as if we did get a 2x on step 3. So\nit’s at least the place where we should start.</p>\n<p>I chose the above because, well, I think it properly models the proportion of\ntime we’re taking when doing things with LLMs: we spend some time asking it to\ndo something, and we spend a bit more time reviewing its output. But we spend a\n<em>lot</em> of time clicking “accept edit,” and a lot of time allowing Claude to\nexecute tools. This will be our next step forward, as this will increase our\nvelocity when working with the tools significantly. However, like with many\noptimization tasks, this is easier said than done.</p>\n<p>The actual mechanics of improving the speed of this step are simple at first:\nhit <code>shift-tab</code> to auto-accept edits, and “Yes, and don’t ask again for <code><cmd></code>\ncommands” when you think the <code><cmd></code> is safe for Claude to run. By doing this,\nonce you have enough commands allowed, your input for step 2 of our development\nloop can drop to zero. Of course, it takes time for Claude to actually implement\nwhat you’ve asked, so it’s not like our 13 minute process drops to three, but\nstill, this is a major efficiency step.</p>\n<p>But we were actively monitoring Claude for a reason. Claude will sometimes do\nincorrect things, and we need to correct it. At some point, Claude will say\n“Hey I’ve finished doing what you asked of me!” and it doesn’t matter how fast\nit does step 2 if we get to step 3 and it’s just incorrect, and we need to\nthrow everything out and try again.</p>\n<p>So, how do we get Claude to guide itself in the right direction?</p>\n<h2 id=\"lets-start-at-the-end\">Let’s start at the end</h2>\n<p>A useful technique for figuring out what you should do is to consider the\nending: where do we want to go? That will inform what we need to do to get\nthere. Well, the ending of step 2 is knowing when to transition to step 3. And\nthat transition is gated by “does the software do what it is supposed to do?”\nThat’s a huge question! But in practice, we can do what we always do: start\nsimple, and iterate from there. Right now, the transition from step 2 to step 3\nis left up to Claude. Claude will use its own judgement to decide when it thinks\nthat the software is working. And it’ll be right. But why leave that up to\nchance?</p>\n<p>I expect that some of you are thinking that maybe I’m belaboring this point.\n“Why not just skip to <code>cargo test</code>? That’s the idea, right? We need tests.” Well\non some level: yes. But on another level, no. I’m trying to teach you how to\nthink here, not give you the answer. Because it might be broader than just “run\nthe tests.” Maybe you are working on a project where the tests aren’t very good\nyet. Maybe you’re working on a behavior that’s hard to automatically test.\nMaybe the test suite takes a very long time, and so isn’t appropriate to be\nrunning over and over and over.</p>\n<p>Remember our plan from the last post? Where Claude finished the plan with this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span> Verification</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> 1. Run cargo build to ensure it compiles</span></span>\n<span class=\"line\"><span> 2. Run `cargo run -- version` to verify it prints \"task 0.1.0\"</span></span>\n<span class=\"line\"><span> 3. Run `cargo run -- --help` to verify help output works</span></span></code></pre>\n<p>These aren’t “tests” in the traditional sense of a test suite, but they <em>are</em>\nobjective measures that Claude can invoke itself to understand if it’s finished\nthe task. Claude could run <code>cargo run -- version</code> after every file edit if it\nwanted to, and as soon as it sees <code>task 0.1.0</code>, it knows that it’s finished.\nYou don’t <em>need</em> a comprehensive test suite. You just need <em>some</em> sort of way\nfor Claude to detect if it’s done in some sort of objective fashion.</p>\n<p>Of course, we can do better.</p>\n<h2 id=\"improving-from-there\">Improving from there</h2>\n<p>While giving Claude a way to know if it’s done working is important, there’s a\nsecond thing we need to pay attention to: when Claude isn’t done working, can we\nguide it towards doing the right thing, rather than the wrong thing?</p>\n<p>For example, those of you who are of a similar vintage as myself may remember the\noutput of early compilers. It was often… not very helpful. Imagine that we told\nClaude that it should run <code>make test</code> to know if things are working, and the only\noutput from it was the exit code: 0 if we succeeded, 1 if we failed. That would\naccomplish our objective of letting Claude know when things are done, but it\nwouldn’t help Claude know what went wrong when it returns 1.</p>\n<p>This is one reason why I think Rust works well with LLMs. Take this incorrect Rust\nprogram:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> y</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>The Rust compiler won’t just say “yeah this program is incorrect,” it’ll give you\nthis (as of Rust 1.93.0):</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>error: expected `;`, found `}`</span></span>\n<span class=\"line\"><span> --> src/main.rs:2:10</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>2 | let y</span></span>\n<span class=\"line\"><span> | ^ help: add `;` here</span></span>\n<span class=\"line\"><span>3 | }</span></span>\n<span class=\"line\"><span> | - unexpected token</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>error[E0282]: type annotations needed</span></span>\n<span class=\"line\"><span> --> src/main.rs:2:9</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>2 | let y</span></span>\n<span class=\"line\"><span> | ^</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>help: consider giving `y` an explicit type</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>2 | let y: /* Type */</span></span>\n<span class=\"line\"><span> | ++++++++++++</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>For more information about this error, try `rustc --explain E0282`.</span></span></code></pre>\n<p>The compiler will point out the exact place in the code itself of where there’s\nan issue, and even make suggestions as to how to fix it. This goes beyond just\nsimply saying “it doesn’t work” and instead nudges you to what might fix the\nproblem. Of course, this isn’t perfect, but if it’s helpful more than not,\nthat’s a win.</p>\n<p>Of course, too much verbosity isn’t helpful either. A lot of tooling has\ngotten much more verbose lately. Often times, this is really nice as a human.\nPleasant terminal output is, well… pleasant. But that doesn’t mean that it’s\nalways good or useful. For example, here’s the default output for <code>cargo test</code>:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>running 3 tests</span></span>\n<span class=\"line\"><span>test bar ... ok</span></span>\n<span class=\"line\"><span>test baz ... ok</span></span>\n<span class=\"line\"><span>test foo ... ok</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span></span></code></pre>\n<p>This is not bad output. It’s nice. But it’s also not useful for an LLM. We don’t\nneed to read all of the tests that are passing, we really just want to see some\nsort of minimal output, and then what failed if something failed. In Cargo’s case,\nthat’s <code>cargo test -q</code> for “quiet”:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>running 3 tests</span></span>\n<span class=\"line\"><span>...</span></span>\n<span class=\"line\"><span>test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span></span></code></pre>\n<p>There is no point in giving a ton of verbose input to an LLM that it isn’t even\ngoing to need to use. If you’re feeding a tools’ output to an LLM, you should\nconsider both what the tool does in the failure case, but also the success case.\nMaybe configure things to be a bit simpler for Claude. You’ll save some tokens\nand get better results.</p>\n<p>All of this has various implications for all sorts of things. For example, types\nare a great way to get quick feedback on what you’re doing. A comprehensive test\nsuite that completes quickly is useful for giving feedback to the LLM. But that\nalso doesn’t inherently mean that types must be better or that you need to be doing\nTDD; whatever gives you that underlying principle of “objective feedback for the\nsuccess case and guidance for the failure case” will be golden, no matter what\ntech stack you use.</p>\n<h2 id=\"whats-good-for-the-goose-human-is-good-for-the-gander-llm\">What’s good for the <del>goose</del> human is good for the <del>gander</del> LLM</h2>\n<p>This brings me to something that may be counter-intuitive, but I think is also\ntrue, and worth keeping in the back of your mind: what’s good for Claude is also\nprobably good for humans working on your system. A good test suite was\nconsidered golden before LLMs. That it’s great for them is just a nice\ncoincidence.</p>\n<p>At the end of the day, Claude is not a person, but it tackles programming\nproblems in a similar fashion to how we do: take in the problem, attempt a\nsolution, run the compiler/linter/tests, and then see what feedback it gets,\nthen iterate. That core loop is the same, even if humans can exercise better\njudgement and can have more skill. And so even though I pitched fancy terminal\noutput as an example of how humans and LLMs need different things, that’s really\njust a superficial kind of thing. Good error messages are still critical for\nboth. We’re just better at having terminal spinners not take up space in our\nheads while we’re solving a problem, and can appreciate the aesthetics in a way\nthat Claude does not.</p>\n<p>Incidentally, this is one of the things that makes me hopeful about the future\nof software development under agentic influence. Engineers always complain that\nmanagement doesn’t give us time to do refactorings, to improve the test suite,\nto clean our code. Part of the reason for this is that we often didn’t do a good\njob of pitching how it would actually help accomplish business goals. But even\nif you’re on the fence about AI, and upset that management is all about AI:\nexplain to management that this stuff is a force multiplier for your agents.\nUse the time you’ve saved by doing things the agentic way towards improving\nyour test suite, or your documentation, or whatever else. I think there’s a chance\nthat all of this stuff leads to higher quality codebases than ones filled with\nslop. But it also requires us to make the decisions that will lead is in that\ndirection.</p>\n<h2 id=\"in-conclusion\">In conclusion</h2>\n<p>That’s what I have for you today: consider how you can help Claude evaluate its\nown work. Give it explicit success criteria, and make evaluating that criteria\nas simple and objective as possible.</p>\n<p>In the next post, we’re gonna finally talk about <code>Claude.md</code>. Can you believe\nthat I’ve talked this much about how to use Claude and we haven’t talked about\n<code>Claude.md</code>? There’s good reason for that, as it turns out. We’re going to talk\na bit more about understanding how interacting with LLMs work, and how it can\nhelp us both improve step 1 in our process, but also continue to make step 2\nbetter and better.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mdg6nftkm22k\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"post s-12mzi62\"><svg class=\"logo s-12mzi62\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg> <div class=\"aside s-12mzi62\"><a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" class=\"avatar-wrapper s-12mzi62\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-12mzi62\"/></a> <div class=\"line s-12mzi62\"></div></div> <div class=\"main s-12mzi62\"><div class=\"meta s-12mzi62\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-12mzi62\"><bdi class=\"display-name-wrapper s-12mzi62\"><span class=\"display-name s-12mzi62\">Steve Klabnik</span></bdi> <span class=\"handle s-12mzi62\">@steveklabnik.com</span></a> <span aria-hidden=\"true\" class=\"dot s-12mzi62\">·</span> <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcznckciyk2e\" title=\"January 22, 2026 at 4:48 PM\" class=\"date s-12mzi62\"><time datetime=\"2026-01-22T16:48:55.938Z\">Jan 22</time></a></div> <p class=\"context s-12mzi62\">Replying to <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" dir=\"auto\" class=\"s-12mzi62\">Steve Klabnik</a></p> <p class=\"rich-text is-small s-1lecfnd\">Agentic development basics: <a target=\"_blank\" href=\"https://steveklabnik.com/writing/agentic-development-basics/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/agen...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/agentic-development-basics/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Agentic development basics</p> <p class=\"description s-rtbqd8\">Blog post: Agentic development basics by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div></div></div><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> </div> <p class=\"rich-text is-large s-1lecfnd\">The most important thing when working with LLMs\n\n<a target=\"_blank\" href=\"https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/the-...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/the-most-important-thing-when-working-with-llms/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">The most important thing when working with LLMs</p> <p class=\"description s-rtbqd8\">Blog post: The most important thing when working with LLMs by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2026-01-27T16:31:10.824Z\" class=\"date s-hik11q\">January 27, 2026 at 4:31 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"35 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>35</span></span> <span class=\"stat s-hik11q\" title=\"4 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>4</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mdg6nftkm22k\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/agentic-development-basics/",
"title": "Agentic development basics",
"description": "In my last post, I suggested that you should start using Claude in your software development process via read-only means at first. The idea is just to get used to interacting with the AI, seeing what it can do, and seeing what it struggles with. Once you’ve got a handle on that…",
"url": "https://steveklabnik.com/writing/agentic-development-basics/",
"published": "2026-01-22T00:00:00.000Z",
"updated": "2026-01-22T00:00:00.000Z",
"content": "<p>In my last post, I suggested that you should start using Claude in your software\ndevelopment process via read-only means at first. The idea is just to get used\nto interacting with the AI, seeing what it can do, and seeing what it struggles\nwith.</p>\n<p>Once you’ve got a handle on that part, it’s time to graduate to writing code.\nHowever, I’m going to warn you about this post: I hope that by the end of it,\nyou’re a little frustrated. This is because I don’t think it’s productive to\nskip to the tools and techniques that experienced users use yet. We have to\nwalk before we run. And more importantly, we have to understand how and why we\nrun. That is, I hope that this step will let you start producing code with\nClaude, but it will also show you some of the initial pitfalls when doing so, in\norder to motivate the techniques you’re going to learn about in part 3.</p>\n<p>So with that in mind, let’s begin.</p>\n<h2 id=\"step-0-version-control\">Step 0: version control</h2>\n<p>Okay I lied. Before we actually begin: you are using version control, right? If\nnot, you may want to go learn a bit about it. Version control, like git (or my\nbeloved jj) is pretty critical for software development, but it’s in my opinion\neven <em>more</em> critical for this sort of development. You really want to be able to\nrestore to previous versions of the code, branch off and try things, and recover\nfrom mistakes. If you already use version control systems religiously, you might\nuse this as an excuse to learn even more features of them. I never bothered with\n<code>git worktree</code>s in the past, but I use <code>jj workspace</code>s with agents all the time\nnow.</p>\n<h2 id=\"step-1-mindset-shift-stop-writing-code\">Step 1: Mindset shift: stop writing code</h2>\n<p>Okay, here’s my first bit of advice: commit yourself to not writing code any\nmore. I don’t mean forever, I don’t mean all the time, I mean, while you’re\ntrying to learn agentic software development, on the project that you’re\nlearning it with, just don’t write any code manually.</p>\n<p>This might be a bit controversial! However, I think it is essential. Let me tell\nyou a short story. Many years ago, I was in Brazil. I wanted to try scuba diving\nfor the first time. Seemed like a good opportunity. Now, I don’t remember the\nexact setup, but I do remember the hardest part for me. Our instructor\ntold us to put the mask on, and then lean forward and put our faces in the water\nand breathe through the regulator. I simply could not do it. I got too in my\nhead, it was like those “you are now breathing manually” memes. I forget if it\nwas my idea or the instructor’s idea, but what happened in practice: I just\njumped in. My brain <em>very quickly</em> went from “but how do I do this properly” to\n“oh God, if you don’t figure this out right the fuck now you’re gonna fuckin die\nidiot” and that’s exactly what I needed to do it. A few seconds later, I was\nbreathing just fine. I just needed the shock to my system, I needed to commit.\nAnd I figured it out.</p>\n<p>Now, I’m turning 40 and this happened long ago, and it’s reached more of a\npersonal myth status in my head, so maybe I got the details wrong, maybe this is\nhorribly irresponsible and someone who knows about diving can tell me if that\nexperience was wrong, but the point has always stuck with me: sometimes, you\njust gotta go for it.</p>\n<p>This dovetails into another part I snuck into there: “on the project that you’re\nlearning it with.” I really think that you should undergo a new project for this\nendeavor. There’s a few reasons for this:</p>\n<ol>\n<li>You can be less precious about the code. This isn’t messing up one of your\nprojects, this is a throwaway scratch thing that doesn’t matter.</li>\n<li>“AI does better on greenfield projects” is not exactly true, but there’s\nenough truth to it that I think you should do a new project. It’s really more\nabout secondary factors than it is actual greenfield vs brownfield\ndevelopment but whatever, doesn’t matter: start new.</li>\n</ol>\n<p>Pick something to get started with, and create a new repo. I suggest something\nyou’ve implemented before, or maybe something you know how to do but have never\nbothered to take the time to actually build. A small CLI tool might be a good\nidea. Doesn’t super matter what it is.</p>\n<p>For the purposes of this example, I’m going to build a task tracking CLI\napplication. Because there aren’t enough of those in the world.</p>\n<h2 id=\"step-2-your-first-few-steps-with-claude\">Step 2: Your first few steps with Claude</h2>\n<p>I recommend making a new fresh directory, and initializing the project of your\nchoice. I’m using Rust, of course, so I’ll <code>cargo new tasks && cd tasks</code>. You\ncan make Claude do it, but I don’t think that starting from an initialized\nproject is a bad idea either. I’m more likely to go the <code>cargo new</code> route if I\nknow I’m building something small, and more of the “make Claude do it” route if\nI’m doing like, a web app with a frontend, backend, and several services. Anyway\npoint is: get your project to exist, and then just run Claude Code. I guess you\nshould <a href=\"https://code.claude.com/docs/en/quickstart\">install it first</a>, but anyway, you can just</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"plaintext\"><code><span class=\"line\"><span>$ claude</span></span></code></pre>\n<p>to get started. At the time of writing, Claude will ask you if you trust the\nfiles in this folder, you want to say yes, because you just created it. You’ll\nalso get some screens asking if you want to do light or dark mode, to log in\nwith your Anthropic account, stuff like that.</p>\n<p>And then you’ll be in.</p>\n<p>Claude will ask you to run <code>/init</code> to create a CLAUDE.md, but we’re not gonna do\nthat at the start. We need to talk about even more basic things than that first.</p>\n<p>You’ll be at a prompt, and it’ll even suggest something for you to get started\nwith. Mine says “Try “fix typecheck errors\"\" at the moment. We’re gonna try to get\nClaude to modify a few lines of our program. The <code>cargo new</code> in Rust produces\nthis program:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"Hello, world!\"</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>so I’ll ask Claude this:</p>\n<blockquote>\n<p>Hi claude! right now my program prints “Hello, world”, can we have it print\n“Goodbye, world” instead?</p>\n</blockquote>\n<p>Claude does this for me:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>● I'll make that change for you.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>● Read(src/main.rs)</span></span>\n<span class=\"line\"><span> ⎿ Read 4 lines</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>● Update(src/main.rs)</span></span></code></pre>\n<p>And then asks this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span> Edit file src/main.rs</span></span>\n<span class=\"line\"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>\n<span class=\"line\"><span> 1 fn main() {</span></span>\n<span class=\"line\"><span> 2 - println!(\"Hello, world!\");</span></span>\n<span class=\"line\"><span> 2 + println!(\"Goodbye, world!\");</span></span>\n<span class=\"line\"><span> 3 }</span></span>\n<span class=\"line\"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>\n<span class=\"line\"><span> Do you want to make this edit to main.rs?</span></span>\n<span class=\"line\"><span> ❯ 1. Yes</span></span>\n<span class=\"line\"><span> 2. Yes, allow all edits during this session (shift+tab)</span></span>\n<span class=\"line\"><span> 3. No</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Esc to cancel · Tab to add additional instructions</span></span></code></pre>\n<p>Claude wants to edit a file, and so by default, it has to ask us permission to\ndo so. This is a terrible place to end up, but a great place to get started! We\nwant to use these prompts at first to understand what Claude is doing, and sort\nof “code review” it as we go. More on that in a bit. Anyway, this is why you\nshould answer “Yes” to this question, and not “Yes, allow all edits during this\nsession.” We want to keep reviewing the code for now. You want to be paying\nclose attention to what Claude is doing, so you can build up some intuition\nabout it.</p>\n<p>Before clicking yes, I want to talk about what Claude did in my case here. Note\nthat my prompt said</p>\n<blockquote>\n<p>right now my program prints “Hello, world”, can we have it print “Goodbye,\nworld”</p>\n</blockquote>\n<p>Astute observers will have noticed that it actually says <code>Hello, world!</code> and not\n<code>Hello, world</code>. We also asked it to have it say <code>Goodbye, world</code> and it is\nshowing a diff that will make it say <code>Goodbye, world!</code>. This is a tiny\ndifferent, but it is also important to understand: Claude is going to try and\nfigure out what you mean and do that. This is both the source of these tools’\npower and also the very thing that makes it hard to trust them. In this case,\nClaude was right, I didn’t type the exact string when describing the current\nbehavior, and I didn’t mean to remove the <code>!</code>.</p>\n<p>In the previous post, I said that you shouldn’t be mean to Claude. I think it\nmakes the LLM perform worse. So now it’s time to talk about your own reaction to\nthe above: did you go “yeah Claude fucked up it didn’t do exactly what I asked?”\nor did you go “yeah Claude did exactly what I asked?” I think it’s important to\ntry and let go of preconceived notions here, especially if your reaction here\nwas negative. I know this is kind of woo, just like “be nice to Claude,” but you\nhave to approach this as “this is a technology that works a little differently\nthan I’m used to, and that’s why I’m learning how to meet it on its own terms”\nrather than “it didn’t work the way I expected it to, so it is wrong.” A non-woo\nway of putting it is this: the right way to approach “it didn’t work” in this\ncontext is “that’s a skill issue on my part, and I’m here to sharpen my skills.”\nYes, there are limits to this technology and it’s not perfect. That’s not the\npoint. You’re not doing that kind of work right now, you’re doing <code>sed</code>.</p>\n<p>Now, I should also say that like, if you don’t want to learn a new tool? 100%\nokay with me. Learned some things about a tool, and didn’t like it? Sure! Some\nof you won’t like agentic development. That’s okay. No worries, thanks for\nreading, have a nice day. I mean that honestly. But for those folks who do want\nto learn this, I’m trying to communicate that I think you’ll have a better time\nlearning it if you try to get into the headspace of “how do I get the results I\nwant” rather than getting upset and giving up when it doesn’t work out.</p>\n<p>Okay, with that out of the way, if you asked a small enough question, Claude\nprobably did the right thing. Let’s accept.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>● Done. Your program will now print \"Goodbye, world!\" instead.</span></span></code></pre>\n<h2 id=\"planning\">Planning</h2>\n<p>This might be a good time to commit & save your progress. You can use <code>!</code> to put\nClaude Code into “bash mode” and run commands, so I just <code>!jj commit -m \"goodbye world\"</code> and I’m good. You can also use another terminal, I just figured I’d let\nyou know. It’s good for short commands.</p>\n<p>Let’s try something bigger. To do that, we’re gonna invoke something called\n“plan mode.” Claude Code has three (shhhh, we don’t talk about the fourth yet)\nmodes. The first one is the “ask to accept edits” mode. But if you hit\n<code>shift+tab</code>, you’ll see <code>⏵⏵ accept edits on (shift+tab to cycle)</code> at the bottom\nleft. We don’t want to automatically accept edits. Hit <code>shift+tab</code> again, and\nyou’ll see this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>⏸ plan mode on (shift+tab to cycle)</span></span></code></pre>\n<p>This is what we want. Plan mode. Plan mode is useful any time you’re doing work\nthat’s on the larger side, or just when you want to think through something\nbefore you begin. In plan mode, Claude cannot modify your files until you accept\nthe plan. With plan mode, you talk to Claude about what you want to do, and you\ncollaborate on a plan together. A nice thing about it is that you can\ncommunicate the things you are sure of, and also ask about the things you’re not\nsure of. So let’s kick off some sort of plan to build the most baby parts of our\napp. In my case, I’m prompting it with this:</p>\n<blockquote>\n<p>hi claude! i want this application to grow into a task tracking app. right\nnow, it’s just hello world. I’d like to set up some command line argument\nparsing infrastructure, with a <code>task version</code> command that prints the version.\ncan we talk about that?</p>\n</blockquote>\n<p>Yes, I almost always type <code>hi claude!</code>, feel free to not. And I always feel like\na “can we talk about that” on the end is nice too. I try to talk to Claude the\nway I’d talk to a co-worker. Obviously this would be too minor of a thing to\nbother to talk to a co-worker about, but like, you know, baby steps. Note that\nwhat I’m asking is basically a slightly more complex “hello world”, just getting\nsome argument parsing up. You want something this sized: you know how it should\nbe done, it should be pretty simple, but it’s not a fancy <code>sed</code> command.</p>\n<p>With plan mode, Claude will end up responding to this by taking a look at your\nproject, considering what you might need, and then coming up with a plan. Here’s\nthe first part of Claude’s answer to me:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>● Sure! This is a great foundation to build on. Since the codebase is minimal right now (just the hello world in src/main.rs), I</span></span>\n<span class=\"line\"><span> can skip extensive exploration and go straight to planning.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> For CLI argument parsing in Rust, the standard choice is clap - it's the most popular library with excellent subcommand</span></span>\n<span class=\"line\"><span> support, derive macros for clean code, and built-in version/help handling. This will scale well as you add more commands like</span></span>\n<span class=\"line\"><span> task add, task list, etc.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Let me write up a plan for you.</span></span></code></pre>\n<p>It’ll then come up with a plan, and it usually write it out to a file somewhere:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>● Updated plan</span></span>\n<span class=\"line\"><span> ⎿ Wrote 56 lines to ../../.claude/plans/shiny-dancing-cray.md</span></span>\n<span class=\"line\"><span> # Plan: CLI Argument Parsing Infrastructure</span></span>\n<span class=\"line\"><span> ## Goal</span></span>\n<span class=\"line\"><span> Set up command line argument parsing for the task tracking app with a `task version` subcommand.</span></span>\n<span class=\"line\"><span> ## Approach</span></span>\n<span class=\"line\"><span> Use **clap** with derive macros - the standard Rust CLI library.</span></span>\n<span class=\"line\"><span> ## Changes</span></span>\n<span class=\"line\"><span> … +46 lines (ctrl+o to expand)</span></span></code></pre>\n<p>You can go read the file if you want to, but it’s not needed. Claude is going to\neventually present the plan to you directly, and you’ll review it before moving on.\nClaude will also probably ask you a question or maybe even a couple, depending.\nThere’s a neat little TUI for responding to its questions, it can even handle\nmultiple questions at once:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>● I've drafted a plan using clap with derive macros. Before finalizing, I have one question:</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>────────────────────────────────────────────────────────────────────────────────────────────</span></span>\n<span class=\"line\"><span> ☐ CLI Library</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>Which CLI parsing library would you prefer?</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>❯ 1. clap (Recommended)</span></span>\n<span class=\"line\"><span> Most popular, feature-rich, excellent subcommand support, built-in help/version</span></span>\n<span class=\"line\"><span> 2. argh</span></span>\n<span class=\"line\"><span> Simpler and lighter, derive-based, from Google</span></span>\n<span class=\"line\"><span> 3. Manual parsing</span></span>\n<span class=\"line\"><span> Use std::env::args directly, no dependencies, more boilerplate</span></span>\n<span class=\"line\"><span> 4. Type something.</span></span>\n<span class=\"line\"><span>────────────────────────────────────────────────────────────────────────────────────────────</span></span>\n<span class=\"line\"><span> Chat about this</span></span>\n<span class=\"line\"><span> Skip interview and plan immediately</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>Enter to select · ↑/↓ to navigate · Esc to cancel</span></span></code></pre>\n<p>For those of you that don’t write Rust, this is a pretty good response! Clap is\nthe default choice in the ecosystem, arg is a decent option too, and doing it\nyourself is always possible. I’m going to choose clap, it’s great. If you’re not\nsure about the question, you can arrow down to “Chat about this” and discuss it\nmore.</p>\n<p>Here’s why you don’t need to read the file: Claude will pitch you on its plan:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>● User answered Claude's questions:</span></span>\n<span class=\"line\"><span> ⎿ · Which CLI parsing library would you prefer? → clap (Recommended)</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>● Clap it is. The plan is ready - it adds clap with derive macros and sets up the subcommand structure with task version as the</span></span>\n<span class=\"line\"><span> first command. This gives you a solid foundation to add more commands later.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span></span>\n<span class=\"line\"><span> Ready to code?</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Here is Claude's plan:</span></span>\n<span class=\"line\"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>\n<span class=\"line\"><span> Plan: CLI Argument Parsing Infrastructure</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Goal</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Set up command line argument parsing for the task tracking app with a task version subcommand.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Approach</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Use clap with derive macros - the standard Rust CLI library.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Changes</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> 1. Update Cargo.toml</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Add clap dependency with derive feature:</span></span>\n<span class=\"line\"><span> [dependencies]</span></span>\n<span class=\"line\"><span> clap = { version = \"4\", features = [\"derive\"] }</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> 2. Update src/main.rs</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> - Define a Cli struct with clap derive macros</span></span>\n<span class=\"line\"><span> - Define a Commands enum for subcommands (starting with Version)</span></span>\n<span class=\"line\"><span> - Implement the version subcommand to print the version from Cargo.toml</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Structure:</span></span>\n<span class=\"line\"><span> use clap::{Parser, Subcommand};</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> #[derive(Parser)]</span></span>\n<span class=\"line\"><span> #[command(name = \"task\")]</span></span>\n<span class=\"line\"><span> #[command(about = \"A task tracking application\")]</span></span>\n<span class=\"line\"><span> struct Cli {</span></span>\n<span class=\"line\"><span> #[command(subcommand)]</span></span>\n<span class=\"line\"><span> command: Commands,</span></span>\n<span class=\"line\"><span> }</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> #[derive(Subcommand)]</span></span>\n<span class=\"line\"><span> enum Commands {</span></span>\n<span class=\"line\"><span> /// Print version information</span></span>\n<span class=\"line\"><span> Version,</span></span>\n<span class=\"line\"><span> }</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> fn main() {</span></span>\n<span class=\"line\"><span> let cli = Cli::parse();</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> match cli.command {</span></span>\n<span class=\"line\"><span> Commands::Version => {</span></span>\n<span class=\"line\"><span> println!(\"task {}\", env!(\"CARGO_PKG_VERSION\"));</span></span>\n<span class=\"line\"><span> }</span></span>\n<span class=\"line\"><span> }</span></span>\n<span class=\"line\"><span> }</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Verification</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> 1. Run cargo build to ensure it compiles</span></span>\n<span class=\"line\"><span> 2. Run ./target/debug/tasks version to verify it prints \"task 0.1.0\"</span></span>\n<span class=\"line\"><span> 3. Run ./target/debug/tasks --help to verify help output works</span></span>\n<span class=\"line\"><span>╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Would you like to proceed?</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> ❯ 1. Yes, clear context and auto-accept edits (shift+tab)</span></span>\n<span class=\"line\"><span> 2. Yes, auto-accept edits</span></span>\n<span class=\"line\"><span> 3. Yes, manually approve edits</span></span>\n<span class=\"line\"><span> 4. Type here to tell Claude what to change</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> ctrl-g to edit in VS Code · ~/.claude/plans/shiny-dancing-cray.md</span></span></code></pre>\n<p>This is pretty good! Now, if you like this plan, you can select #3. Remember,\nwe’re not auto accepting just yet! Don’t worry about the difference between 1\nand 2 now, we’ll talk about it someday. But, I actually want Claude to tweak\nthis plan, I wouldn’t run <code>./target/debug/tasks version</code>, I would do <code>cargo run -- version</code>. So I’m going to go down to four and type literally that to Claude:</p>\n<blockquote>\n<p>I wouldn’t run <code>./target/debug/tasks version</code>, I would do <code>cargo run -- version</code>.</p>\n</blockquote>\n<p>And Claude replies:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>● Good point - let me update the verification section.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>● Updated plan</span></span>\n<span class=\"line\"><span> ⎿ /plan to preview</span></span>\n<span class=\"line\"><span> 51</span></span>\n<span class=\"line\"><span> 52 ## Verification</span></span>\n<span class=\"line\"><span> 53 1. Run `cargo build` to ensure it compiles</span></span>\n<span class=\"line\"><span> 54 -2. Run `./target/debug/tasks version` to verify it prints \"task 0.1.0\"</span></span>\n<span class=\"line\"><span> 55 -3. Run `./target/debug/tasks --help` to verify help output works</span></span>\n<span class=\"line\"><span> 54 +2. Run `cargo run -- version` to verify it prints \"task 0.1.0\"</span></span>\n<span class=\"line\"><span> 55 +3. Run `cargo run -- --help` to verify help output works</span></span></code></pre>\n<p>and then presents the menu again. See, this is where the leverage of “Claude\nfigures out what I mean” can be helpful: I only told it about <code>version</code>, but it\nalso changed to <code>cargo run -- --help</code> as well. However, there’s a drawback too:\nwe didn’t tell Claude we wanted to have help output! However, this is also a\npositive: Claude considered help output to be so basic that it’s suggesting it\nfor our plan. It’s up to you to decide if this is an overreach on Claude’s part.\nIn my case, I’m okay with it because it’s so nearly automatic with Clap and it’s\nsomething I certainly want in my tool, so I’m going to accept this plan.</p>\n<p>Iterate with Claude until you’re happy with the plan, and then do the same.</p>\n<p>I’m not going to paste all of the diffs here, but for me, Claude then went and\ndid the plan: it added the dependency to <code>Cargo.toml</code>, it added the needed code\nto <code>src/main.rs</code>, it ran <code>cargo build</code> to try and do the build. Oh yeah, here’s\na menu we haven’t seen yet:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span> Bash command</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> cargo build 2>&1</span></span>\n<span class=\"line\"><span> Build the Rust project</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Do you want to proceed?</span></span>\n<span class=\"line\"><span> ❯ 1. Yes</span></span>\n<span class=\"line\"><span> 2. Yes, and don't ask again for cargo build commands in /home/stat/tmp/tasks</span></span>\n<span class=\"line\"><span> 3. No</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Esc to cancel · Tab to add additional instructions</span></span></code></pre>\n<p>This is a more interesting question than the auto-edit thing. Claude won’t run\ncommands without you signing off on them. If you’re okay with letting Claude run\nthis command every time without asking, you can choose 2, and if you want to\nconfirm every time, you can type 1. Completely up to you.</p>\n<p>Now it ran <code>cargo run -- version</code>, and <code>cargo run -- --help</code>. Everything looked\ngood, so I see:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>● Done. The CLI infrastructure is set up:</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> - cargo run -- version outputs task 0.1.0</span></span>\n<span class=\"line\"><span> - cargo run -- --help shows the help with available commands</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> You now have a foundation to add more subcommands to the Commands enum as you build out the task tracking features.</span></span></code></pre>\n<p>And that’s it, we’ve built our first feature! Yeah, it’s pretty small, and in\nthis case, we probably could have copy/pasted the documentation, but again,\nthat’s not the point right now: we’re just trying to take very small steps\nforward to get used to working with the tool. We <em>want</em> it to be something\nthat’s quick for us to verify. We are spending more time here than we would if\nwe did it by hand, because that time isn’t wasted: it’s time learning. As we\nramp up the complexity of what we can accomplish, we’ll start seeing speed\ngains. But we’re deliberately going slow and doing little right now.</p>\n<h2 id=\"stretch-your-legs\">Stretch your legs</h2>\n<p>From here, that’s exactly what I’d like you to do: figure out where the limits\nare. Try something slightly larger, slightly harder. Try to do stuff with just a\nprompt, and then throw that commit away and try it again with plan mode. See\nwhat stuff you need plan mode for, and what you can get away with with just a\nsimple prompt.</p>\n<p>I’ll leave you with an example of a prompt I might try next: asking Cladue for\ntheir opinion on what you should do. The next thing I’d do in this project is to\nswitch back into planning mode and ask this:</p>\n<blockquote>\n<p>what do you think is the best architecture for this tracking app? we haven’t\ndiscussed any real features, design, or requirements. this is mostly a little\nsample application to practice with, and so we might not need a real database,\nwe might get away with a simple file or files to track todos. but maybe\nsomething like sqlite would still be appropriate. if we wanted to implement a\nnext step for this app, what do you think it should be?</p>\n</blockquote>\n<p>Here’s what Claude suggested:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>● Great question - this is the kind of design discussion that's valuable before diving into implementation. Let me share my</span></span>\n<span class=\"line\"><span> thoughts.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Storage: File vs SQLite</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> For a practice app, I'd recommend starting with a simple JSON file:</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> - No additional dependencies - just serde + serde_json</span></span>\n<span class=\"line\"><span> - Human-readable - you can inspect/edit ~/.tasks.json directly</span></span>\n<span class=\"line\"><span> - Focus on app logic - learn clap, error handling, and Rust patterns without database complexity</span></span>\n<span class=\"line\"><span> - Easy to migrate later - if you outgrow it, moving to SQLite is a good exercise in itself</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> SQLite is great but adds cognitive load (SQL, migrations, connection handling). For a practice app, that complexity might</span></span>\n<span class=\"line\"><span> distract from the core learning.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Recommended Next Step</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> The natural progression is the core CRUD operations. I'd suggest implementing task add next:</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> task add \"Buy groceries\"</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> This introduces:</span></span>\n<span class=\"line\"><span> - Taking arguments in subcommands (clap)</span></span>\n<span class=\"line\"><span> - Reading/writing a data file (serde)</span></span>\n<span class=\"line\"><span> - Defining your Task struct (what fields? id, description, created_at, completed?)</span></span>\n<span class=\"line\"><span> - Error handling patterns</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> After that: task list, then task done <id>, then task remove <id>.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span></span>\n<span class=\"line\"><span> ☐ Next step</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>What would you like to tackle next?</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>❯ 1. task add (Recommended)</span></span>\n<span class=\"line\"><span> Add a task with a description, stored in a JSON file</span></span>\n<span class=\"line\"><span> 2. task list first</span></span>\n<span class=\"line\"><span> Set up storage and list (empty at first), then add</span></span>\n<span class=\"line\"><span> 3. Use SQLite instead</span></span>\n<span class=\"line\"><span> Start with SQLite from the beginning for the learning experience</span></span>\n<span class=\"line\"><span> 4. Type something.</span></span>\n<span class=\"line\"><span>──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span></span>\n<span class=\"line\"><span> Chat about this</span></span>\n<span class=\"line\"><span> Skip interview and plan immediately</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>Enter to select · ↑/↓ to navigate · Esc to cancel</span></span></code></pre>\n<p>This plan is pretty solid. But again, it’s a demo app. The important part is,\nyou can always throw this away if you don’t like it. So try some things. Give\nClaude some opinions and see how they react. Try small features, try something\nlarger. Play around. But push it until Claude fails.</p>\n<h2 id=\"what-to-do-when-claude-fails\">What to do when Claude fails</h2>\n<p>At some point, you will run into problems. Maybe you already have! What you\nshould do depends on the failure mode.</p>\n<p>The first failure you’re gonna run into is “I don’t like the code!” Maybe Claude\njust does a bad job. You have two options: the first is to just tell Claude to\nfix it. Claude made a mess, Claude can clean it up. In more extreme cases, you\nmay want to just simply <code>jj abandon</code> or <code>git reset --hard</code> and start again.\nHonestly, the second approach is better in a lot of cases, but I’m not going to\nalways recommend it just yet. The reason why it is is that it gives you some\ntime to reflect on why Claude did a bad job. But we’re gonna talk about that as\nthe next post in this series! So for now, stick to the ‘worse’ option: just tell\nClaude to fix problems you find.</p>\n<p>The second kind of failure is one where Claude just really struggles to get\nthings right. It looks in the wrong places in your codebase, it tries to find a\nbug and can’t figure it out, it misreads output and that leads it astray, etc.\nThis kind of failure is harder to fix with the tools you have available to you\nright now. What matters is taking note of them, so that you can email them to\nme, haha. I mean, feel free to do that, and I can incorporate specific\nsuggestions into the next posts, but also, just being able to reflect on what\nClaude struggles with is a good idea generally. You’ll be able to fix them\neventually, so knowing what you need to improve matters.</p>\n<h2 id=\"what-is-compaction\">What is “compaction”?</h2>\n<p>If Claude works long enough, you’ll see something about “compaction.” We haven’t\ndiscussed things at a deep enough level to really understand this yet, so don’t\nworry about it! You may want to note one thing though: Claude tends to do worse\nafter compaction, in my opinion. So one way to think about this is, “If I see\ncompaction, I’ve tried to accomplish too large a task.” Reflect on if you could\nhave broken this up into something smaller.</p>\n<p>We’ll talk about this more in the next post.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>So that’s it! Let Claude write some code, in a project you don’t care about. Try\nbigger and harder things until you’re a bit frustrated with its failures. You\nwill hit the limits, because you’re not doing any of the intermediate techniques\nto help Claude do a good job. But my hope is, by running into these issues,\nyou’ll understand the motivation for those techniques, and will be able to\nbetter apply them in the future.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mcznckciyk2e\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"post s-12mzi62\"><svg class=\"logo s-12mzi62\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg> <div class=\"aside s-12mzi62\"><a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" class=\"avatar-wrapper s-12mzi62\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-12mzi62\"/></a> <div class=\"line s-12mzi62\"></div></div> <div class=\"main s-12mzi62\"><div class=\"meta s-12mzi62\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-12mzi62\"><bdi class=\"display-name-wrapper s-12mzi62\"><span class=\"display-name s-12mzi62\">Steve Klabnik</span></bdi> <span class=\"handle s-12mzi62\">@steveklabnik.com</span></a> <span aria-hidden=\"true\" class=\"dot s-12mzi62\">·</span> <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mbu3gbqrwk2y\" title=\"January 7, 2026 at 6:20 PM\" class=\"date s-12mzi62\"><time datetime=\"2026-01-07T18:20:23.515Z\">Jan 7</time></a></div> <p class=\"rich-text is-small s-1lecfnd\">Getting started with Claude for software development: <a target=\"_blank\" href=\"https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/gett...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Getting started with Claude for software development</p> <p class=\"description s-rtbqd8\">Blog post: Getting started with Claude for software development by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div></div></div><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> </div> <p class=\"rich-text is-large s-1lecfnd\">Agentic development basics: <a target=\"_blank\" href=\"https://steveklabnik.com/writing/agentic-development-basics/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/agen...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/agentic-development-basics/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Agentic development basics</p> <p class=\"description s-rtbqd8\">Blog post: Agentic development basics by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2026-01-22T16:48:55.938Z\" class=\"date s-hik11q\">January 22, 2026 at 4:48 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"38 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>38</span></span> <span class=\"stat s-hik11q\" title=\"1 repost\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>1</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcznckciyk2e\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 6 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/how-to-think-about-gas-town/",
"title": "How to think about Gas Town",
"description": "I want to continue with my series on how you can use Claude Code for software development , but I have at least two posts I need to write first. This post is one of those two. Thanks for all of the kind words you all have said about the first post in the series, I’ll absolutely…",
"url": "https://steveklabnik.com/writing/how-to-think-about-gas-town/",
"published": "2026-01-15T00:00:00.000Z",
"updated": "2026-01-15T00:00:00.000Z",
"content": "<p>I want to continue with my <a href=\"/series/getting-started-with-claude-code\">series on how you can use Claude Code for software\ndevelopment</a>, but I have at least two posts I need to write\nfirst. This post is one of those two. Thanks for all of the kind words you all\nhave said about the first post in the series, I’ll absolutely be continuing it,\nhopefully next week.</p>\n<p>With that out of the way… let’s talk about Gas Town. If you’re not familiar,\nit’s a project from Steve Yegge that has a lot of people having a lot of\nfeelings. Steve launched Gas Town just under two weeks ago, and in general, his\nposts about it have brought out a reaction that has varied from “what the\nfuck?!?” to “I don’t get it.” I’ll link Bryan here, not to pick on him, but sort\nof the opposite: I think this is a very normal response to all of this, and\nespecially from him:</p>\n<bluesky-post src=\"at://did:plc:ucct3fwwarzd7rety6nzq4cc/app.bsky.feed.post/3mcft3omsa226\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:ucct3fwwarzd7rety6nzq4cc/bafkreibajqtfkfkb7tkctsl42q747tekfc4yuek5dhjbe7plc24c7b3rti\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Bryan Cantrill</span></bdi> <span class=\"handle s-hik11q\">@bcantrill.bsky.social</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">WTAF\n<a target=\"_blank\" href=\"https://steve-yegge.medium.com/gas-town-emergency-user-manual-cf0e4556d74b\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steve-yegge.medium.com/gas-town-eme...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steve-yegge.medium.com/gas-town-emergency-user-manual-cf0e4556d74b\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:ucct3fwwarzd7rety6nzq4cc/bafkreick2ibnbvizxsiafszp6qkaf3gegryrxxfhu2qweaa2ddghl74zwa\" alt=\"\" class=\"thumbnail s-rtbqd8\"/> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Gas Town Emergency User Manual</p> <p class=\"description s-rtbqd8\">It’s been a busy 12 days since I launched Gas Town. ️I’ve merged over 100 PRs from nearly 50 contributors, adding 44k lines of code that no…</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steve-yegge.medium.com</span></div></div></a></div> <time datetime=\"2026-01-14T19:39:13.273Z\" class=\"date s-hik11q\">January 14, 2026 at 7:39 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"148 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>148</span></span> <span class=\"stat s-hik11q\" title=\"32 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>32</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc/post/3mcft3omsa226\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 30 replies on Bluesky</span></a></div></div></div> </bluesky-post>\n<p>However, I’m also not exactly a normal person:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mcfylftvw22s\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"post s-12mzi62\"><svg class=\"logo s-12mzi62\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg> <div class=\"aside s-12mzi62\"><a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" class=\"avatar-wrapper s-12mzi62\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-12mzi62\"/></a> <div class=\"line s-12mzi62\"></div></div> <div class=\"main s-12mzi62\"><div class=\"meta s-12mzi62\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-12mzi62\"><bdi class=\"display-name-wrapper s-12mzi62\"><span class=\"display-name s-12mzi62\">Steve Klabnik</span></bdi> <span class=\"handle s-12mzi62\">@steveklabnik.com</span></a> <span aria-hidden=\"true\" class=\"dot s-12mzi62\">·</span> <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcfyeyndzs2s\" title=\"January 14, 2026 at 9:13 PM\" class=\"date s-12mzi62\"><time datetime=\"2026-01-14T21:13:54.475Z\">Jan 14</time></a></div> <p class=\"rich-text is-small s-1lecfnd\">normal people: gas town is insane and yegge is on drugs\n\nme, problematic french philosophy enjoyer: i dunno this seems not that weird at all\n\nalmost going to grad school broke me a little bit, i think</p> </div></div><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> </div> <p class=\"rich-text is-large s-1lecfnd\">\"You can use Polecats without the Refinery\" is too out there for you?</p> <div class=\"embeds s-azdpbr\"><div class=\"image-embed is-bordered is-aligned s-t3k6fc\"><div class=\"single-item tl tr bl br is-standalone s-t3k6fc\" style=\"aspect-ratio: 500/522\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreiakmz36gmfjffm3scdr4s3mx5k63d5xjbplfsgi4bbmyjmnki6t4q\" alt=\"you are like a little baby, watch this\" class=\"image s-t3k6fc\"/> <div class=\"placeholder s-t3k6fc\"></div></div></div></div> <time datetime=\"2026-01-14T21:17:29.649Z\" class=\"date s-hik11q\">January 14, 2026 at 9:17 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"32 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>32</span></span> <span class=\"stat s-hik11q\" title=\"1 repost\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>1</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcfylftvw22s\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 3 replies on Bluesky</span></a></div></div></div> </bluesky-post>\n<p>So, today I’d like to talk about Gas Town, and what I think Yegge is doing.</p>\n<h2 id=\"what-is-gas-town\">What is Gas Town?</h2>\n<p>Gas town is two things simultaneously: something that’s kind of boring and\nobvious, and something that’s opaque and confusing, illegible almost.</p>\n<p>If you’ve found yourself saying “what?!?” when reading about Gas Town, this post\nis for you.</p>\n<p>I want to start with the boring and obvious, and move into the opaque and\nillegible.</p>\n<h2 id=\"gas-town-is-inevitable\">Gas Town is inevitable</h2>\n<p>Okay, so I almost went with “Gas Town is Inevitable” as the title of this post,\nbut I think that’s a bit too provocative, and not quite accurate. I do think\n<em>something like</em> Gas Town was going to be tried by someone, sometime. That’s the\ninevitable part. This is because Gas Town isn’t really that novel, actually.</p>\n<p>Here’s what Gas Town is: you have a workspace. In that workspace, you have\nprojects. Each project has a bug tracker, used like normal: feature requests and\nbug reports. Gas Town is software that allows you to instruct AI agents to\nautonomously knock out those bugs.</p>\n<p>That’s it.</p>\n<p>This isn’t a fundamentally novel idea. The novel part comes in that it’s agents\ndoing the work, not humans.</p>\n<p>This is why I describe it as “inevitable,” it’s an idea that pretty logically\nfollows from the idea of “a software agent can successfully complete programming\ntasks.” Humans find a big list of tasks to be a useful way to organize work, so\nthere’s no reason to believe that agents would not. So you follow this idea\nwherever it takes you. That’s the experimental part of Gas Town: does this work?\nCan it work? We don’t know! Steve is trying.</p>\n<p>One level of abstraction deeper, Steve is using some pretty tried and true ideas\nhere from Erlang. There’s supervisor trees, mailboxes, stuff like that. This is\na pretty solid base to work from! Erlang is legendary in these spaces for a reason.</p>\n<p>Anyway yeah. That’s really just it. Unless you’re particularly interested in\nthis topic or question, that’s really all you need to know about Gas Town to\nunderstand it.</p>\n<p>So why all of the weird terminology?</p>\n<h2 id=\"opacity-and-community-building\">Opacity and community building</h2>\n<p>AI is weird. The tools are very different than we’re used to. If 2025 was any\nmeasure, 2026 is going to be a very interesting year for this profession. I know\nI’m personally re-thinking a lot of sacred cows that I’ve worshipped for my\nwhole career. I don’t think all of them are bad, but I do think that things are\nweird enough that it’s worth trying to figure things out from first principles\nagain.</p>\n<p>And that’s where I think Steve is at too.</p>\n<p>And I’ll also be honest, I’ve been slowly working myself into thinking about all\nof this stuff too. I even have my own little project I started this week,\n<a href=\"https://github.com/steveklabnik/docket\">docket</a>, which is sort of one step down a road that Gas Town is many\nmiles down already. I wasn’t going to talk about Docket for a while, if ever,\nbut I think that maybe if Gas Town is too much, two thousand lines of Rust and a\nlittle CLI may make more sense to you.</p>\n<p>But that’s sort of the problem here: while Steve and I are very different,\nthere’s one way in which we’re the same: people read our blogs. I know that\nprojects I do get attention, heck, <a href=\"https://rue-lang.dev/\">Rue</a> is not really ready for public\nconsumption, really. But because people find the stuff I mess around with to be\ninteresting, it got a lot of attention, far disproportionate for what it is. You\ncan put strings into an array, but you can’t even get them out yet! It’s just\ntoo early!</p>\n<p>So, what do you do if you:</p>\n<ol>\n<li>Know your projects get attention.</li>\n<li>You also want like-minded people to come on this journey with you.</li>\n<li>You don’t want to waste time explaining things to people who don’t get it.</li>\n</ol>\n<p>You reach for a tool that many people have used over the years: you get weird\nwith it.</p>\n<p>There’s many examples of this happening in art and political movements. A pretty\nfamous one you might have heard of is <a href=\"https://en.wikipedia.org/wiki/Surrealism\">Surrealism</a>. One of the core\nideas of surrealism is that they’re trying to unlock the power of the\nsubconscious mind, bypassing the conscious to (hopefully) reveal deeper, more\nprofound truths.</p>\n<p>One of my favorite surrealist works is <a href=\"https://en.wikipedia.org/wiki/The_Treachery_of_Images\">“La Trahison des images”</a> by\nRené Magritte. It is a painting of a pipe, with some words below: “Ceci n’est\npas une pipe” or “This is not a pipe.” At first, most people’s reaction to the\nwork is “uhhhh but that is a pipe?” But what the work is asking you to do is\nthink a little bit harder: this isn’t a pipe. It’s a painting of a pipe. That we\ntend to conflate the image of something with the thing is a bit weird, if you\nthink about it a bit more. Why do we do that, anyway? What is the connection\nbetween language, reality, images, and thoughts?</p>\n<p>The painting was made in 1929, so these ideas are a bit more mainstream by now,\nso you may not find this particular line of inquiry to be illuminating, but you\nknow, that’s what happens 97 years later.</p>\n<p>Anyway. So if you’re someone who is going to attract attention, and want to\nengage with a subset of those people, but also push away others, you could do\nworse than trying to go surreal. It’s something that would be helpful both for\nchallenging assumptions, as well as for finding fellow travelers while repelling\nthose who aren’t interested in the same goals.</p>\n<p>Maybe he is on metaphorical drugs. But that’s deliberate, is all I’m saying. And\nmaybe drugs aren’t your thing, and that’s fine too.</p>\n<h2 id=\"is-this-a-good-thing\">Is this a good thing?</h2>\n<p>I also want to to make a few other things clear: I haven’t talked to Steve about\nany of this, this is me just talking about what I see. Also, I haven’t tried Gas\nTown myself, and I’m not sure I will. This is because while I’m somewhat aligned\nwith Steve’s train of thought here, I’m also not entirely.</p>\n<p>I’m more interested in watching from afar. And that’s because, well, I have some\nconcerns with the way that Gas Town is doing things. But I’m also glad that he’s\ntrying it. It’s a rocket ship, and a lot of those exploded in the early days.\nTons of people died trying to invent the airplane. Some of their ideas were bad,\nbut eventually, enough of the conceptual space was explored to figure out the\ngood ideas.</p>\n<h2 id=\"one-example-question\">One example question</h2>\n<p>What I want to leave you with is an example of the kind of experimentation I’m\nseeing in Gas Town.</p>\n<p>At Oxide, <a href=\"https://oxide.computer/principles\">they talk a lot about their values</a>. It was a thing I really\nappreciated about working there. On Monday, there was an episode of Oxide and\nFriends on the topic of one of Oxide’s values as it applies to LLMs: Rigor.</p>\n<bluesky-post src=\"at://did:plc:6235fybsui2cjt4fcspdbwpd/app.bsky.feed.post/3mcfgktcrbs2n\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:6235fybsui2cjt4fcspdbwpd\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:6235fybsui2cjt4fcspdbwpd/bafkreidqaixw7yj2vj64nd7ixiqncujwo6trchymloh2bycaorqg7bjkhy\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:6235fybsui2cjt4fcspdbwpd\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Adam Leventhal</span></bdi> <span class=\"handle s-hik11q\">@ahl.bsky.social</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">On Monday, <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:sl3g3nhnad34sfhjnqdivbzd\" class=\"mention s-1lecfnd\">@sunshowers.io</a> and <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:ctphcgyhnllfluywokibtrgb\" class=\"mention s-1lecfnd\">@davidcrespo.bsky.social</a> joined me and <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:ucct3fwwarzd7rety6nzq4cc\" class=\"mention s-1lecfnd\">@bcantrill.bsky.social</a> to talk about their use of LLMs for building software--not to make vibe-coded one-offs, but to build much *more* robust systems software.</p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://youtu.be/fmFt4-jjEc0\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:6235fybsui2cjt4fcspdbwpd/bafkreiahoa3iyfh5cgirajxzhq5wafzqinzzqeh3piwzscs2mkt2ureu2u\" alt=\"\" class=\"thumbnail s-rtbqd8\"/> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Oxide and Friends 1/12/2026 -- Engineering Rigor in the LLM Age</p> <p class=\"description s-rtbqd8\">YouTube video by Oxide Computer Company</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">youtu.be</span></div></div></a></div> <time datetime=\"2026-01-14T15:55:02.860Z\" class=\"date s-hik11q\">January 14, 2026 at 3:55 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"56 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>56</span></span> <span class=\"stat s-hik11q\" title=\"17 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>17</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:6235fybsui2cjt4fcspdbwpd/post/3mcfgktcrbs2n\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 2 replies on Bluesky</span></a></div></div></div> </bluesky-post>\n<p>Gas Town is <em>aggressively</em> not rigorous. And while rigor is a great value to\nhave, when you’re in an experimental mindset, relaxing rigor can be a valid\nstrategy to move forward.</p>\n<p>But I think there’s a really interesting question to explore there: in the same\nway that we can use LLMs in a rigorous way, is there a form of rigor that works\nwith AI agents merging code into trunk? What do we <em>really</em> mean by “rigor”? Is\nit synonymous with “tests pass”? Does it mean that the state of the system must\nbe fully rigorous up front? Or is rigor an end state we work towards?</p>\n<p>I am skeptical, but that’s also exactly why I’m thinking about such things. For\nfolks who are trying to reckon seriously with AI and the impact it will have on\nsoftware development, I think 2026 is a year to open your mind, challenge\nassumptions, and re-consider what actually matters. The outcome of that might be\n“yeah, we were right before and these ideas are bad,” but I have a sneaking\nsuspicion that the world has changed, and some old assumptions do not hold any\nlonger.</p>\n<p>We’ll find out.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mcia32tae22j\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">How to think about Gas Town: <a target=\"_blank\" href=\"https://steveklabnik.com/writing/how-to-think-about-gas-town/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/how-...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/how-to-think-about-gas-town/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">How to think about Gas Town</p> <p class=\"description s-rtbqd8\">Blog post: How to think about Gas Town by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2026-01-15T18:36:50.633Z\" class=\"date s-hik11q\">January 15, 2026 at 6:36 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"138 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>138</span></span> <span class=\"stat s-hik11q\" title=\"18 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>18</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mcia32tae22j\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 21 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/",
"title": "Getting started with Claude for software development",
"description": "2025 was an interesting year in many ways. One way in which it was interesting for me is that I went from an AI hater to a pretty big user. And so I’ve had a few requests for a “using Claude” guide, so I figure new year, why not give it a shot? The lack of this kind of content…",
"url": "https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/",
"published": "2026-01-07T00:00:00.000Z",
"updated": "2026-01-07T00:00:00.000Z",
"content": "<p>2025 was an interesting year in many ways. One way in which it was interesting\nfor me is that I went from an AI hater to a pretty big user. And so I’ve had a\nfew requests for a “using Claude” guide, so I figure new year, why not give it a\nshot? The lack of this kind of content was something that really frustrated me\nstarting out, so feels like a good thing to contribute to the world.</p>\n<p>This post is going to be for software developers that are interested in learning\nabout using these tools as of early 2026. I’m going to spend this post talking\nabout some background, and then the first steps towards getting your feet wet.\nIf folks like it, I’ll follow up with more. There’s a lot here. I’m going to be\nspeaking about Claude directly, because it’s the tool I use the most, but a lot\nof this should apply to other platforms as well.</p>\n<h2 id=\"theres-a-lot-here\">There’s a lot here</h2>\n<p>The first thing I want to say on this topic is that there’s a reason that this\nis the first post of possibly many: there’s a lot here, as I just said above.\nThis matters more than you might think at first. Becoming productive with LLMs\nis not actually easy, no matter what other people tell you. A lot of advice in\nthis space is given by people who don’t have a teaching background, and have\nforgotten how much work they’ve put in to get to where they are.</p>\n<p>I liken it to vim: everyone acknowledges that modal editing is a different way\nof working. We joke about how hard it is to learn how to quit vim if you’ve\naccidentally started it up. But many people also acknowledge that the learning\ncurve is worth it, due to the power you get. I think of LLMs like vim: they’re\nnot super easy to get real results from, but the time invested can be worth it.</p>\n<p>It’s also worth saying up front: maybe it’s not worth it, for you. I don’t fault\nanyone for not wanting to spend time learning a new tool, especially in a space\nthat’s moving as fast as this one. Effectively everything I’m going to talk\nabout in this post has really only come into its own in the last 12-14 months.\nMaybe in another 12 months this post will be useless. I don’t know. But just\nlike we might not find the time to learn vim to be worth it over just using a\nmore normal editor, that doesn’t mean that deciding that all of this isn’t worth\nyour time isn’t a rational, reasonable decision to make. You’re not going to be\n“left behind” or whatever some of the boosters say, in the same way that you\ndon’t need to learn vim to do software dev. We aren’t doing vim vs emacs wars\nhere. We’re saying “hey if you want to learn vim this is how I think you can,\nand if not, that’s fine.”</p>\n<p>Furthermore, because there’s so much to cover, this post is going to be\nbackground and step 1. Because otherwise it would be too dang long. You can’t\njust read ten thousand words on this stuff and be an expert, you have to go\nactually use the things. So you should be taking time between each post to go\nand do that, and so not putting it all in one place should give you natural\nbreakpoints to go and actually try this stuff out.</p>\n<h2 id=\"intentionality\">Intentionality</h2>\n<p>The very first thing I want to say on this entire topic is something that I\nthink about a lot. I have generally had better outcomes with LLMs than a lot of\npeople I know. And that’s bothered me. And I’m not sure exactly why that is. But\nI do have one idea.</p>\n<p>I like to approach this space in a … maybe “scientific” way is too strong, but\nat least a rational one. I try things out, discard what doesn’t seem to work,\nand keep what seems to work. I try and think critically about this space.</p>\n<p>However.</p>\n<p>I do think that the whole “vibe” term, while complicated in this space, is also\nimportant. Vibes do matter, actually. I have some more science-y and some more\nfolks-y reasons that I believe this. But I do think that the attitude you bring\ntowards this process partially dictates your success, and I think you should be\nconscious of that while you go on this journey.</p>\n<p>Is that too woo-y for you? Okay, let me make it concrete: I un-ironically believe\nthat swearing at Claude makes it perform worse. I think you will get better\nresults working with an LLM if you treat them like you’d treat a respected\nco-worker, and you will get worse results if you berate, insult, or otherwise\nmistreat them.</p>\n<p>This matters because I think that for a lot of LLM-skeptical people who give\nthis a shot, they may not actually go “Hey claude what’s your fucking problem”\n(though I have literally seen this happen) they will tend to let their\nfrustrations show a bit more when things don’t work out. Use your emotional\nregulation skills. It’s very okay to be critical in response to whatever Claude\ndoes, but do it in a way that wouldn’t get you reported to HR in a healthy\ncompany. Do this:</p>\n<blockquote>\n<p>Why did you do it that way? I would have preferred if we did <this> instead.</p>\n</blockquote>\n<p>Not this:</p>\n<blockquote>\n<p>Stop making such basic mistakes. You know that we do <this> and not <that>,\nidiot.</p>\n</blockquote>\n<p>I think that being kind to people is good for you, but I also believe that even\nif you’re a misanthrope, consider this a skill to get increased output from the\ntool. I think a bit of anthropomorphization is actually a good thing here. We’ll\ncome back to that later during the more practical steps, but basically, that’s\nthe higher level principle at work: an LLM is not a person. But it is working\nbased off of language that people use. That’s its API. And so interacting with\nit in the way you’d interact with a co-worker is, in my mind, the right way to\ndo it.</p>\n<p>Maybe I’ll elaborate on this belief someday. Or maybe not. I do this for\npersonal belief reasons more than anything else. But it is something I want to\nshare.</p>\n<h2 id=\"claude-web-vs-claude-code\">Claude web vs Claude Code</h2>\n<p>Okay! Now that we’ve got that out of the way, let’s talk about the various ways\nyou can use Claude! There’s a number of them, actually, but I want to focus on\ntwo: on the web at <a href=\"https://claude.ai\">https://claude.ai</a>, and with <a href=\"https://www.claude.com/product/claude-code\">Claude\nCode</a>.</p>\n<p>Using Claude in these ways is fundamentally different. Both have pros and cons.\nFor real actual software development, you want to use Claude Code. This is due\nto the “agentic loop”, which you’ll learn more about in a bit. But for your\nfirst steps, using it via the web is okay. It’s mostly just important to know\nthat your experience using the web interface is not going to be the same as\nusing Claude Code. If I only had access to the web interface, I wouldn’t be so\nbullish on this stuff. But it is and can be useful. Especially when getting your\nfeet wet, as long as you can understand that they’re meaningfully different.</p>\n<h2 id=\"plans-vs-api-pricing\">Plans vs API pricing</h2>\n<p>This gets into another topic that matters: money. Another reason I do not fault\nanyone for not spending time with these tools is that vim is free, whereas\nClaude is very much not. However.</p>\n<p>There are three major factors in the money equation: Claude Web vs Claude Code,\nwhich models you have access to, and the actual cost. Let’s talk about them.</p>\n<p>You can load up <a href=\"https://claude.ai\">https://claude.ai</a> and talk to Claude right now for free. But\nyou cannot use Claude Code without paying. So if you want to start incredibly\nsmall, using the website at first before you fork over money can make sense.\nAgain, that’s fine, just know that the experience is different. But it may be a\ngood way to start.</p>\n<p>In 2024 and 2025, there was a good argument that you needed to be on a paid plan\nbecause that’s how you got access to the latest models. While this is still true\nto some degree, models have advanced far enough that the changes are less\nimportant over time. I do think that in the first half of 2026, it still does\nmatter to a degree. Basically, the difference between Claude 3, 4, and 4.5 is\nsignificant, but for me, Claude 4 was good enough a year ago to get real work\ndone. I’m not 100% sure which one you get for free today, but it’s at least\nfine. And I think that by the time the next round of models come out, the ones\nyou’ll have access to for free will be basically good enough to make this\nquestion moot. But do know that you get what you pay for, and paying for things\ndoes get you better performance.</p>\n<p>(Speaking of models, you’ll hear Claude referred to in three ways: Haiku,\nSonnet, and Opus. As the names imply, worst to best there, though also, fastest\nto slowest. Sonnet, especially the 4.5 version, is pretty good for everything.\nOpus 4.5 is wonderful. Haiku is great for certain things.)</p>\n<p>As for actual cost: there’s $20/month, $100/month, and $200/month plans, as well\nas “you pay per API call.” You might be tempted to think “I’ll just pay per API\ncall and keep my usage down.” This is a reasonable thing to think, and also a\nterrible mistake to make. You get a lot of bang for your buck with the plans. To\ngive you an idea, I recently hit my weekly limit last night on the $200/month\nplan, and my estimated usage for that week (which again, I’m paying $50 for)\nwould have been $1440.73 if I were paying by the API call. Now, I am a very\nheavy user, but the point stands: as someone trying out these tools, it is way\neasy to spend more than $20 of API tokens. If you want to give these tools a\nreal shot, come up with a spare $20, sign up for the cheap plan, and then cancel\nafter your experiment is over. You get access to Claude Code and you’ve capped\nyour spend. It’s a win/win. There’s some good secondary effects of trying to be\nfrugal here but I think that’s more of an intermediate than an advanced topic,\nto be honest. I think worrying about the money while you build these skills is a\ndistraction. Cap your spend via a plan so that way you can not stress out about\nbreaking the bank.</p>\n<p>Okay, with all of that background out of the way: let’s talk about your first\nsteps here.</p>\n<h2 id=\"your-first-steps-a-conversation\">Your first steps: a conversation</h2>\n<p>Everyone is interested in the ability of LLMs to generate code. But I think\nthat’s actually step 2, not step 1. The way I want you to start using these\ntools is purely <em>read-only</em> at first. This is also why the website is okay to\nget started with too; Claude Code is far better at generating code than the site\nis, but we’re not going to start by writing code.</p>\n<p>Find some code you’ve written recently. It can be literally anything. Load up\n<a href=\"https://claude.ai\">https://claude.ai</a>, and type:</p>\n<blockquote>\n<p>Hi Claude! Can we talk about this code?</p>\n</blockquote>\n<p>And then paste your code in. You don’t need any fancy prompting techniques. You\ndon’t even need to say what language it is. Just give it some code. It could be\nten lines, it could be a hundred. I wouldn’t recommend a thousand to start.</p>\n<p>Claude will probably respond with some sort of basic analysis of what you’ve\ndone, and then a question. I gave it ~50 lines of code a friend and I were\ndiscussing recently, and it gave me this back:</p>\n<blockquote>\n<p>Sure! This looks like <description of what it does>. You’ve got <three things\nthat the code does>.</p>\n<p>What’s on your mind about it? Are you thinking through the design, running\ninto a specific issue, or wanting feedback on a particular aspect?</p>\n</blockquote>\n<p>From here, you have a ton of options of which way to go, but they really depend\non what you’ve pasted in. Here’s some fun prompt ideas:</p>\n<blockquote>\n<p>Do you think this code is idiomatic?</p>\n<p>If you could improve one thing about this code, what might it be?</p>\n<p>If I wanted to modify this code to do <something>, how would you go about\ndoing that?</p>\n<p>Are there any bugs in this code?</p>\n<p>Are there any security implications of this code I may not have thought about?</p>\n</blockquote>\n<p>And so on. Anyway, the goal here is to just get used to this whole thing. It’s a\nbit weird! It’s very different than talking to a compiler.</p>\n<p>If Claude says something you disagree with, push back a little, just like you\nwould a co-worker:</p>\n<blockquote>\n<p>I’m not sure I agree with that. The reason why is that in some other part of\nthe system, there’s <behavior> and so that would impact this sort of\ndecision.</p>\n<p>Why did you suggest that? I’d like to understand more.</p>\n</blockquote>\n<p>Claude will absolutely not be right all of the time. And that’s okay! The goal\nis to work together, not that this is a magic tool that suddenly solves all of\nyour problems.</p>\n<h2 id=\"second-steps-bigger-questions\">Second steps: bigger questions</h2>\n<p>Once you’ve done this a few times, you might want to graduate to Claude Code.\nThe reason for this is that you can start to scale up your questions. Once\nyou’ve installed it and logged in, you’ll be at a terminal prompt. It might bug\nyou about creating a CLAUDE.md, don’t worry about that for now. Continue having\nconversations with Claude about your codebase. The reason that this is a a big\nstep up is that before, you had to paste all of the code in. Now, Claude can go\nfind your code itself. Some prompts for you to try:</p>\n<blockquote>\n<p>Please give me a code review of my codebase and suggest five things I could do\nto improve it.</p>\n<p>Can you find any bugs in <component>?</p>\n<p>I’m curious about the performance of <component>, can we talk about it?</p>\n</blockquote>\n<p>One thing I like to do here is have Claude double check my intuitions. A few\nmonths ago, working on an application in Rust, I was considering a refactoring.\nI hadn’t done it because I was worried that it would be tedious, take a while,\nand maybe not improve the codebase. It might! But it might not. But putting in\nthe day or two to do the refactor wasn’t really worth finding out if maybe that\nwould be wasted. So, I asked Claude. This is an example of a bit longer of a\nprompt:</p>\n<blockquote>\n<p>Hi Claude! I am considering refactoring my code. In a function like this:\n<paste code>, I don’t like how I did things, and I’m considering doing it\nlike this instead: <paste code>. However, I know that changes the signature,\nwhich impacts other code in the codebase. A few questions for you: 1. how many\nfunction signatures would need to be updated if I made this change? 2. can you\nshow me what the code would look like if I did this refactoring on one of my\nsimpler endpoints? 3. can you show me what the code would look like if I did\nthis refactoring on one of my most complex endpoints?</p>\n</blockquote>\n<p>Claude came back and said something like “250 signatures would need to change,\nhere’s the before and after using these two examples from your codebase.” Now,\nClaude isn’t perfect: maybe it was actually 260 signatures. But the point is,\nthis helped me characterize my intuition here: it would be a non-trivial amount\nof work. But I also got to see its impact on real code I had written, which\nhelped me decide if this refactoring would actually help me in some of the more\nhairy parts of the codebase.</p>\n<p>Note that there’s not really any special “prompt engineering” going on here. You\ndon’t need to do “as a senior software engineer” or stuff like that. Just talk\nto it like you’d talk to a person. It’s fine. That doesn’t mean that prompts are\nuseless, but this sort of optimization is an intermediate to advanced topic, and\nfrankly, I’m skeptical that at this point the “as an x” technique even helps.\nMore on that someday. The point is, you can start asking more complex questions\nas you get more comfortable with the tool.</p>\n<p>Because Claude works asynchronously, you can just fire off questions like these\nin the background, and come back to them when it’s done. Well, sorta. Let’s talk\nabout permissions before we wrap this up.</p>\n<h2 id=\"permissions\">Permissions</h2>\n<p>By default, Claude will put you in an “ask before edits” mode. This is a good\nway to start. It’ll check in with you before doing certain things, and you can\nsay yes or no. Please consider what it’s about to do, and give the answer you’re\ncomfortable with. Advanced users basically let Claude do whatever it wants, but\nyou’re not there yet, and there’s risks involved that aren’t obvious to you just\nyet as a new user, so even though it can be a bit annoying to say yes every time\nit asks, I’d encourage you to start off with minimal permissions. It gives you\nthe option to say “commands like this one are okay for the rest of my session”\nand so when it wants to <code>grep</code> or something, that can be nice to agree to, but\nI’d encourage you to not use it for writing code just yet, and tell it no if it\nasks. We’ll do that in a follow-up post.</p>\n<h2 id=\"in-conclusion\">In conclusion</h2>\n<p>So that’s my intro to getting started with Claude. Spend $20, talk to it like\nyou’d talk to a person, and use it as a means of getting feedback on your code,\ndon’t have it write anything just yet. Graduate to larger and larger questions\nas you get comfortable with what it can do. Gently push back when you think it\ngets out of line. But your goal here is a baseline understanding of what the\ntool is capable of, not to vibe code out an entire app in an afternoon. These\nskills may seem too basic, but I promise you, it gets harder from here, and so\nyou’ll want a solid foundation in read-only questions before we graduate to\nhaving Claude write some code.</p>\n<p>I hope this was helpful to you.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3mbu3gbqrwk2y\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Getting started with Claude for software development: <a target=\"_blank\" href=\"https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/gett...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/getting-started-with-claude-for-software-development/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Getting started with Claude for software development</p> <p class=\"description s-rtbqd8\">Blog post: Getting started with Claude for software development by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2026-01-07T18:20:23.515Z\" class=\"date s-hik11q\">January 7, 2026 at 6:20 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"338 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>338</span></span> <span class=\"stat s-hik11q\" title=\"51 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>51</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3mbu3gbqrwk2y\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 29 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/",
"title": "Thirteen years of Rust and the birth of Rue",
"description": "I started using Rust on December 21, 2012. It’s now December 22, 2025. That’s a long time. I don’t have a ton of thoughts about Rust to share today, like I thought I might. I program in the language regularly, it’s nice, I prefer it to all others. I have my beefs and my regrets,…",
"url": "https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/",
"published": "2025-12-21T00:00:00.000Z",
"updated": "2025-12-21T00:00:00.000Z",
"content": "<p>I started using Rust on December 21, 2012. It’s now December 22, 2025. That’s a\nlong time.</p>\n<p>I don’t have a ton of thoughts about Rust to share today, like I thought I\nmight. I program in the language regularly, it’s nice, I prefer it to all\nothers. I have my beefs and my regrets, like any real project, but for the most\npart, I am pretty happy these days. To be honest, after a pretty rough last few\nyears, that’s been the theme of 2025: I’m pretty happy these days.</p>\n<p>I have long been wondering if I should try and make my own language. I really\nlike them! That’s part of why I got involved in Ruby and then Rust in the first\nplace! But I’ll be turning 40 in a month, and I barely have time for things\nanymore. So I had kind of put that on the back burner.</p>\n<p>But one thing that 2025 changed: I went from an AI skeptic to writing most of my\ncode with Claude. I’m still a bit unsure about LLMs outside of programming, but\nas a software development tool, I’ve found them finally to be good enough to be\nlegitimately useful. Your mileage may vary, I’m not really going to try and tell\nyou that they should be for you, only that they are for me.</p>\n<p>And so, earlier this year, I embarked on starting my own little language project.\nI decided to call it “Rue”, becuase:</p>\n<ol>\n<li>It has to start with Ru, duh. I did Ruby and then Rust, so my hands are\nbasically tied here…</li>\n<li>It has a negative connotation, “to rue the day”, like Rust has a possible\nnegative interpretation.</li>\n<li>It has a nature connotation, a\n<a href=\"https://en.wikipedia.org/wiki/Ruta_graveolens\">Rue</a>, like Rust has a possible\nfungi connotation.</li>\n<li>It’s short.</li>\n</ol>\n<p>I have also been wondering for a few years if you can build a language without\nfinancial backing and a team. I think LLMs might change that equation. Yes,\nClaude costs money, but I mean like, you don’t need to do what Mozilla did and\ndrop seven or eight figures worth of money on a development team to make\nmeaningful progress. And since I don’t have a ton of personal time these days,\nwhat better way to test that than to actually give it a go.</p>\n<p>Anyway, I got into it, and kind of made a mess of the first go. And then I had a\ntremendously busy fall, and had to put things by the wayside.</p>\n<p>But this week, I had some time after work, and decided to start over. And I’m\nmuch more happy with how it’s going this time. So I’ve decided it’s time to at\nleast let the world know I’m working on it, just because why not.</p>\n<p>I don’t expect it to grow to anything more than my hobby project, but then\nagain, that’s also true of Rasmus and Graydon and a ton of other language\nauthors, so, never say never. But that’s not an explicit goal here. I reserve\nthe right to just stop working on this when I think it’s not fun. I’m not trying\nto build a community or even add committers. It’s just something for me for fun.</p>\n<p>You can find Rue over at <a href=\"https://rue-lang.dev/\">https://rue-lang.dev/</a> if you want to check it out.\nThere’s a blog with an RSS feed. I may blog there, I may not. But I’ll probably\nkeep it over there more than over here, in general.</p>\n<p>I’m not saying that Claude is amazing, or that this code is fantastic, or\nanything. The language is still very janky. I’m sure I have codegen bugs. It’s\nmissing basic features. It is very, very much a work in progress.</p>\n<p>Thank you to everyone for making 2025 a nice year. There’s a lot of terrible\nthings in this world right now, but I’m more hopeful than ever, personally.\nI hope to still be writing Rust in another ten years, and maybe even writing\nsome Rue. We’ll see :)</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3majjeqo4ik2t\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Thirteen years of <a target=\"_blank\" href=\"https://bsky.app/hashtag/rustlang\" class=\"hashtag s-1lecfnd\">#rustlang</a> and the birth of <a target=\"_blank\" href=\"https://bsky.app/hashtag/ruelang\" class=\"hashtag s-1lecfnd\">#ruelang</a> <a target=\"_blank\" href=\"https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/thir...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/thirteen-years-of-rust-and-the-birth-of-rue/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Thirteen years of Rust and the birth of Rue</p> <p class=\"description s-rtbqd8\">Blog post: Thirteen years of Rust and the birth of Rue by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-12-21T20:05:35.682Z\" class=\"date s-hik11q\">December 21, 2025 at 8:05 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"111 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>111</span></span> <span class=\"stat s-hik11q\" title=\"10 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>10</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3majjeqo4ik2t\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 19 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/i-see-a-future-in-jj/",
"title": "I see a future in jj",
"description": "In December of 2012, I was home for Christmas, reading Hacker News. And that’s when I saw “ Rust 0.5 released .\"\" I’m a big fan of programming languages, so I decided to check it out. At the time, I was working on Ruby and Rails, but in college, I had wanted to focus on…",
"url": "https://steveklabnik.com/writing/i-see-a-future-in-jj/",
"published": "2025-10-22T00:00:00.000Z",
"updated": "2025-10-22T00:00:00.000Z",
"content": "<p>In December of 2012, I was home for Christmas, reading Hacker News. And that’s\nwhen I saw “<a href=\"https://news.ycombinator.com/item?id=4953293\">Rust 0.5 released</a>.\"\" I’m a big fan of programming languages, so I\ndecided to check it out. At the time, I was working on Ruby and Rails, but in\ncollege, I had wanted to focus on compilers, and my friends were all very much\ninto systems stuff. So I decided to give Rust a try.</p>\n<p>And I liked it! But, for other reasons I won’t get into here, I was thinking\nabout a lot of things in that moment. I was looking to shake things up a bit. So\nI asked myself: is Rust going to be A Thing?</p>\n<h2 id=\"why-i-chose-rust\">Why I chose Rust</h2>\n<p>So, I thought about it. What does a programming language need to be successful?\nIt needs some sort of market fit. It needs to have people willing to work on it,\nas bringing a new language into the world is a lot of work. And it needs users.</p>\n<p>When I considered all of these things, here’s what I saw with Rust:</p>\n<p>Market fit: there was basically no credible alternatives to C and C++. I had\nbeen involved in the <a href=\"https://dlang.org/\">D community</a> a bit, but it was clear that it wasn’t going\nto take off. Go was a few years old, and hit 1.0 earlier that year, but for the\nkinds of work that C and C++ are uniquely able to do, I saw the same problem\nthat I did with D: garbage collection. This doesn’t mean Go isn’t a good\nlanguage, or that it’s not popular, but I didn’t see it as being able to\ncredibly challenge C and C++ in their strongholds. Rust, on the other hand, had\na novel approach to these problems: memory safety without garbage collection.\nNow, I also need to mention that Rust back in those days was much closer to Go\nthan it even is today, but again, I had just learned about it for a few hours, I\ndidn’t really have a deep understanding of it yet. If I had, I actually might\nhave also dismissed it as well, as it wasn’t really GC that was the issue, but a\nsignificant runtime. But again: I hadn’t really come to that understanding yet.\nPoint is: low-level programming was a space where there hadn’t been much\ninnovation in a very long time, and I thought that meant that Rust had a chance.\nCheck.</p>\n<p>For a team: well, Mozilla was backing it. This is a big deal. It meant that\nthere were folks whose job it was to work on the language. There’s so much that\nyou need to do to make a new language, and that means a ton of work, which means\nthat if you’re going to be able to get it done in a reasonable amount of time,\nhaving paid folks working on it is certainly better than the alternative. Check.</p>\n<p>And finally, how does this translate into users? Well, Mozilla was planning on\nusing it in Firefox. This is huge. Firefox is a major project, and if they could\nmanage to use Rust in it, that would prove that Rust was capable of doing real\nwork. And, more importantly, it would mean that there would be a lot of folks\nwho would need to learn Rust to work on Firefox. This would create a base of\nusers, which would help the language grow. Check.</p>\n<p>Finally, even though it wasn’t part of my initial assessment, I just really\nliked the Rust folks. I had joined IRC and chatted with people, and unlike many\nIRC rooms, they were actually really nice. I wanted to be around them more. And\nif I did, other people probably would too. So that was also a plus.</p>\n<p>So, I started learning Rust. I decided to write a tutorial for it, “Rust for\nRubyists,” because I’m a sucker for alliteration. And I eventually joined the\nteam, co-authored The Book, and if you’re reading this post, you probably know\nthe rest of the story.</p>\n<h2 id=\"enter-jj\">Enter jj</h2>\n<p>For some background, <a href=\"https://jj-vcs.github.io/jj/latest/\">jj</a> is a new version control system (VCS), not a\nprogramming language. It is written in Rust though! While I talked about how I\ndecided to get involved with Rust above, my approach here generalizes to other\nkinds of software projects, not just programming languages.</p>\n<p>I have a rule of thumb: if <a href=\"https://sunshowers.io/\">Rain</a> likes something, I will probably like that\nthing, as we have similar technical tastes. So when I heard her talk about jj,\nI put that on my list of things to spend some time with at some point. I was\nespecially intrigued because Rain had worked at Meta on their source control\nteam. So if she’s recommending something related to source control, that’s a\nhuge green flag.</p>\n<p>It took me a while, but one Saturday morning, I woke up a bit early, and thought\nto myself, “I have nothing to do today. Let’s take a look at jj.” So <a href=\"https://github.com/steveklabnik/jujutsu-tutorial/commit/fc8c588dfc58ce2ff54246174fc59aca0fc868b5\">I did</a>.\nYou’ll note that link goes to a commit starting a book about jj. Since it\nworked for me with Rust, it probably would work for me for jj as well. Writing\nabout something really helps clarify my thinking about it, and what better time\nto write something for a beginner than when you’re also a beginner?</p>\n<p>Anyway, people seem to really like my tutorial, and I’m thankful for that.</p>\n<h2 id=\"the-future-of-jj\">The future of jj</h2>\n<p>So, what do I see in jj? Well, a lot of it kind of eerily mirrors what I saw in\nRust: a good market fit, a solid team, and a potential user base.</p>\n<p>But the market fit is interesting. Git has clearly won, it has all of the\nmindshare, but since you can use jj to work on Git repositories, it can be\nadopted incrementally. At <a href=\"https://oxide.computer/\">Oxide</a>, Rain started using jj, and more of us did,\nand now we’ve got a chat channel dedicated to it. This is, in my opinion, the\nonly viable way to introduce a new VCS: it has to be able to be partially\nadopted.</p>\n<p>Google is using jj, and so that is a bit different than Mozilla, but the same\nbasic idea. I have more to say about Google’s relationship to jj, but that’s\ngoing to be a follow-up blog post. What I will say in this post is that at the\nfirst ever jj conference a few weeks ago, Martin (the creator of jj) said that\ninternal adoption is going really well. I’m burying the lede a bit here, because\nthe video isn’t up yet, and I don’t want to get the details of some of the more\nexciting news incorrect in this post. I also don’t mean to imply that everyone\nat Google is using jj, but the contingent feels significant to me, given how\nhard it is to introduce a new VCS inside a company of that size. Well, in this\ncase, it’s using Piper as the backend, so you could argue about some of the\ndetails here, but the point is: jj is being used in projects as small as\nindividual developers and as large as one of the largest monorepos in the world.\nThat’s a big deal. It can show the social proof needed for others to give jj a\nchance.</p>\n<p>Outside of Google, a lot of people say that there’s a bit of a learning curve, but\nonce you get over that, people really like it. Sound familiar? I think jj is\ndifferent from Rust in this regard in that it’s also very easy to learn if you\naren’t someone who really knows a ton about Git. It’s folks that really know Git\ninternals and have put time and care into their workflows that can struggle a\nbit with jj, because jj is different. But for people who just want to get work\ndone, jj is really easy to pick up. And when people do, they often tend to like\nit. jj has developed a bit of a reputation for having a passionate fanbase.\nPeople are adopting it in a skunkworks way. This is a great sign for a new tool.</p>\n<p>And finally, the team. Martin is very dedicated to jj, and has been working on\nit for a long time. There’s also a small group of folks working on it with him.\nIt recently moved out from his personal GitHub account to its own organization,\nand has started a more formal governance. The team is full of people who have a\ndeep history of working on source control tools, and they know what they’re\ndoing. The burgeoning jj community reminds me of that early Rust community: a\nbunch of nice folks who are excited about something and eager to help it grow.</p>\n<p>Basically, to me, jj’s future looks very bright. It reminds me of Rust in all of\nthe best ways.</p>\n<h2 id=\"putting-my-money-where-my-mouth-is\">Putting my money where my mouth is</h2>\n<p>Speaking of burying the lede… I’ve decided to leave Oxide. Oxide is the best\njob I’ve ever had, and I love the people I work with. I was employee 17. I think\nthe business will do fantastic in the future, and honestly it’s a bit of a\nstrange time to decide to leave, since things are going so well. But at the same\ntime, some of my friends have started a new company, <a href=\"https://ersc.io/\">ERSC</a>, which is going to\nbe building a new platform for developer collaboration on top of jj. Don’t\nworry, “errssk” isn’t going to be the name of the product. It’s kind of like how\nGitHub was incorporated as <a href=\"http://logicalawesome.com/\">Logical Awesome</a>, but nobody calls it that.</p>\n<p>This won’t be happening until next month, I have some stuff to wrap up at Oxide,\nand I’m going to take a week off before starting. But as sad as I am to be\nleaving Oxide, I’m also really excited to be able to spend more time working in\nthe jj community, and helping build out this new platform. For those of you\nwho’ve been asking me to finish my tutorial, well, now I’ll have the time to\nactually do that! I’m sorry it’s taken so long! You’ll see me talking about jj\neven more, spending even more time in <a href=\"https://discord.gg/36cDSjyd6K\">the Discord</a>, and generally being more\ninvolved in the community. And I’ll be writing more posts about it here as well,\nof course.</p>\n<p>I’m really excited about this next chapter. 2025 has been a very good year for\nme so far, for a number of reasons, and I am grateful to be able to take a\nchance on something that I’m truly passionate about.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3m3sebwbris2a\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">I see a future in <a target=\"_blank\" href=\"https://bsky.app/hashtag/jj-vcs\" class=\"hashtag s-1lecfnd\">#jj-vcs</a>: <a target=\"_blank\" href=\"https://steveklabnik.com/writing/i-see-a-future-in-jj/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/i-se...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/i-see-a-future-in-jj/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">I see a future in jj</p> <p class=\"description s-rtbqd8\">Blog post: I see a future in jj by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-10-22T17:22:31.703Z\" class=\"date s-hik11q\">October 22, 2025 at 5:22 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"384 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>384</span></span> <span class=\"stat s-hik11q\" title=\"53 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>53</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3m3sebwbris2a\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 67 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/a-tale-of-two-claudes/",
"title": "A tale of two Claudes",
"description": "It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of…",
"url": "https://steveklabnik.com/writing/a-tale-of-two-claudes/",
"published": "2025-06-10T00:00:00.000Z",
"updated": "2025-06-10T00:00:00.000Z",
"content": "<blockquote>\n<p>It was the best of times, it was the worst of times, it was the age of wisdom,\nit was the age of foolishness, it was the epoch of belief, it was the epoch of\nincredulity, it was the season of Light, it was the season of Darkness, it was\nthe spring of hope, it was the winter of despair, we had everything before us,\nwe had nothing before us, we were all going direct to Heaven, we were all going\ndirect the other way—in short, the period was so far like the present period,\nthat some of its noisiest authorities insisted on its being received, for good\nor for evil, in the superlative degree of comparison only.</p>\n<ul>\n<li>Charles Dickens, <em>A Tale of Two Cities</em></li>\n</ul>\n</blockquote>\n<p>I recently had two very different experiences with Claude Code. I wanted to\nshare them both, because I find the contrast interesting.</p>\n<h2 id=\"claude-hates-tailwind-4\">Claude hates Tailwind 4</h2>\n<p>I have really been enjoying <a href=\"https://tailwindcss.com/\">TailwindCSS</a>. I also have started several\nweb projects using it in the past year. Back in January, <a href=\"https://tailwindcss.com/blog/tailwindcss-v4\">Tailwind released\nversion 4</a>. Honestly, I am not a good enough Tailwind user to be able\nto appreciate the new features, but I do prefer to keep my projects up to date.\nSo I’ve needed to update my Tailwind projects to version 4.</p>\n<p>Claude… is nearly completely useless for this.</p>\n<p>You’d think that upgrading a very popular CSS framework would be a\nstraightforward task. But Claude has failed to set it up multiple times. While\nit’s true that Claude Code claims that its training cutoff is in January of\n2025 in its system prompt, it seems to have a cutoff in March. Regardless, a\nuseful tool shouldn’t need to be explicitly trained on the latest version of a\nframework to be able to help with it. Yet, it consistently fails to either set\nup Tailwind 4 in new projects, or upgrade existing projects to this fourth\nrelease.</p>\n<p>I did manage to get it to work earlier this week, when I <a href=\"https://github.com/steveklabnik/steveklabnik.com/commit/fc7a5f6d0a8169e45d67891fe784bdf2270f14f9\">asked it to\nupdate this website</a>, but at this point, it was mostly just me messing\naround. I wish I had saved the exact prompt, but basically I said something like\n“you have failed me consistently in this task, so please search for upgrade\nguides and the like to guide you in this update.” It did do that, but for some\nreason, the upgrade tool couldn’t update my configs, and so it decided to do it\nitself, and well, while this time it did manage to technically update, it didn’t\ndo it completely correctly. It removed the typography plugin, which I had to ask\nit to put back. And it didn’t properly import the config into my global CSS,\nwhich also caused weird issues. Regardless, it did manage to sort of do it,\nafter some coaxing, but this was a frustrating experience that was probably the\nthird or fourth time I had tried to get it to do this.</p>\n<p>While Tailwind 4 is a new major version release, it’s still incredibly popular,\nand one of the hunches I have about LLM usage for coding is that more popular\ntools will be easier for LLMs to use. This didn’t seem to be the case here,\nthough.</p>\n<h2 id=\"claude-loves-elf\">Claude loves… ELF?</h2>\n<p>For… reasons, I am working on an assembler. Don’t worry about it, you may find\nout about it someday, and you may not. It’s not for work, just for fun.</p>\n<p>Anyway, if you’re not familiar, basically this process turns assembly code into\nmachine code. Specifically, in this case, I’m encoding x86_64 assembly into an\nELF executable. ELF is a file format that is used by, among other things, Linux,\nfor executables.</p>\n<p>I’m working with Claude on this, and I again didn’t save my prompts, which I am\nannoyed by, but I roughly said something like “please implement codegen such\nthat it produces an ELF executable” and let it go. I did not expect this to\nwork, or go well. As I said above, I sort of expect more common tasks to be\neasier for LLMs, given that they have more training data. While I’m sure there’s\nsome code in Rust out there that does this, I still don’t expect it to be\nanywhere near as common as TailwindCSS.</p>\n<p>A few minutes later, Claude said “hey, I did it!” For completely unrelated\nreasons, I got a message from <a href=\"https://bsky.app/profile/there.is.no.aarch64.mov\">Ixi</a>, a friend of mine who would be\nparticularly interested in this project, and so I said something along the lines\nof “hey, I’m working on a project you’d be interested in, claude has just made\nsome large claims:</p>\n<blockquote>\n<p>Steve, we’ve successfully implemented a complete end-to-end x86-64 code\ngeneration pipeline</p>\n</blockquote>\n<p>Do you want to look at the PR together?” and they said yes.</p>\n<p>So I shared my screen, and we went through the PR together. It was pretty fine.\nA lot of the stuff at first was normal, but a bit boring: the error handling was\nfine, and maybe slightly different than I would have done, but it wasn’t so bad\nthat if a co-worker had sent in the PR, I would have objected to merging it.</p>\n<p>But Ixi noticed something:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> instruction_size</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&</span><span style=\"color:#79B8FF\">self</span><span style=\"color:#E1E4E8\">, instr</span><span style=\"color:#F97583\">:</span><span style=\"color:#F97583\"> &</span><span style=\"color:#B392F0\">Instruction</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">-></span><span style=\"color:#B392F0\"> u64</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> match</span><span style=\"color:#E1E4E8\"> instr {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Instruction</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">Push</span><span style=\"color:#E1E4E8\">(_) </span><span style=\"color:#F97583\">=></span><span style=\"color:#79B8FF\"> 2</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Instruction</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">Pop</span><span style=\"color:#E1E4E8\">(_) </span><span style=\"color:#F97583\">=></span><span style=\"color:#79B8FF\"> 2</span><span style=\"color:#E1E4E8\">,</span></span></code></pre>\n<p>These sizes are… wrong. They should (probably) be one byte. Fun! Most of the\ncode looked overall reasonable, though. So let’s look at that ELF it produced.</p>\n<p>I ran the executable, and it segfaulted. Surprise! While Claude did think it was\ndone, because it had produced an ELF file, and the tests were passing, it didn’t\nactually work. Given that those sizes were wrong, this wasn’t really a surprise.\nWe decided to dig into the code a bit more, but first: I told Claude “hey, this\nexample you compiled segfaults, something is wrong, can you fix it?” Again, not\nthe exact prompt. I wish I had saved it, but I didn’t think I was writing this\nblog post at the time, so I didn’t. But it was very basic like that. And I let\nClaude go in the background while we took a look.</p>\n<p>We used <code>objdump -xd</code> to see some of the information about the ELF file. We\nended up using <code>gdb</code> to see where it was crashing:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"plaintext\"><code><span class=\"line\"><span>Program received signal SIGSEGV, Segmentation fault.</span></span>\n<span class=\"line\"><span> 0x0000000000400093 in ?? ()</span></span>\n<span class=\"line\"><span> #0 0x0000000000400093 in ?? ()</span></span>\n<span class=\"line\"><span> #1 0x000000000040007d in ?? ()</span></span>\n<span class=\"line\"><span> #2 0x0000000000000001 in ?? ()</span></span>\n<span class=\"line\"><span> #3 0x00007fffffffddb3 in ?? ()</span></span>\n<span class=\"line\"><span> #4 0x0000000000000000 in ?? ()</span></span></code></pre>\n<p>There’s no debug info here, so we only have addresses. There was a jump going to\nan address that ends in <code>93</code>. This is incorrect: <code>0x400093</code> was three bytes into\na <code>movabs $0x24, rdi</code>, so… we were jumping to the middle of an instruction,\nwhich is nonsense, hence the segfault.</p>\n<p>(Now that I’m looking at this, I also find these other addresses suspect. I wish\nI had taken better notes while we were looking at this… the point is this was\na quick check as to what was going on, and it was very clear that this is just\nstraight-up wrong.)</p>\n<p>How could this happen? Well, if the sizes of the instructions were wrong, when\nthe code figures out where to jump, it would go to the wrong place, as it would\nmis-calculate how far away the jump target was. So this all made sense.\nHypothesis: instruction sizes are incorrect, which means the jump calculations\nare incorrect, which means fail.</p>\n<p>I had to run, so we ended the call, but I left Claude running. A few minutes\nlater, I checked in on it, and that’s when I was really surprised.</p>\n<p>Claude had fixed the issue! It wasn’t really even about that, but also, how it\ndid so:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"plaintext\"><code><span class=\"line\"><span>● Bash(gdb -batch -ex \"run\" -ex \"bt\" --args ./debug_test)</span></span>\n<span class=\"line\"><span> ⎿ Program received signal SIGSEGV, Segmentation fault.</span></span>\n<span class=\"line\"><span> 0x0000000000400093 in ?? ()</span></span>\n<span class=\"line\"><span> #0 0x0000000000400093 in ?? ()</span></span>\n<span class=\"line\"><span> #1 0x000000000040007d in ?? ()</span></span>\n<span class=\"line\"><span> #2 0x0000000000000001 in ?? ()</span></span>\n<span class=\"line\"><span> #3 0x00007fffffffddb3 in ?? ()</span></span>\n<span class=\"line\"><span> #4 0x0000000000000000 in ?? ()</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>● Perfect! The crash is at 0x400093, which means the call</span></span>\n<span class=\"line\"><span>instruction is jumping to the wrong location. The issue is</span></span>\n<span class=\"line\"><span>in my relocation calculation. Let me examine this more carefully.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Looking at the hex dump again:</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>Entry point: 0x400078</span></span>\n<span class=\"line\"><span>Call instruction: e8 16 00 00 00 (5 bytes)</span></span>\n<span class=\"line\"><span>Next instruction: 0x40007d</span></span>\n<span class=\"line\"><span>Target: 0x40007d + 0x16 = 0x400093</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> But the main function is actually at file offset 0x8A, which is</span></span>\n<span class=\"line\"><span> virtual address 0x40008A.</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> The issue is in my symbol address calculation. Let me examine</span></span>\n<span class=\"line\"><span> the assembler code:</span></span></code></pre>\n<p>Hmmm. It’s kind of right, but also maybe not? I kept scrolling:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"plaintext\"><code><span class=\"line\"><span> ● I found the issue! The assembler is calculating symbol</span></span>\n<span class=\"line\"><span> addresses relative to the start of the machine code (offset 0),</span></span>\n<span class=\"line\"><span> but when resolving relocations, it needs to account for the</span></span>\n<span class=\"line\"><span> fact that the machine code will be loaded at the ELF entry</span></span>\n<span class=\"line\"><span> point address.</span></span></code></pre>\n<p>That also didn’t feel right? More scrollback through. It was creating sample\nprograms in assembly, and then assembling them, and then examining the contents\nof the files. It was double checking its assumptions and understanding of the\ncode, and then, there it was:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"plaintext\"><code><span class=\"line\"><span>There's a bug in my instruction size calculation. Let me check</span></span>\n<span class=\"line\"><span>the instruction_size function:</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>● Read(src/lib.rs)</span></span>\n<span class=\"line\"><span> ⎿ Read 20 lines (ctrl+r to expand)</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>● Found the bug! Mov(, _) => 10 is wrong. It assumes all mov</span></span>\n<span class=\"line\"><span> instructions are 10 bytes (mov reg, imm64), but mov rdi, rax</span></span>\n<span class=\"line\"><span> is only 3 bytes.</span></span></code></pre>\n<p>Okay, wait… <code>mov</code>, and not <code>push</code>/<code>pop</code>? Sure enough, not only had it figured\nout that <code>push</code> and <code>pop</code> were one byte, not two, but it had also figured out\nthat <code>cmp</code> had a few forms with different sizes, and that <code>mov</code> did too. After\nfixing that… the executables worked!</p>\n<p>But really, the most impressive part is that it had basically done exactly what\nwe had done, but in the background, while we were looking at the code. It took a\nlittle longer to get there, because it went down one rabbit hole, but it had\nalso found other bugs that weren’t immediately obvious to us. And, I’ll be extra\nhonest: while I understand what’s going on here, I am not super great at\n<code>objdump</code> and <code>gdb</code>, and if Ixi wasn’t around, Claude would have probably found\nthis before me, if I were alone. This is part of why having friends is great,\nbut if I didn’t have that friend around at that moment, this would have\ntremendously helped.</p>\n<p>I am also impressed because, as mentioned above: I would expect this task to be\nmuch more rare than web development stuff. Yet, Claude did a far better job,\nfinding the issue quickly and fixing it, as opposed to the multiple times I gave\nit a chance with Tailwind.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>What does this mean? I don’t know, but I figured I want to start documenting\nthese experiences, good and bad, and so I wanted to share both of these stories,\none good, and one bad.</p>\n<p>LLMs are not magic, or perfect, but I have found them to be useful. It’s\nimportant to share both the successes, and also the failures. I’m hoping to do a\nbetter job of tracking the prompts and details to give you a better\nunderstanding of the specifics of how I’m using these tools, but the perfect is\nthe enemy of the good, and so these are the stories you’re going to get for now.</p>\n<hr/>\n<p>Here’s my post about this post on Bluesky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lrb6sb7fzl2i\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">A tale of two Claudes\n\n<a target=\"_blank\" href=\"https://steveklabnik.com/writing/a-tale-of-two-claudes/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/a-ta...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/a-tale-of-two-claudes/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">A tale of two Claudes</p> <p class=\"description s-rtbqd8\">Blog post: A tale of two Claudes by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-06-10T15:17:05.751Z\" class=\"date s-hik11q\">June 10, 2025 at 3:17 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"57 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>57</span></span> <span class=\"stat s-hik11q\" title=\"9 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>9</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lrb6sb7fzl2i\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 10 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/is-rust-faster-than-c/",
"title": "Is Rust faster than C?",
"description": "Someone on Reddit recently asked: What would make a Rust implementation of something faster than a C implementation, all things being the same? I think this is a great and interesting question! It’s really tough because it ultimately relies on what exactly you mean by “all things…",
"url": "https://steveklabnik.com/writing/is-rust-faster-than-c/",
"published": "2025-06-09T00:00:00.000Z",
"updated": "2025-06-09T00:00:00.000Z",
"content": "<p>Someone on Reddit recently asked:</p>\n<blockquote>\n<p>What would make a Rust implementation of something faster than a C\nimplementation, all things being the same?</p>\n</blockquote>\n<p>I think this is a great and interesting question! It’s really tough because it\nultimately relies on what exactly you mean by “all things being the same.” And I\nthink this is something that makes it hard to compare languages.</p>\n<p>Here’s some ways in which you can argue that things are “the same” but also,\nthat they’re not, and what they imply for runtime performance.</p>\n<h2 id=\"inline-assembly\">Inline Assembly</h2>\n<p>Rust has inline assembly built into the language. C has inline assembly as a\nvery common compiler extension, to the degree where saying it’s not part of the\nlanguage is an arguable nitpick.</p>\n<p>Here’s an example in Rust:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">use</span><span style=\"color:#B392F0\"> std</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">arch</span><span style=\"color:#F97583\">::</span><span style=\"color:#E1E4E8\">asm;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">#[</span><span style=\"color:#F97583\">unsafe</span><span style=\"color:#E1E4E8\">(no_mangle)]</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">pub</span><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> rdtsc</span><span style=\"color:#E1E4E8\">() </span><span style=\"color:#F97583\">-></span><span style=\"color:#B392F0\"> u64</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> lo</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> hi</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> unsafe</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> asm!</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"rdtsc\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> out</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"eax\"</span><span style=\"color:#E1E4E8\">) lo,</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> out</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"edx\"</span><span style=\"color:#E1E4E8\">) hi,</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> options</span><span style=\"color:#E1E4E8\">(nomem, nostack, preserves_flags),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> );</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> ((hi </span><span style=\"color:#F97583\">as</span><span style=\"color:#B392F0\"> u64</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\"><<</span><span style=\"color:#79B8FF\"> 32</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">|</span><span style=\"color:#E1E4E8\"> (lo </span><span style=\"color:#F97583\">as</span><span style=\"color:#B392F0\"> u64</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>This reads the time stamp counter with <code>rdtsc</code>, and returns its value.</p>\n<p>Here’s the example in C:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"c\"><code><span class=\"line\"><span style=\"color:#F97583\">#include</span><span style=\"color:#9ECBFF\"> <stdint.h></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">uint64_t</span><span style=\"color:#B392F0\"> rdtsc</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">void</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">{</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> uint32_t</span><span style=\"color:#E1E4E8\"> lo, hi;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> __asm__</span><span style=\"color:#E1E4E8\"> _</span><span style=\"color:#B392F0\">_volatile__</span><span style=\"color:#E1E4E8\"> (</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"rdtsc\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> : </span><span style=\"color:#9ECBFF\">\"=a\"</span><span style=\"color:#E1E4E8\">(lo), </span><span style=\"color:#9ECBFF\">\"=d\"</span><span style=\"color:#E1E4E8\">(hi)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> );</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> return</span><span style=\"color:#E1E4E8\"> ((</span><span style=\"color:#F97583\">uint64_t</span><span style=\"color:#E1E4E8\">)hi </span><span style=\"color:#F97583\"><<</span><span style=\"color:#79B8FF\"> 32</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">|</span><span style=\"color:#E1E4E8\"> lo;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>These (under <code>-0</code> in both rustc <code>1.87.0</code> and clang <code>20.1.0</code>) produce the same\nassembly:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"asm\"><code><span class=\"line\"><span style=\"color:#F97583\">rdtsc</span><span style=\"color:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> rdtsc</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> shl</span><span style=\"color:#79B8FF\"> rdx</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">32</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> mov</span><span style=\"color:#79B8FF\"> eax</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">eax</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> or</span><span style=\"color:#79B8FF\"> rax</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">rdx</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> ret</span></span></code></pre>\n<p>Here’s a link on Godbolt: <a href=\"https://godbolt.org/z/f7K8cfnx7\">https://godbolt.org/z/f7K8cfnx7</a></p>\n<p>Does this count? I don’t know. I don’t think it really speaks to the question\nasked, but it is one way to answer the question.</p>\n<h2 id=\"similar-code-different-results\">Similar code, different results</h2>\n<p>Rust and C can have different semantics for similar code. Here’s a struct in\nRust:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Rust</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> x</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> y</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u64</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> z</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> u32</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>Here’s “the same” struct in C:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"c\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#E1E4E8\"> C {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> uint32_t</span><span style=\"color:#E1E4E8\"> x;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> uint64_t</span><span style=\"color:#E1E4E8\"> y;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> uint32_t</span><span style=\"color:#E1E4E8\"> z;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">};</span></span></code></pre>\n<p>In Rust, this struct is 16 bytes (on x86_64, again) and in C, it is 24. This is\nbecause Rust is free to reorder the fields to optimize for size, while C\nis not.</p>\n<p>Is this the same, or different?</p>\n<p>In C, you can re-order the fields to get the same size. In Rust, you can write\n<code>#[repr(C)]</code> to get the same layout as C. Does this mean we should have written\ndifferent Rust or different C to get “the same” thing?</p>\n<h2 id=\"social-factors\">Social Factors</h2>\n<p>Some people have reported that, thanks to Rust’s checks, they are more willing\nto write code that’s a bit more dangerous than in the equivalent C (or C++),\nwhere they’d do a bit more copying to play it a bit safer. This would be “the\nsame” in the sense of the same devs on the same project, but the code would be\ndifferent, due to judgement calls. You can make an argument that that’s not the\nsame, but is different too.</p>\n<p>An example of this from a long time ago is the Stylo project. Mozilla tried to\nparallelize Firefox’s style layout twice in C++, and both times the project\nfailed. The multithreading was too tricky to get right. The third time, they\nused Rust, and managed to ship. This is the same project, (though not the same\nprogrammers, I believe) by the same organization, but one was possible and one\nwas not. Is that “the same”? In some senses, but not in others.</p>\n<p>This also goes for a similar question: assuming we have a junior developer\nwriting Rust, and also writing C, for the same task. Are we going to get faster\ncode in one or the other? This controls for ability, but not for the same code.\nIs that “the same”? I don’t know. What about an expert in each language, someone\nwho knows Rust super well but doesn’t know C, and vice versa, being given the\nsame task? Is that different than a junior, or an “average” developer?</p>\n<h2 id=\"compile-time-vs-runtime\">Compile time vs runtime?</h2>\n<p>Another redditor asks:</p>\n<blockquote>\n<p>I’m not Rust expert, but aren’t most (all?) of the safety checks compile time\nchecks? They shouldn’t have any runtime impact.</p>\n</blockquote>\n<p>A great question! Part of this is another example of defaults being different.</p>\n<p><code>array[0]</code> is valid in both languages.</p>\n<p>In Rust, there’s a bounds check at runtime. In C, there is not. Does this mean\nthat they’re the same? In Rust, I could write <code>array.get_unchecked(0)</code>, and get\nC’s semantics. In C, I could write a bounds check to get Rust’s semantics. Are\nany of those “the same”?</p>\n<p>In Rust, that check may be optimized away, if the compiler can prove it’s safe.\nIn C, if we wrote the bounds check by hand, the check may be optimized away, if\nthe compiler can prove it’s safe. Are any of those “the same”?</p>\n<p>They aren’t wrong that a lot of Rust’s safety checks are at compile time. But\nsome are at runtime. But this raises another interesting question: That compile-time\ncheck may cause you to write different code for the same task as in C. A common\nexample is using indices rather than pointers. That may mean that the generated\ncode performs differently. Is that check truly “at compile time”? Technically,\nat the micro level, yes. At the engineering level? Possibly not!</p>\n<h2 id=\"my-conclusions\">My Conclusions</h2>\n<p>I think the most important part of this question is related to possibility, that\nis:</p>\n<ol>\n<li>If we assume C is the ‘fastest language,’ whatever that means.</li>\n<li>Is there any inherent reason why Rust could not do the same things?</li>\n</ol>\n<p>I think the answer to that is “no,” even ignoring the inline assembly case. So\non that most important, fundamental level, the answer is “there’s no difference\nbetween the two.”</p>\n<p>But we’re not usually talking about that. We’re usually talking about something\nin the context of engineering, a specific project, with specific developers,\nwith specific time constraints, and so on. I think that there are so many\nvariables that it is difficult to draw generalized conclusions.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lr6xouhoxc2i\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Is <a target=\"_blank\" href=\"https://bsky.app/hashtag/rustlang\" class=\"hashtag s-1lecfnd\">#rustlang</a> faster than C?\n\n<a target=\"_blank\" href=\"https://steveklabnik.com/writing/is-rust-faster-than-c/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/is-r...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/is-rust-faster-than-c/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Is Rust faster than C?</p> <p class=\"description s-rtbqd8\">Blog post: Is Rust faster than C? by Steve Klabnik</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-06-09T18:04:36.057Z\" class=\"date s-hik11q\">June 9, 2025 at 6:04 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"91 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>91</span></span> <span class=\"stat s-hik11q\" title=\"19 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>19</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lr6xouhoxc2i\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 10 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/",
"title": "I am disappointed in the AI discourse",
"description": "Recently, something happened that made me kind of like, break a little bit. I’m still not entirely sure why this whole thing bothers me so much, but here’s a blog post about it. I happened to ask ChatGPT something for fun. I often do this just to see what it says, and if I feel…",
"url": "https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/",
"published": "2025-05-28T00:00:00.000Z",
"updated": "2025-05-28T00:00:00.000Z",
"content": "<p>Recently, something happened that made me kind of like, break a little bit. I’m\nstill not entirely sure why this whole thing bothers me so much, but here’s a\nblog post about it. I happened to ask ChatGPT something for fun. I often do this\njust to see what it says, and if I feel like it’s right or wrong. I tabbed over\nto another tab, and the top post on my Bluesky feed was something along these\nlines:</p>\n<blockquote>\n<p>ChatGPT is not a search engine. It does not scan the web for information. You\ncannot use it as a search engine. LLMs only generate statistically likely\nsentences.</p>\n</blockquote>\n<p>The thing is… ChatGPT was over there, in the other tab, searching the web. And\nthe answer I got was pretty good.</p>\n<p>I’m not linking to the post because I don’t want to continue to pile on to this\nperson, because it’s not really about them. It’s about the state of the\ndiscourse around AI.</p>\n<p>I am not claiming that ChatGPT is an amazing search engine. What is breaking my\nbrain a little bit is that all of the discussion online around AI is so\nincredibly polarized. This isn’t a “the middle is always right” sort of thing\neither, to be clear. It’s more that both the pro-AI and anti-AI sides are loudly\nproclaiming things that are pretty trivially verifiable as not true. On the anti\nside, you have things like the above. And on the pro side, you have people\ntalking about how no human is going to be programming in 2026. Both of these\nthings fail a very basic test of “@gork is this true?” (sorry this is a bad\njoke, if you’re not terminally <del>38 years old</del>on Bluesky, substitute “chat is\nthis true?” (sorry this is a bad joke, if you’re over the age of 40 substitute\n“is this true?” (sorry humor is one way of coping with stress and writing this\npost is stressing me out))).</p>\n<p>Oh, and of course, ethical considerations of technology are important too. I’d\nlike to have better discussions about that as well. <del>For me, capabilities precede\nthe ethical dimension, because the capabilities inform what is and isn’t\nethical.</del> (EDIT: I am a little unhappy with my phrasing here. A shocker given\nthat I threw this paragraph together in haste! What I’m trying to get at is that\nI think these two things are inescapably intertwined: you cannot determine the\nethics of something until you know what it is. I do not mean that capabilities\nare somehow more important.) But I also know reasonable people disagree with me\non that. Let’s talk about it! I mean that for <em>all</em> dimensions of this topic,\nnot solely the capabilities.</p>\n<p>Now, it’s not like I expect everyone to come to some consensus on a technology.\nFor example, a lot of people like Rust and hate Go, and a lot of people like Go\nand hate Rust. That’s fine. That doesn’t bother me. But there’s something\ndifferent about this that is driving me up a wall.</p>\n<p>To be clear, I am not particularly pro or anti AI. Here’s what I currently\nthink:</p>\n<ul>\n<li>LLMs can be helpful for software development.</li>\n<li>LLMs for software development are being oversold by some people who stand to\ngain a lot of money from them.</li>\n<li>LLMs for software development are being undersold by some people who have\ndecided for various reasons that they do not like the technology.</li>\n<li>Not everyone who dislikes LLMs is uninformed, just like not everyone who likes\nLLMs is a grifter.</li>\n<li>AI art is often terrible and slightly creepy.</li>\n<li>I have seen AI art that was fine. This is very rare.</li>\n<li>AI writing is often bland and boring but better than the average person’s\nwriting. Just to be clear, I feel like an elitist dick saying that. And maybe\nI am. I dunno, working on it.</li>\n</ul>\n<p>Anyway, just to be clear, (how many times can you say ‘just to be clear’ in one\npost, Steve, come on (at what level of nested parenthetical do I implement some\nsort of interlocutor into my blog like <a href=\"https://xeiaso.net/\">Xe</a>)) I don’t think that if you\nthink LLMs suck at software development, you’re wrong. What I want to be able to\ndo is talk about it in a reasonable way, and figure out why we have different\nopinions. That is it. But for some reason, being able to do that feels\nimpossible.</p>\n<p>I’m going to end up blogging more about AI/LLMs in the near future, to get some\nmore of this stuff off of my chest and to try and be the change I want to see in\nthe world. So expect some of that, probably. But until then, if you happen to\nhave any links to reasonable discussion on these topics, I’d love to see them.\nHere’s two posts I recently read and really enjoyed thinking about what they\nwere saying:</p>\n<ul>\n<li><a href=\"https://annievella.com/posts/the-software-engineering-identity-crisis/\">The Software Engineering Identity Crisis</a></li>\n<li><a href=\"https://jmsdnns.com/tech/algo-underground/\">Algorithmic Underground</a></li>\n</ul>\n<p>Thanks. If you want to get mad about this post please just ignore me.</p>\n<hr/>\n<p>Here’s my post about this post on Bluesky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lqaqe6uc3c2u\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">I am disappointed in the AI discourse <a target=\"_blank\" href=\"https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/i-am...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/i-am-disappointed-in-the-ai-discourse/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">I am disappointed in the AI discourse</p> <p class=\"description s-rtbqd8\"></p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-05-28T17:33:29.514Z\" class=\"date s-hik11q\">May 28, 2025 at 5:33 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"909 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>909</span></span> <span class=\"stat s-hik11q\" title=\"266 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>266</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lqaqe6uc3c2u\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 208 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/rust-ten-years-later/",
"title": "Rust 1.0, ten years later",
"description": "It’s kind of hard to believe it’s been ten years since Rust 1.0 was released. I stopped working on Rust three years ago, and frankly, the last three years have been pretty rough. But 2025 is shaping up to be a really good year for me, with lots of positive changes. With that in…",
"url": "https://steveklabnik.com/writing/rust-ten-years-later/",
"published": "2025-05-15T00:00:00.000Z",
"updated": "2025-05-15T00:00:00.000Z",
"content": "<p>It’s kind of hard to believe it’s been ten years since Rust 1.0 was released.</p>\n<p>I stopped working on Rust three years ago, and frankly, the last three years\nhave been pretty rough. But 2025 is shaping up to be a really good year for me,\nwith lots of positive changes.</p>\n<p>With that in mind, this post is going to be a bit of a different one than you\nmight expect. For a fantastic post that’s a bit more traditional, check out\nwhat Graydon wrote for the Rust Foundation: <a href=\"https://rustfoundation.org/media/10-years-of-stable-rust-an-infrastructure-story/\">10 years of stable Rust: an\ninfrastructure story</a>. It’s fantastic, of course.</p>\n<p>I’d instead like to reflect a bit on a comment I saw on the internet the other day:</p>\n<blockquote>\n<p>[Rust is] not a great hobby language but it is a fantastic professional\nlanguage, precisely because of the ease of refactors and speed of development\nthat comes with the type system and borrow checker.</p>\n</blockquote>\n<p><a href=\"https://pcwalton.github.io/\">Patrick Walton</a>, one of the earliest contributors to Rust, had this\nto say:</p>\n<blockquote>\n<p>I never thought I’d live to see the day when someone would say this. The first\n5 years of Rust were all “this is interesting for hobby projects but nobody will\never adopt this in industry”.</p>\n</blockquote>\n<p>The release of Rust 1.0 was great, but it was also stressful. The few years\nbefore Rust 1.0 were marked by a deep, deep anxiety among the team. I’m sure\nsomeone else who was around back then will disagree with me, but that’s my\nrecollection. It may not have always been overt, but it was there. A large\nquestion was looming over us: would anyone actually use this thing? Would it\nbecome a hobbyist language, or would it be something that people actually\nused in production?</p>\n<p>At this point, we now know the answer: yes, Rust is used a lot. It’s used\nfor real, critical projects to do actual work by some of the largest companies\nin our industry. We did good.</p>\n<p>The release of 1.0 was the start of that journey in earnest. It was time to stop\nchanging things all the time, and to start focusing on stability. It was time to\ngain more users, and to fix the issues that they found. We had to start shipping\nstable releases, and we had to start doing it on a regular basis. We had to\nstart doing all of the things that a real programming language does.</p>\n<p>Over the last ten years, a lot changed. It wasn’t easy, and it took a lot of\nblood, sweat, and many, many tears. But we did it. We built a real programming\nlanguage, and we built a real community around it.</p>\n<p>Rust is very much not perfect. There’s more work to be done, and arguably,\nsomeone out there should probably be starting a language that will someday eat\nRust’s lunch.</p>\n<p>But ten years ago, we were pushing a button and praying that it would all work\nout. And now, ten years later, I’m doing this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>❯ rustup update stable</span></span>\n<span class=\"line\"><span>info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'</span></span>\n<span class=\"line\"><span>info: latest update on 2025-05-15, rust version 1.87.0 (17067e9ac 2025-05-09)</span></span>\n<span class=\"line\"><span>info: downloading component 'rust-src'</span></span>\n<span class=\"line\"><span>info: downloading component 'cargo'</span></span>\n<span class=\"line\"><span>info: downloading component 'clippy'</span></span>\n<span class=\"line\"><span>info: downloading component 'rust-docs'</span></span>\n<span class=\"line\"><span>info: downloading component 'rust-std'</span></span>\n<span class=\"line\"><span>info: downloading component 'rustc'</span></span>\n<span class=\"line\"><span>info: downloading component 'rustfmt'</span></span>\n<span class=\"line\"><span>info: removing previous version of component 'rust-src'</span></span>\n<span class=\"line\"><span>info: removing previous version of component 'cargo'</span></span>\n<span class=\"line\"><span>info: removing previous version of component 'clippy'</span></span>\n<span class=\"line\"><span>info: removing previous version of component 'rust-docs'</span></span>\n<span class=\"line\"><span>info: removing previous version of component 'rust-std'</span></span>\n<span class=\"line\"><span>info: removing previous version of component 'rustc'</span></span>\n<span class=\"line\"><span>info: removing previous version of component 'rustfmt'</span></span>\n<span class=\"line\"><span>info: installing component 'rust-src'</span></span>\n<span class=\"line\"><span>info: installing component 'cargo'</span></span>\n<span class=\"line\"><span>info: installing component 'clippy'</span></span>\n<span class=\"line\"><span>info: installing component 'rust-docs'</span></span>\n<span class=\"line\"><span> 19.9 MiB / 19.9 MiB (100 %) 16.3 MiB/s in 1s ETA: 0s</span></span>\n<span class=\"line\"><span>info: installing component 'rust-std'</span></span>\n<span class=\"line\"><span> 29.4 MiB / 29.4 MiB (100 %) 22.2 MiB/s in 1s ETA: 0s</span></span>\n<span class=\"line\"><span>info: installing component 'rustc'</span></span>\n<span class=\"line\"><span> 76.3 MiB / 76.3 MiB (100 %) 23.9 MiB/s in 3s ETA: 0s</span></span>\n<span class=\"line\"><span>info: installing component 'rustfmt'</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> stable-x86_64-unknown-linux-gnu updated - rustc 1.87.0 (17067e9ac 2025-05-09) (from rustc 1.86.0 (05f9846f8 2025-03-31))</span></span></code></pre>\n<p>and then going about my day. It is a rare privilege to be able to spend ten\nyears building a tool you want to use, and then be able to spend your time at\nwork using it. I am grateful to have had that opportunity, and I hope that\neveryone else who has worked on Rust over the years feels the same way.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lpa37moavc2s\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\"><a target=\"_blank\" href=\"https://bsky.app/hashtag/rustlang\" class=\"hashtag s-1lecfnd\">#rustlang</a> 1.0, ten years later <a target=\"_blank\" href=\"https://steveklabnik.com/writing/rust-ten-years-later/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/rust...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/rust-ten-years-later/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Rust 1.0, ten years later</p> <p class=\"description s-rtbqd8\"></p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-05-15T17:49:56.018Z\" class=\"date s-hik11q\">May 15, 2025 at 5:49 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"198 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>198</span></span> <span class=\"stat s-hik11q\" title=\"35 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>35</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lpa37moavc2s\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 13 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/thoughts-on-bluesky-verification/",
"title": "Thoughts on Bluesky Verification",
"description": "Today, Bluesky rolled out blue checks . And, I was given one. Now, I’m not the biggest fan of this sort of feature, however, I also don’t really think this kind of feature is for me. I really like the idea of domain verification; I have been like “oh okay that’s coming from a…",
"url": "https://steveklabnik.com/writing/thoughts-on-bluesky-verification/",
"published": "2025-04-21T00:00:00.000Z",
"updated": "2025-04-21T00:00:00.000Z",
"content": "<p>Today, <a href=\"https://bsky.social/about/blog/04-21-2025-verification\">Bluesky rolled out blue checks</a>. And, I was given one.\nNow, I’m not the biggest fan of this sort of feature, however, I also don’t\nreally think this kind of feature is for me. I really like the idea of domain\nverification; I have been like “oh okay that’s coming from a <code>.gov</code> account”\nfrom time to time. But I don’t think most people really think about domains the\nway that programmers do.</p>\n<p>I have wanted to update my <a href=\"https://steveklabnik.com/writing/how-does-bluesky-work/\">“How does Bluesky work?”</a> post for a while\nnow, but I’ve been super busy. So, let’s ignore the product question for now,\nand focus on the technical question. How does verification work?</p>\n<h2 id=\"the-golden-rule-of-atproto\">The golden rule of atproto</h2>\n<p>I’ve also been thinking about designing apps on top of atproto. There’s a kind\nof rule of thumb that I realized about doing this, a sort of “golden rule” if\nyou will:</p>\n<blockquote>\n<p>You are the only person who can write records into your PDS.</p>\n</blockquote>\n<p>This has really interesting implications! For example, sometimes people ask for\n“soft blocks” on Bluesky. This is a twitter ‘feature’ where if you block\nsomeone, and then unblock them, they’re not following you any more. But on\nBluesky, if you soft block someone, they’re still following you. Why does that\nhappen?</p>\n<p>Following someone on Bluesky means that you write a record of the lexicon\n<a href=\"https://github.com/bluesky-social/atproto/blob/a1b2370dbc451570eafb982f326e8c110277ac01/lexicons/app/bsky/graph/follow.json\"><code>app.bsky.graph.follow</code></a> into your PDS. So you might assume that\nblocking someone would delete this record from their PDS. But that would violate\nthe golden rule! You can’t delete records from someone else’s PDS. So, instead,\nblocking someone on Bluesky means that you write a record of the lexicon\n<a href=\"https://github.com/bluesky-social/atproto/blob/a1b2370dbc451570eafb982f326e8c110277ac01/lexicons/app/bsky/graph/block.json\"><code>app.bsky.graph.block</code></a> into your PDS. Unblocking them deletes that\nrecord from your PDS. But it doesn’t delete the record from their PDS. So, if\nyou block someone, and then unblock them, they still have a record in their PDS\nthat says they’re following you.</p>\n<p>This may not be the behavior that you want as a user, but it follows from the\nconstraints of the overall protocol design.</p>\n<h2 id=\"how-verification-works\">How verification works</h2>\n<p>Now that we know the golden rule, we can understand how verification works. How\ndoes an account get verified? Well, someone has to be writing a record into a\nPDS somewhere. The natural design is the one they’ve chosen: someone becomes\nverified by someone else writing a record of the type\n<a href=\"https://github.com/bluesky-social/atproto/blob/a1b2370dbc451570eafb982f326e8c110277ac01/lexicons/app/bsky/graph/verification.json\"><code>app.bsky.graph.verification</code></a> into their PDS. <a href=\"https://pdsls.dev/at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.graph.verification/3lndpy3ngb62l\">Here</a> is\nthe record verifying me. It looks like this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"json\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">{</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"$type\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"app.bsky.graph.verification\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"handle\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"steveklabnik.com\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"subject\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"did:plc:3danwc67lo7obz2fmdg6jxcr\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"createdAt\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"2025-04-21T10:49:07.620Z\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> \"displayName\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"Steve Klabnik\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>This is in <code>@bsky.app</code>’s PDS. It describes my DID, and my handle and display\nname. One thing not mentioned in the blog post, but is in the comments of the\nlexicon, is that changing your handle or display name makes this record invalid.\nI didn’t know that! Good to remember in case I decide to make some jokes in my\ndisplay name.</p>\n<p>That’s the basics: a record exists, and a blue check shows up on my account. But\nwait, isn’t this centralized? I thought Bluesky was decentralized? Well, yes and\nno.</p>\n<p>You see, anyone can put a record of this type into their PDS. So, if I wanted to\nverify someone else, I can do that too: here’s <a href=\"https://pdsls.dev/at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.graph.verification/3lnduurfk7q2k\">me verifying\n<code>@jcsalterego.bsky.social</code></a>. But if you go to Jerry’s profile, you won’t\nsee a blue check. Why is that? Well, because the Bluesky AppView only shows a\nblue check if the record is from a “trusted verifier.” Who is that? Well, as far\nas we know, it’s from <code>@bsky.app</code> and <code>@nytimes.com</code>. Why can’t we tell more?\nWell, they’ve implemented this as a database column in the AppView, it’s not\n“in-protocol,” in other words.</p>\n<p>EDIT: Samuel has provided us with the list:</p>\n<bluesky-post src=\"at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.feed.post/3lndyqyyr4k2k\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:p2cp5gopk7mgjegy6wadk3ep/bafkreihrdgva6ebn3vmjb4qww3yqozf4eoq26mmraouegpawhr5mo5pnxu\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Samuel</span></bdi> <span class=\"handle s-hik11q\">@samuel.fm</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"context s-hik11q\">Replying to an unknown post</p> <p class=\"rich-text is-large s-1lecfnd\"><a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:z72i7hdynmk6r22z27h6tvur\" class=\"mention s-1lecfnd\">@bsky.app</a>\n<a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:eclio37ymobqex2ncko63h4r\" class=\"mention s-1lecfnd\">@nytimes.com</a>\n<a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:inz4fkbbp7ms3ixufw6xuvdi\" class=\"mention s-1lecfnd\">@wired.com</a> \n<a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:b2kutgxqlltwc6lhs724cfwr\" class=\"mention s-1lecfnd\">@theathletic.bsky.social</a></p> <time datetime=\"2025-04-21T20:26:13.833Z\" class=\"date s-hik11q\">April 21, 2025 at 8:26 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"25 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>25</span></span> <span class=\"stat s-hik11q\" title=\"10 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>10</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep/post/3lndyqyyr4k2k\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 1 reply on Bluesky</span></a></div></div></div> </bluesky-post>\n<p>So in some sense this is a centralized feature: clients can display information\nhowever they choose, and they’ve made a product choice that only trusted\nverifiers will show up as blue checks. But in another sense, it’s not\ncentralized, as anyone has the power to verify anyone else. Alternative clients\ncan decide to show or hide any of this information; <a href=\"https://deer.social/\">Deer Social</a>, one of these\nalternate clients, has added a user preference to hide all blue\nchecks, for example. A different one could show every verification as a blue\ncheck, or display them differently, say one badge per verification, styled like\nthe avatar of the person who verified you.</p>\n<p>EDIT: turns out that the default AppView also lets you hide blue checks:</p>\n<bluesky-post src=\"at://did:plc:35kdk2ntcs626zs6cm62i7ih/app.bsky.feed.post/3lne5sehgxc2y\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"post s-12mzi62\"><svg class=\"logo s-12mzi62\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg> <div class=\"aside s-12mzi62\"><a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" class=\"avatar-wrapper s-12mzi62\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-12mzi62\"/></a> <div class=\"line s-12mzi62\"></div></div> <div class=\"main s-12mzi62\"><div class=\"meta s-12mzi62\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-12mzi62\"><bdi class=\"display-name-wrapper s-12mzi62\"><span class=\"display-name s-12mzi62\">Steve Klabnik</span></bdi> <span class=\"handle s-12mzi62\">@steveklabnik.com</span></a> <span aria-hidden=\"true\" class=\"dot s-12mzi62\">·</span> <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lne4frl43s2d\" title=\"April 21, 2025 at 9:31 PM\" class=\"date s-12mzi62\"><time datetime=\"2025-04-21T21:31:31.924Z\">Apr 21, 2025</time></a></div> <p class=\"rich-text is-small s-1lecfnd\">Thoughts on Bluesky verification\n\n<a target=\"_blank\" href=\"https://steveklabnik.com/writing/thoughts-on-bluesky-verification/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/thou...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/thoughts-on-bluesky-verification/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Thoughts on Bluesky Verification</p> <p class=\"description s-rtbqd8\"></p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div></div></div><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:35kdk2ntcs626zs6cm62i7ih\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:35kdk2ntcs626zs6cm62i7ih/bafkreieezekf5jp4apfb6y3kfam5tvs32ktqbgtoq5waqunu2omo7tufkq\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:35kdk2ntcs626zs6cm62i7ih\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">jolheiser</span></bdi> <span class=\"handle s-hik11q\">@jolheiser.com</span></a> </div> <p class=\"rich-text is-large s-1lecfnd\">This was very informative, thanks for the write-up! \nI was curious about being able to hide the checkmarks, but unless I'm mistaken this appears to also be an option on the bluesky appview. \nAt least I was able to check the preference in my settings->moderation->verification settings</p> <time datetime=\"2025-04-21T21:56:28.122Z\" class=\"date s-hik11q\">April 21, 2025 at 9:56 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"2 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>2</span></span> <span class=\"stat s-hik11q\" title=\"0 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>0</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:35kdk2ntcs626zs6cm62i7ih/post/3lne5sehgxc2y\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 1 reply on Bluesky</span></a></div></div></div> </bluesky-post>\n<p>The point is still the same though: there’s choice here. Back to the original post!</p>\n<p>The underlying protocol is totally open, but you can make an argument that most\nusers will use the main client, and therefore, in practice it’s more centralized\nthan not. My mental model of it is the bundled list of root CAs that browsers\nship; in theory, DNS is completely decentralized, but in practice, a few root\nCAs are more trusted than others. It’s sort of similar here, except there’s no\nreal “delegation” involved: verifiers are peers, not a tree.</p>\n<p>This core design allows Bluesky to adjust the way it works in the future fairly\neasily, they could allow you to decide who to trust, for example. We’ll see how\nthis feature evolves over time.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>I’m flattered that I was chosen to be verified; if I was trying to ship this\nfeature, my name wouldn’t come up 181st on the list. I was really curious about\nthe design when they announced this feature, I assumed it was going to be closer\nto a PGP web of trust, or delegated in some way. This design is much simpler and\nless centralized than I expected: this is arguably more horizontal than the web\nof trust would have been.</p>\n<p>At the same time, I am interested in seeing if this changes the culture of the\nsite; some see the blue check as a status symbol of some kind, and think it\nmeans what you say matters more. I don’t think that’s true, personally, but it\ndoesn’t really matter what I think. To redraw the analogy with DNS, the blue\ncheck is very similar to the green lock: it doesn’t mean that what you’re saying\nis true, or right, or good, it just means that you are who you say you are. But\neven though a blue check is technically isomorphic (or close enough) to a green\nlock, I think a lot of people perceive it as being more than that. We’ll just\nhave to see. What I am glad about it is that it’s an in-protocol design, rather\nthan an external one like DMs are. I understand why they did it that way, but\nI’d rather that remain the exception rather than the rule.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lne4frl43s2d\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Thoughts on Bluesky verification\n\n<a target=\"_blank\" href=\"https://steveklabnik.com/writing/thoughts-on-bluesky-verification/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/thou...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/thoughts-on-bluesky-verification/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Thoughts on Bluesky Verification</p> <p class=\"description s-rtbqd8\"></p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-04-21T21:31:31.924Z\" class=\"date s-hik11q\">April 21, 2025 at 9:31 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"227 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>227</span></span> <span class=\"stat s-hik11q\" title=\"48 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>48</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lne4frl43s2d\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 24 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/",
"title": "Thinking like a compiler: places and values in Rust",
"description": "A while back, someone on the internet asked about this syntax in Rust: * pointer_of_some_kind = blah; They wanted to know how the compiler understands this code, especially if the pointer wasn’t a reference, but a smart pointer. I wrote them a lengthy reply, but wanted to expand…",
"url": "https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/",
"published": "2025-04-07T00:00:00.000Z",
"updated": "2025-04-07T00:00:00.000Z",
"content": "<p>A while back, someone on the internet asked about this syntax in Rust:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">*</span><span style=\"color:#E1E4E8\">pointer_of_some_kind </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> blah;</span></span></code></pre>\n<p>They wanted to know how the compiler understands this code, especially if the\npointer wasn’t a reference, but a smart pointer. I wrote them a lengthy reply,\nbut wanted to expand and adapt it into a blog post in case a broader audience\nmay be interested.</p>\n<p>Now, I don’t work on the Rust compiler, and haven’t really ever, but what I do\nknow is language semantics. If you’re a language nerd, this post may not be\nsuper interesting to you, other than to learn about Rust’s value categories, but\nif you haven’t spent a lot of time with the finer details of programming\nlanguages, I’m hoping that this may be a neat peek into that world.</p>\n<hr/>\n<p>Programming languages are themselves languages, in the same sense that human\nlanguages are. Well, mostly, anyway. The point is, to understand some Rust code\nlike this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">*</span><span style=\"color:#E1E4E8\">pointer_of_some_kind </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> blah;</span></span></code></pre>\n<p>We can apply similar tools to how we might understand some “English code” like\nthis:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>You can't judge a book by its cover.</span></span></code></pre>\n<p>Oh, you also might ask yourself as you’re reading the next sections, “why so\nmany steps?” The short answer is a classic one: by breaking down the big problem\nof “what does this mean?” into smaller steps, each step is easier. Doing everything\nat once is way more difficult than a larger number of smaller steps. I’m going\nto cover the classical ways compilers work, there’s a ton of variety when you\nstart getting into more modern ways of doing things, and often these steps are\nblended together, or done out of order, all kinds of other things. Handling\nerrors is a huge topic in and of itself! Consider this a starting point, not an\nending one.</p>\n<p>Let’s get into it.</p>\n<h2 id=\"lexical-analysis-aka-scanning-or-tokenizing\">Lexical Analysis (aka ‘scanning’ or ‘tokenizing’)</h2>\n<p>The first thing we want to do is to try and figure out if these words are even\nvalid words at all. With computer languages, this process is called “lexical\nanalysis,” though you’ll also hear the term “tokenizing” to describe it. In other\nwords, we’re not interested in any sort of meaning at all at this stage, we just\nwant to figure out what we’re even talking about.</p>\n<p>So if we look at this English sentence:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>You can't judge a book by its cover.</span></span></code></pre>\n<p>We follow a two step process in order to tokenize it: we first “scan” it to\nproduce a sequence of “lexemes.” We do this by following some rules. I’m not\ngoing to give you a sample of the rules for English here, as this post is\nalready long enough. But you might end up with something like this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>You</span></span>\n<span class=\"line\"><span>can't</span></span>\n<span class=\"line\"><span>judge</span></span>\n<span class=\"line\"><span>a</span></span>\n<span class=\"line\"><span>book</span></span>\n<span class=\"line\"><span>by</span></span>\n<span class=\"line\"><span>its</span></span>\n<span class=\"line\"><span>cover</span></span>\n<span class=\"line\"><span>.</span></span></code></pre>\n<p>Note how we do have <code>'</code> in <code>can't</code>, but <code>.</code> is separate from <code>cover</code>. These\nare the kinds of rules we’d be following: the <code>'</code> is because this is a contraction,\nbut the <code>.</code> is not really part of <code>cover</code>, but its own thing.</p>\n<p>We then run a second step and evaluate each individual string of characters, turning\nthem into “tokens.” A token is some sort of data type in your compiler, probably, so\nfor example we could do this in Rust:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">enum</span><span style=\"color:#B392F0\"> Token</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#B392F0\">String</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Punctuation</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#B392F0\">String</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>And so the output of our tokenizer might be an array of something like</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">[</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"You\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"can't\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"judge\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"a\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"book\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"by\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"its\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"cover\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> Punctuation</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\".\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">]</span></span></code></pre>\n<p>At this point, we know we have something semi-coherent, but we’re still\nnot sure it’s valid yet. On to the next step!</p>\n<h2 id=\"syntactic-analysis-aka-parsing\">Syntactic Analysis (aka ‘parsing’)</h2>\n<p>Funny enough, this is an area where human-language linguistics and compilers\nmean things that are slightly different. With human language, parsing is often\ncombined with our next step, which is semantic analysis. But we (most of the\ntime) try to separate syntax and semantic analysis in compilers.</p>\n<p>Again, I’m going to massively simplify with the English. We’re going to use\nthese rules for what a sentence is:</p>\n<ol>\n<li>A sentence is a sequence of words.</li>\n<li>A sentence has a “subject,” which is the first word, and must be capitalized.</li>\n<li>A sentence ends with a period.</li>\n</ol>\n<p>Obviously this is a tiny subset of English, but you get the idea. The goal of\nsyntactic analysis is to turn our sequence of tokens into a richer data\nstructure that’s easier to work with. In other words, we’ve figured out that\nour sentence is made up of valid sequences of characters, but do they fit the\ngrammatical rules of our language? Note that we also don’t need to store everything;\nfor example, maybe our English data structure looks like this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Sentence</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> subject</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> String</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> words</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">String</span><span style=\"color:#E1E4E8\">>,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>Where’s the period? How do we know <code>subject</code> must be capitalized? That’s the job\nof syntactic analysis. Since every sentence ends with a period, we don’t need to\ntrack it in our data structure: the analysis makes sure that it’s true, and then\nwe aren’t doing it again. Likewise, we don’t need to store our subject as a\ncapitalized string if we don’t want to: we determined that the input was, but we\ncan transform it as needed. So our value after syntax analysis might look like this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#B392F0\">Sentence</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> subject</span><span style=\"color:#F97583\">:</span><span style=\"color:#9ECBFF\"> \"you\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> words</span><span style=\"color:#F97583\">:</span><span style=\"color:#E1E4E8\"> [</span><span style=\"color:#9ECBFF\">\"can't\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"judge\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"a\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"book\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"by\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"its\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"cover\"</span><span style=\"color:#E1E4E8\">],</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>Often, for computer languages, a tree-like structure works well, and so you’d\nsee an “abstract syntax tree,” or “AST” at the end of this stage. But it’s not\nstrictly speaking required, whatever data structure makes sense for you can\nwork.</p>\n<p>Now that we have a richer data structure, we’re almost done. Now we have to get\ninto meaning.</p>\n<h2 id=\"semantic-analysis-aka-wtf-does-this-mean\">Semantic Analysis (aka ‘wtf does this mean’)</h2>\n<p>Imagine our sentence wasn’t “You can’t judge a book by its cover.” but instead\nthis:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor</span></span>\n<span class=\"line\"><span>incididunt ut labore et dolore magna aliqua.</span></span></code></pre>\n<p>This is a famous bit of text that’s incoherent. The words are all Latin words,\nand it <em>feels</em> like it might be a sentence, but it’s nonsense. We could parse\nthis into a <code>Sentence</code>:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#B392F0\">Sentence</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> subject</span><span style=\"color:#F97583\">:</span><span style=\"color:#9ECBFF\"> \"Lorem\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> words</span><span style=\"color:#F97583\">:</span><span style=\"color:#E1E4E8\"> [</span><span style=\"color:#9ECBFF\">\"ipsum\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"dolor\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"sit\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"amet\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#6A737D\">/* and continued */</span><span style=\"color:#E1E4E8\"> ],</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>But it’s not valid. How do we determine <em>that</em>?</p>\n<p>Well, in the context of English, “Lorem” isn’t a valid English word. So if we\nwere to check that the subject is a valid word, we’d successfully reject this\nsentence. In computer languages, we’d do similar things like type checking:\n<code>5 + \"hello\"</code> might lex and parse just fine, but when we try and figure out\nwhat it means, we learn it’s nonsense. Except if your language lets you add\nnumbers and strings!</p>\n<p>After semantic analysis, we’ve determined that our program is good, aka “well\nformed.” In a compiler, we’d then go on to generate machine code or byte code\nto represent our program. But that stuff, while incredibly interesting, isn’t\nwhat we’re talking about here: remember our original objective? It was to\nunderstand this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">*</span><span style=\"color:#E1E4E8\">pointer_of_some_kind </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> blah;</span></span></code></pre>\n<p>That’s semantics. So now that we have some background, let’s talk about how\nto understand this code.</p>\n<h2 id=\"so-how-do-i-understand-this-code\">So… how do I understand this code?</h2>\n<p>Well, to understand the code, we first need to understand how it lexes and\nparses. In other words, what the <em>grammar</em> of our language is. How our language\nwould lex, tokenize, and then parse our code. In this case, it’s Rust. Rust’s\ngrammar is large and complex, so we’ll only be talking about part of it today.\nWe’re going to focus on statements vs expressions.</p>\n<p>You may have heard that Rust is an “expression based language” before. Well,\nthis is what people mean. You see, when it comes to most of the things you say\nin a program, they’re often one of these two things. Expressions are things that\nproduce some sort of value, and statements are used to sequence the evaluation\nof expressions. That’s a bit abstract, so let’s get concrete.</p>\n<h3 id=\"statements\">Statements</h3>\n<p>Rust has a few kinds of statements: first, there’s “declaration statements”\nand “expression statements,” and each have their own sub-kinds as well.</p>\n<p>Declaration statements have two kinds: <em>item declarations</em>, and <em>let statements</em>.\nItem declarations are things like <code>mod</code> or <code>struct</code> or <code>fn</code>: they declare certain\nthings exist. <code>let</code> statements are probably the most famous form of statement in\nRust, they look like this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span> OuterAttribute* let PatternNoTopAlt ( : Type )? (= Expression † ( else BlockExpression) ? ) ? ;</span></span></code></pre>\n<p>That’s… a mouthful. We haven’t talked about <code>*</code> or <code>?</code> yet, and we don’t really\nwant to cover some of the more exotic parts of Rust right now. So we’re going\nto talk about this via a simpler grammar first:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>let Variable = Expression;</span></span></code></pre>\n<p>This is how we create new variables in Rust: we say <code>let</code>, and then a name, an <code>=</code>,\nand then finally some expression. The result of evaluating that expression becomes\nthe value of the variable.</p>\n<p>This is leaving out a lot: the name isn’t just a name, it’s a pattern, which is\nvery cool. <code>let else</code> exists in Rust now, and that’s cool. We’re ignoring types\nhere. But you can get the basics with just this simple version.</p>\n<p>Expression statements are much simpler:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>ExpressionWithoutBlock ; | ExpressionWithBlock ;?</span></span></code></pre>\n<p>The <code>|</code> there is an or, so we can either have a single expression followed by a <code>;</code>,\nor a block (denoted by <code>{}</code>s, which can optionally (the ? means it can exist or not)\nbe followed by a <code>;</code>.)</p>\n<p>So to think like a compiler, you can start to figure out how to combine these rules.\nFor example:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> x </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> 5</span><span style=\"color:#F97583\"> +</span><span style=\"color:#79B8FF\"> 6</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">};</span></span></code></pre>\n<p>Here, we have a let statement, but the expression on the right hand side of the <code>=</code>\nis an <code>ExpressionWithBlock</code>. Here’s a pop quiz for you: is the <code>;</code> part of the let\nexpression, or part of the expression on the right hand side?</p>\n<p>The answer is, it’s part of the <code>let</code>. The let expression has a mandatory <code>;</code>, but\nthe block does not, and so:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>let x = ExpressionWithBlock;</span></span></code></pre>\n<p>If we had the semicolon with the block, we’d still need the one for the let, and\nso we’d have <code>};;</code>. Which the compiler accepts, but it warns about it.</p>\n<p>Going back to our original code:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">*</span><span style=\"color:#E1E4E8\">pointer_of_some_kind </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> blah;</span></span></code></pre>\n<p>We don’t have a <code>let</code>, and this isn’t an item declaration: this is an expression\nstatement. We have a <code>ExpressionWithoutBlock</code>, followed by a <code>;</code>. So now we have\nto talk about expressions.</p>\n<h3 id=\"expressions\">Expressions</h3>\n<p>There are a <em>lot</em> of expression types in Rust. <a href=\"https://doc.rust-lang.org/stable/reference/expressions.html\">Section 8.2 of the\nReference</a> has 19 sub-sections. Whew! In this case, this code is an\nOperation Expression, and more specifically, an <a href=\"https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#assignment-expressions\">Assignment Expression</a>:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>Expression = Expression</span></span></code></pre>\n<p>Easy enough! So the left hand side of the <code>=</code> is an expression with <code>*pointer_of_some_kind</code>, and the right hand side is <code>blah</code>. Easy enough!</p>\n<p>But these two expressions are in some ways, the entire reason that I wrote this\npost. We just finally got here! You see, the reference has this to say about\nassignment expressions:</p>\n<blockquote>\n<p>An assignment expression moves a value into a specified place.</p>\n</blockquote>\n<p>What are those?</p>\n<h4 id=\"places-and-values\">Places and Values</h4>\n<p>C, and older versions of C++, called these two things “lvalue” and “rvalue,”\nfor “left” and “right”, like the sides of the <code>=</code>. More recent C++ standards\nhave more categories. Rust splits the difference: it only has two categories,\nlike C, but they map more cleanly to two of C++‘s categories. Rust calls\nlvalues, the left hand side, a “place,” and rvalues, the right hand side,\na “value.” Here’s two more precise definitions, from the Unsafe Code Guidelines:</p>\n<ul>\n<li>A <a href=\"https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#place\">place</a> is basically a pointer, but might contain more information\nsuch as size or alignment. A place has a type, indicating the type of values\nthat it stores.</li>\n<li>A <a href=\"https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#value\">value</a> A value is what gets stored in a place. A value has a type.</li>\n</ul>\n<p>Both of these have expression forms, so a <em>place expression</em> produces a place\nwhen it’s evaluated, and a <em>value expression</em> produces a value. And that’s\nhow <code>=</code> works: we have a place expression on the left, a value expression\non the right, and we put that value in that place. Easy enough!</p>\n<p>Once again, the code we were trying to figure out:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">*</span><span style=\"color:#E1E4E8\">pointer_of_some_kind </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> blah;</span></span></code></pre>\n<p>So <code>*</code>, the dereference operator, takes the pointer, and evaluates to the place\nwhere it points: its address. And <code>blah</code> gives us the value to put there.</p>\n<p>Case closed? Not quite!</p>\n<h3 id=\"deref\">Deref</h3>\n<p>Rust has a trait, <code>Deref</code>, that lets us override the <code>*</code> operator. So let’s\ntalk about this example, to make things easier:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">use</span><span style=\"color:#B392F0\"> std</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">ops</span><span style=\"color:#F97583\">::</span><span style=\"color:#E1E4E8\">{</span><span style=\"color:#B392F0\">Deref</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">DerefMut</span><span style=\"color:#E1E4E8\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> DerefMutExample</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">T</span><span style=\"color:#E1E4E8\">> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> value</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> T</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">impl</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">T</span><span style=\"color:#E1E4E8\">> </span><span style=\"color:#B392F0\">Deref</span><span style=\"color:#F97583\"> for</span><span style=\"color:#B392F0\"> DerefMutExample</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">T</span><span style=\"color:#E1E4E8\">> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> type</span><span style=\"color:#B392F0\"> Target</span><span style=\"color:#F97583\"> =</span><span style=\"color:#B392F0\"> T</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> deref</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&</span><span style=\"color:#79B8FF\">self</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">-></span><span style=\"color:#F97583\"> &</span><span style=\"color:#79B8FF\">Self</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">Target</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> &</span><span style=\"color:#79B8FF\">self</span><span style=\"color:#F97583\">.</span><span style=\"color:#E1E4E8\">value</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">impl</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">T</span><span style=\"color:#E1E4E8\">> </span><span style=\"color:#B392F0\">DerefMut</span><span style=\"color:#F97583\"> for</span><span style=\"color:#B392F0\"> DerefMutExample</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">T</span><span style=\"color:#E1E4E8\">> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> deref_mut</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&mut</span><span style=\"color:#79B8FF\"> self</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">-></span><span style=\"color:#F97583\"> &mut</span><span style=\"color:#79B8FF\"> Self</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">Target</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> &mut</span><span style=\"color:#79B8FF\"> self</span><span style=\"color:#F97583\">.</span><span style=\"color:#E1E4E8\">value</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#E1E4E8\"> main () {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#F97583\"> mut</span><span style=\"color:#E1E4E8\"> x </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> DerefMutExample</span><span style=\"color:#E1E4E8\"> { value</span><span style=\"color:#F97583\">:</span><span style=\"color:#9ECBFF\"> 'a'</span><span style=\"color:#E1E4E8\"> };</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> *</span><span style=\"color:#E1E4E8\">x </span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\"> 'b'</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> assert_eq!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">'b'</span><span style=\"color:#E1E4E8\">, x</span><span style=\"color:#F97583\">.</span><span style=\"color:#E1E4E8\">value);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>You can play with it <a href=\"https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0d6c7f3d8a2c00bc79dabef47ab550e2\">here</a>.</p>\n<p>We didn’t talk about the kind of expression that’s relevant here yet: a <em>path\nexpression.</em></p>\n<blockquote>\n<p>Path expressions that resolve to local or static variables are place\nexpressions, other paths are value expressions.</p>\n</blockquote>\n<p>We talked about <code>let</code> earlier: in <code>let mut x = DerefMutExample { value: 'a' };</code>\nabove, the <code>x</code> is a path expression, and since it’s resolving to our new variable,\nthat means it’s a place expression. The <code>DerefMutExample { value: 'a' }</code> is a\nvalue expression, because it doesn’t resolve to a variable.</p>\n<p>Let’s talk about <code>*x = 'b';</code>, remember our assignment expression:</p>\n<p>expression = expression;</p>\n<p>And what it does: it moves a value into a place.</p>\n<p>To understand how <code>*</code> works, we only need to add one more thing: the Dereference\nexpression. It’s produced by the deference operator, <code>*</code>. It looks like this:</p>\n<p>*expression</p>\n<p>Its semantics are pretty straightforward:</p>\n<ul>\n<li>If the expression has the type <code>&T</code>, <code>&mut T</code>, <code>*const T</code>, or <code>*mut T</code>, then\nthis evaluates to the place of the value being pointed to, and gives it the\nsame mutability.</li>\n<li>If the expression is not one of those types, then it is equivalent to either\n<code>*std::ops::Deref::deref(&x)</code> if it’s immutable or\n<code>*std::ops::DerefMut::deref_mut(&mut x)</code></li>\n</ul>\n<p>That’s it. Now we have enough to fully understand <code>*x = 'b'</code>:</p>\n<ul>\n<li><code>'b'</code> is a value expression</li>\n<li><code>*x</code> is not a pointer type, so we expand it to\n<code>*std::ops::DerefMut::deref_mut(&mut x)</code> and try again</li>\n<li><code>std::ops::DerefMut::deref_mut(&mut x)</code> returns the type <code>&mut char</code> in this\ncase, and it is pointing at the place of <code>self.value</code> (which I’m gonna call\n<code><that place></code> for short), which currently has the value <code>'a'</code> stored in it. Now\nwe have <code>*&mut <that place></code>.</li>\n<li>We now use the other rule of the dereference operator, we’re operating on a\n<code>&mut T</code>, so <code>*&mut <that place></code> refers to <code><that place></code>.</li>\n<li>We now have <code><that place> = 'b'</code>, and so we move that <code>'b'</code> into that place.</li>\n</ul>\n<p>Whew! That’s that.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>Thinking like a compiler can be fun! Once you’ve mastered the idea of grammars\nand gotten used to substituting things, you can figure out all sorts of\ninteresting stuff. In this specific case, sometimes people wonder why <code>Deref</code>\nreturns a reference if the whole goal is to show where something should point…\nand if you didn’t know that dereference expressions expanded to something with\n<code>*</code> in it, it would be confusing! But now you know. And hopefully learned some\ninteresting things about values, places, and how compilers think about code\nalong the way.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lmanw5qdik2o\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Thinking like a compiler: places and values in <a target=\"_blank\" href=\"https://bsky.app/hashtag/rustlang\" class=\"hashtag s-1lecfnd\">#rustlang</a>\n\n<a target=\"_blank\" href=\"https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/thin...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/thinking-like-a-compiler-places-and-values-in-rust/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Thinking like a compiler: places and values in Rust</p> <p class=\"description s-rtbqd8\"></p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-04-07T19:09:04.578Z\" class=\"date s-hik11q\">April 7, 2025 at 7:09 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"85 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>85</span></span> <span class=\"stat s-hik11q\" title=\"11 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>11</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lmanw5qdik2o\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/",
"title": "Does unsafe undermine Rust's guarantees?",
"description": "When people first hear about unsafe in Rust, they often have questions. A very normal thing to ask is, “wait a minute, doesn’t this defeat the purpose?” And while it’s a perfectly reasonable question, the answer is both straightforward and has a lot of nuance. So let’s talk about…",
"url": "https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/",
"published": "2025-03-17T00:00:00.000Z",
"updated": "2025-03-17T00:00:00.000Z",
"content": "<p>When people first hear about <code>unsafe</code> in Rust, they often have questions. A very\nnormal thing to ask is, “wait a minute, doesn’t this defeat the purpose?” And while\nit’s a perfectly reasonable question, the answer is both straightforward and has a\nlot of nuance. So let’s talk about it.</p>\n<p>(The straightforward answer is “no”, by the way.)</p>\n<h2 id=\"memory-safe-languages-and-their-implementations\">Memory safe languages and their implementations</h2>\n<p>The first way to think about this sort of thing is to remember that programming\nlanguages and their implementations are two different things. This is sometimes\ndifficult to remember, since many programming languages only have one\nimplementation, but this general principle applies even in those cases. That is,\nthere are the desired semantics of the language you are implementing, and then\nthe codebase that makes programs in that language do those things.</p>\n<p>Let’s examine a “real” programming language to talk about this: <a href=\"https://en.wikipedia.org/wiki/Brainfuck\">Brainfuck</a>.\nBrainfuck programs are made up of eight different characters, each one performing\none operation. Brainfuck programs can only do those eight things, and nothing\nmore.</p>\n<p>However, this simplicity leads to two interesting observations about the\ndifferences between languages in the abstract and their implementations.</p>\n<h3 id=\"unsafety-in-implementation\">Unsafety in implementation</h3>\n<p>The first is that properties of the language itself are distinct from properties\nof an implementation of the language. The semantics of the Brainfuck language\ndon’t provide an opportunity to call some sort of re-usable function\nabstraction, for example. Yet, when we write a Brainfuck interpreter or\ncompiler, we can use a programming language that supports functions. That\ndoesn’t change that Brainfuck programs themselves can’t define or call\nfunctions.</p>\n<p>And in fact, we often want to implement a language in another language that has\nmore abilities than the language we’re implementing. For example, while it’s\npossible to implement Brainfuck in Brainfuck, it’s much easier to do so in a\nlanguage that lets you define and call functions. Another classic example is\ninteracting with hardware. Brainfuck has the <code>.</code> instruction that allows you to\nproduce output. But in order to actually produce output to say, a terminal, you\ninteract with your platform’s API to do so. But inside of that API, eventually\nyou’ll hit a layer where there are no abstractions: you need to talk to hardware\ndirectly. Doing so is not statically able to be guaranteed to be memory safe,\nbecause the hardware/software interface often boils down to “put some\ninformation at this arbitrary memory location and the hardware will take it from\nthere.” Our Brainfuck program is operating in an environment just like any\nother; at the end of the day it has to coordinate with the underlying operating\nsystem or hardware at some point. But just because our implementation must do\nso, doesn’t mean that our Brainfuck programs do so directly. The semantics of\nprograms written in Brainfuck aren’t changed by the fact that the implementation\ncan (and must) do things that are outside of those semantics.</p>\n<p>Let’s take Ruby as a more realistic example than Brainfuck. Ruby does not let us modify\narbitrary memory addresses from within Ruby itself. This means that pure Ruby\nprograms should not be able to produce a segmentation fault. But in the real\nworld, Ruby programs can segfault. This is possible if there’s a bug in the Ruby\ninterpreter’s code. Sure, it’s our Ruby code that’s making the call to a\nfunction that results in a segfault, but the real fault lies with the code\noutside of Ruby’s purview.</p>\n<p>That this can happen doesn’t mean that Ruby’s guarantees around memory\nmanipulation is somehow useless, or suffers segmentation faults at the same rate\nas programs in a language that allow you to manipulate arbitrary memory as a\nmatter of course. But it does also mean that we don’t need to look at our entire\nprogram to figure out where the problem comes from: it instead comes from our\nnon-Ruby code. Ruby’s guarantees have helped us eliminate a lot of suspects when\nfiguring out whodunit.</p>\n<h3 id=\"extending-languages-and-unsafety\">Extending languages and unsafety</h3>\n<p>The second property is that certain implementations may extend the language in\narbitrary ways. For example, I could write a Brainfuck interpreter and say that\nI support a ninth instruction, <code>@</code>, that terminates the program when invoked.\nThis would be a non-portable extension, and Brainfuck programs written in my\nvariant wouldn’t work in other interpreters, but sometimes, this technique is\nuseful.</p>\n<p>Many languages find that these sorts of extensions are useful, and so offer a\nfeature called a “foreign function interface” that allows for your program to\ninvoke code in a different language. This provides the ability to do things that\nare outside of the domain of the language itself, which can be very useful.\nBrainfuck does not offer an FFI, but if it did, you could imagine that <code>@</code> could\nbe implemented in terms of it, making programs that use <code>@</code> portable again, as\nlong as the extended functionality was included somehow, often as a library of\nsome kind.</p>\n<p>Just like our implementation has the ability to do things outside of our\nlanguages’ semantics, FFI and similar extension mechanisms also give us the\nability to do arbitrary things. I can write an extension for Ruby that writes to\narbitrary memory. And I can cause a segfault. But we’re in the same place that\nwe were with our implementation issues; we know that if we get a segfault, the\nblame lies not with our Ruby code, but instead, with either the implementation\nor our FFI.</p>\n<h3 id=\"so-how-is-this-memory-safe\">So how is this memory safe?</h3>\n<p>It may seem contradictory that we can call a language “memory safe” if\nreal-world programs have the ability to cause memory problems. But the thing is,\nit isn’t <em>really</em> programs in that language that caused the issue: it was FFI,\nwhich is in a different language entirely, or it was implementation issues, and\nimplementations must do memory unsafe things, thanks to a need to interact with\nthe OS or hardware. And so the definition of “memory safe language” is commonly\nunderstood to refer to languages and their implementations in the absence of\neither implementation bugs or FFI bugs. In practice, these bugs occur at such a\nlow rate compared to languages that are clearly not memory safe that this\npractical definition serves a good purpose, even if you may feel a bit\nuncomfortable with these “exceptions.”</p>\n<p>But there’s actually a deeper reason why these exceptions are acceptable, and\nthat’s due to how we understand properties of programs and programming languages\nin the first place. That is, this isn’t just a practical “well these exceptions\nseem fine” sort of thing, they’re actually okay on a deeper level.</p>\n<h2 id=\"programs-properties-and-proofs\">Programs, properties, and proofs</h2>\n<p>So how do we know anything about how programs and programming languages work at\nall?</p>\n<blockquote>\n<p>Computer science is no more about computers than astronomy is about\ntelescopes.</p>\n<ul>\n<li>Edsger Wybe Dijkstra</li>\n</ul>\n</blockquote>\n<p>A fun thing about computer science is that it’s closely related to other\ndisciplines, including math! And so there’s a long history of using math\ntechniques to understand computers and their programs.</p>\n<p>One technique of building knowledge is the idea of proofs. If you get a\ndegree in computer science, you’ll engage with proofs quite a lot. I even\ntook a class on logic in the philosophy department as part of my degree.</p>\n<p>I don’t intend to give you a full introduction to the idea of proofs in this\nblog post, but there’s some high-level concepts that are useful to make sure\nwe’re in the same page about before we go forward.</p>\n<h3 id=\"aristotelian-syllogisms\">Aristotelian syllogisms</h3>\n<p>Here is a very classic example of a form of reasoning called a “syllogism”,\ngiven by Aristotle in 350 BCE:</p>\n<ol>\n<li>All men are mortal.</li>\n<li>Socrates is a man.</li>\n<li>Therefore, Socrates is mortal.</li>\n</ol>\n<p>These first two lines are called “propositions,” and the third is a conclusion.\nWe can base our conclusion on a logical relationship between the information given\nto us by the propositions.</p>\n<p>But how do we know propositions are true? Are all men mortal? What’s important\nhere is that we, for the purposes of our proof, assume that propositions are\ntrue. And we do this because, on some level, we cannot know everything. And so\nto begin, we have to start somewhere, and make some assumptions about what we\nknow. It’s true that later, we may discover a fact that disproves our\nproposition, and now our proof no longer works. But that’s just the way that\nthe world works. It doesn’t prevent these sorts of proofs from being useful to\nhelp us gain knowledge about the world and how it works, as best as we can tell\nat the current time.</p>\n<p>So on some level, this is also why Ruby is a memory safe language even though a\nC extension can segfault: there’s always some kind of things that we have to\nassume are true. Memory safe languages are ones where the amount of code we have\nto assume is memory safe, rather than is guaranteed to be by the semantics, is\nsmall, and preferably indicated in the code itself. Put another way, the amount\nof code we need to trust is memory safe is large in a memory unsafe language,\nand small in a memory safe language, rather than zero.</p>\n<h3 id=\"logic-and-programs\">Logic and programs</h3>\n<p>As time went on, so did our understanding of logic. And, of course, we even have\ncompeting logics! And then this gets fun, because terms can mean something\nslightly different. For example, in more recent logics, we’d call something like\n“All men are mortal” to be an <em>axiom</em>, rather than a proposition. Same idea:\nit’s something that we accept without proof.</p>\n<p>As computers appeared, people sought to apply the rules for mathematical logic\nonto them. We even call circuits that carry out classic logical operations\n“logic gates.” Number theory and logic were foundational to making computers\nwork. And so, once high level languages appeared on the scene, there was\ninterest in applying these mathematical tools to understanding programs as well.\nThis discipline is called “formal verification,” and the general idea is to\ndescribe various properties we wish a system to have, and then use formal\nmethods from mathematics to demonstrate that this is true.</p>\n<p>This area of study is very deep, and I don’t plan to cover the vast majority of\nit here. However, I do want to pursue one particular thread in this area.</p>\n<h3 id=\"hoare-logic\">Hoare Logic</h3>\n<p><a href=\"https://dl.acm.org/doi/10.1145/363235.363259\">“Hoare Logic”</a> is, well:</p>\n<blockquote>\n<p>In this paper an attempt is made to explore the logical foundations of\ncomputer programming by use of techniques which were first applied in the\nstudy of geometry and have later been extended to other branches of\nmathematics. This involves the elucidation of sets of axioms and rules of\ninference which can be used in proofs of the properties of computer programs.\nExamples are given of such axioms and rules, and a formal proof of a simple\ntheorem is displayed. Finally, it is argued that important advantage, both\ntheoretical and practical, may follow from a pursuance of these topics.</p>\n</blockquote>\n<p>Incidentally, C. A. R. Hoare, the author of this paper, and Graydon Hoare, the\ncreator of Rust, are unrelated.</p>\n<p>How does Hoare logic work? Yet again, not going to cover all of it, but the\ngeneral idea is this: In order to figure out if a program does what it is\nsupposed to do, we need to be able to reason about the state of the program\nafter execution happens. And so we need to be able to describe the state before,\nand its relationship to the state after. And so you get this notation:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>P { Q } R</span></span></code></pre>\n<p>P is a precondition, Q is a program, and R is the result of the program\nexecuting with those preconditions.</p>\n<p>But most programs have more than one statement. So how could we model this?\nHoare gives us the Rule of Composition:</p>\n<p>If <code>P { Q1 } R1</code> and <code>R1 { Q2 } R</code> then <code>P { Q1; Q2 } R</code>.</p>\n<p>This allows us to build up a program by proving each statement in turn.</p>\n<p>Hoare logic is very neat, and I’ve only scratched the surface here. People did a\nlot of work to extend Hoare logic to include more and more aspects of programs.\nBut then, something else happened.</p>\n<h3 id=\"separation-logic\">Separation Logic</h3>\n<p>In 2002, <a href=\"https://www.cs.cmu.edu/~jcr/seplogic.pdf\">Separation Logic: A Logic for Shared Mutable Data Structures</a>\nwas published.</p>\n<blockquote>\n<p>In joint work with Peter O’Hearn and others, based on early ideas of Burstall,\nwe have developed an extension of Hoare logic that permits reasoning about\nlow-level imperative programs that use shared mutable data structure.</p>\n</blockquote>\n<p>Hmm, shared mutable data structures? Where have I heard that before…</p>\n<p>Let’s see what they have to say:</p>\n<blockquote>\n<p>The problem faced by these approaches is that the correctness of a program\nthat mutates data structures usually depends upon complex restrictions on the\nsharing in these structures.</p>\n</blockquote>\n<p>For sure. Well, what are we to do about this?</p>\n<blockquote>\n<p>The key to avoiding this difficulty is to introduce a novel\nlogical operation <code>P * Q</code>, called separating conjunction (or\nsometimes independent or spatial conjunction), that asserts\nthat P and Q hold for disjoint portions of the addressable\nstorage.</p>\n</blockquote>\n<p>What disjoint portions of addressable storage might we care about?</p>\n<blockquote>\n<p>Our intent is to capture the low-level character of machine language. One can\nthink of the store as describing the contents of registers, and the heap as\ndescribing the contents of an addressable memory.</p>\n</blockquote>\n<p>Pretty useful!</p>\n<p>Before we talk a bit about how separation logic works, consider this paragraph\non why it’s named as such:</p>\n<blockquote>\n<p>Since these logics are based on the idea that the structure of an assertion\ncan describe the separation of storage into disjoint components,we have come\nto use the term separation logics, both for the extension of predicate\ncalculus with the separation operators and the resulting extension of Hoare\nlogic. A more precise name might be storage separation logics, since it is\nbecoming apparent that the underlying idea can be generalized to describe the\nseparation of other kinds of resources.</p>\n</blockquote>\n<p>The plot thickens.</p>\n<p>Anyway, in Separation Logic, we use slightly different notation than Hoare Logic:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>{ P } C { Q }</span></span></code></pre>\n<p>This says that we start with the precondition <code>P</code>, and if the program <code>C</code>\nexecutes, it will not have undefined behavior, and if it terminates, <code>Q</code> will\nhold.</p>\n<p>Furthermore, there is the “frame rule”, which I am going to butcher the notation\nfor because I haven’t bothered to install something to render math correctly\njust for this post:</p>\n<p>If <code>{ p } c { q }</code></p>\n<p>then <code>{ p * r } c { q * r }</code></p>\n<p>where no free variable in <code>r</code> is modified by <code>c</code>.</p>\n<p>Why is it interesting to add something to both sides of an equation, in a sense?\nWell what this gives us the ability to do is add any predicates about parts of\nthe program that <code>c</code> doesn’t modify or mutate. You might think of <code>&mut T</code> or\neven just ownership in general: we can reason about just these individual parts\nof a program, separately from the rest of it. In other words, we have some\nfoundational ideas for ownership and even bits of borrowing, and while this original\npaper doesn’t involve concurrency, eventually Concurrent Separation Logic would become\na thing as well.</p>\n<p>I think the paper explains why the frame rule matters better than I can:</p>\n<blockquote>\n<p>Every valid specification <code>{p} c {q}</code> is “tight” in the sense that every cell\nin its footprint must either be allocated by <code>c</code> or asserted to be active by\n<code>p</code>; “locality” is the opposite property that everything asserted to be active\nbelongs to the footprint. The role of the frame rule is to infer from a local\nspecification of a command the more global specification appropriate to the\nlarger footprint of an enclosing command.</p>\n</blockquote>\n<p>What this gives us is something called “local reasoning,” and local reasoning\nis <em>awesome</em>. Before I talk about that, I want to leave you with one other\nvery interesting paragraph:</p>\n<blockquote>\n<p>Since our logic permits programs to use unrestricted address arithmetic,there\nis little hope of constructing any general-purpose garbage collector. On the\nother hand, the situation for the older logic, in which addresses are disjoint\nfrom integers, is more hopeful. However, it is clear that this logic permits\none to make assertions, such as “The heap contains two elements” that might be\nfalsified by the execution of a garbage collector, even though, in any\nrealistic sense, such an execution is unobservable.</p>\n</blockquote>\n<p>Anyway. Let’s talk about local vs global analysis.</p>\n<h2 id=\"global-vs-local-analysis\">Global vs local analysis</h2>\n<p>Proving things about programs isn’t easy. But one thing that can make it even\nharder is that, for many properties of many programs you’d want to analyze, you\nneed to do a <em>global analysis</em>. As an example, let’s use Ruby. Ruby is an\nincredibly dynamic programming language, which makes it fairly resistant to\nstatic analysis.</p>\n<h3 id=\"global-analysis\">Global analysis</h3>\n<p>Here is a Ruby program. Do you know if this program executes successfully?</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"ruby\"><code><span class=\"line\"><span style=\"color:#F97583\">class</span><span style=\"color:#B392F0\"> Foo</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> def</span><span style=\"color:#B392F0\"> self.bar</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"baz\"</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> end</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">p</span><span style=\"color:#79B8FF\"> Foo</span><span style=\"color:#E1E4E8\">.</span><span style=\"color:#B392F0\">bar</span></span></code></pre>\n<p>Yeah, it prints <code>baz</code>. But what about this Ruby program?</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"ruby\"><code><span class=\"line\"><span style=\"color:#F97583\">class</span><span style=\"color:#B392F0\"> Foo</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> def</span><span style=\"color:#B392F0\"> self.bar</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\"> \"baz\"</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> end</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">end</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">require</span><span style=\"color:#9ECBFF\"> \"foo_ext.rb\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">p</span><span style=\"color:#79B8FF\"> Foo</span><span style=\"color:#E1E4E8\">.</span><span style=\"color:#B392F0\">bar</span></span></code></pre>\n<p>We can’t know. <code>foo_ext.rb</code> may contain no relevant code, but it also might\ninclude something like this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"ruby\"><code><span class=\"line\"><span style=\"color:#79B8FF\">Foo</span><span style=\"color:#E1E4E8\">.</span><span style=\"color:#B392F0\">class_eval</span><span style=\"color:#E1E4E8\"> { </span><span style=\"color:#F97583\">undef</span><span style=\"color:#79B8FF\"> :bar</span><span style=\"color:#E1E4E8\"> }</span></span></code></pre>\n<p>In which case, when we try and call <code>Foo.bar</code>, it no longer exists:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span><anonymous>': eval:13:in `block in <main>': undefined method `bar' for class `Foo' (NameError)</span></span>\n<span class=\"line\"><span>eval:13:in `class_eval'</span></span>\n<span class=\"line\"><span>eval:13:in `<main>'</span></span>\n<span class=\"line\"><span>-e:in `eval'</span></span></code></pre>\n<p>Ouch. So in this case, we’re going to need the whole code of our program in\norder to be able to figure out what’s going on here.</p>\n<p>Incidentally, <a href=\"https://sorbet.org/\">Sorbet</a> is a very cool project to add type checking to\nRuby. They do require access to the entire Ruby source code in order to do their\nanalysis. But they also made some decisions to help make it more tractable; if\nyou try Sorbet out on the web, the type checker is <em>fast</em>. What happens when you\ntry the above code with Sorbet?</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>editor.rb:5: Unsupported method: undef https://srb.help/3008</span></span>\n<span class=\"line\"><span> 5 |Foo.class_eval { undef :bar }</span></span>\n<span class=\"line\"><span> ^^^^^^^^^^</span></span></code></pre>\n<p>This is a very fair tradeoff! It’s very common with various forms of analysis to\nchoose certain restrictions in order to make what they want to do tractable. Ruby\nhas so much evil in it, just not supporting some of the more obscure things is\ncompletely fair, in my opinion.</p>\n<h3 id=\"local-analysis\">Local analysis</h3>\n<p>The opposite of global analysis is, well, local analysis. Let’s consider a Rust\nequivalent of our Ruby:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Foo</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">impl</span><span style=\"color:#B392F0\"> Foo</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> bar</span><span style=\"color:#E1E4E8\">() </span><span style=\"color:#F97583\">-></span><span style=\"color:#B392F0\"> String</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> String</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">from</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"baz\"</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> dbg!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#B392F0\">Foo</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">bar</span><span style=\"color:#E1E4E8\">());</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>Can we know if this program works? Sure, everything is here. Now, if we tried the\nsame trick as the Ruby code, we know that it would work, because Rust doesn’t have\nthe ability to remove the definition of <code>bar</code> like Ruby does. So let’s try something\nelse:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Foo</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">impl</span><span style=\"color:#B392F0\"> Foo</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> bar</span><span style=\"color:#E1E4E8\">() </span><span style=\"color:#F97583\">-></span><span style=\"color:#B392F0\"> String</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // body hidden</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> dbg!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#B392F0\">Foo</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">bar</span><span style=\"color:#E1E4E8\">());</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>Can we know if <code>main</code> is properly typed here? We can, even though we know\nnothing about the body. That’s because <code>dbg!</code> takes any value that implements\nthe <code>Debug</code> trait, and we know that <code>Foo::bar()</code> returns <code>String</code>, which\nimplements <code>Debug</code>. Typechecking <code>main</code> is local to main’s implementation, we\ndon’t need to look into the bodies of any function it calls to determine if it’s\nwell-typed. We only need to know their signatures. If we didn’t require <code>bar</code> to\nhave a type signature, we’d have to peek into its body to figure out what it\nreturns, and so on for any function that <code>bar</code> calls in its body. Instead of\nthis arbitrarily deep process, we can just look at the signature and be done\nwith it.</p>\n<h2 id=\"composability-of-proofs\">Composability of proofs</h2>\n<p>So why is local analysis so helpful? The first reason is either speed or\nscalability, depending on how you want to look at it. If you are running global\nanalysis checks, they will get more expensive the larger your codebase, since\nthey will require checking the entire thing to work. Whereas local analysis only\nrequires a local context, it doesn’t get any more expensive when you add more\ncode to your project, only when you change that local context. So when you’re\ntrying to scale checks up to larger projects, local analysis is crucial.</p>\n<p>But I also like to personally think about it as a sort of abstraction. That is,\nglobal analysis is a leaky abstraction: changes in one part of the codebase can\ncascade into other parts. Remember this line about the frame rule?</p>\n<blockquote>\n<p>The role of the frame rule is to infer from a local specification of a command\nthe more global specification appropriate to the larger footprint of an\nenclosing command.</p>\n</blockquote>\n<p>If we have local reasoning, we can be sure that changes locally don’t break out\nof the boundaries of those changes. As long as the types of our function don’t\nchange, we can mess with the body as much as we want, and we know that the rest\nof the analysis of the program is still intact. This is really useful, in the\nsame way that abstractions are useful when building programs more generally.</p>\n<h2 id=\"rustbelt\">Rustbelt</h2>\n<p>Okay, we have taken a real deep dive here. What’s this all have to do with\nunsafe Rust?</p>\n<p>Well, seven years ago, <a href=\"https://plv.mpi-sws.org/rustbelt/popl18/paper.pdf\">RustBelt: Securing the Foundations of the Rust\nProgramming Language</a> by Ralf Jung, Jaques-Henri Jourdan, Robbert\nKrebbers, and Derek Dreyer, was published:</p>\n<blockquote>\n<p>In this paper, we give the first formal (and machine-checked) safety proof for\na language representing a realistic subset of Rust.</p>\n</blockquote>\n<p>But it’s more exciting than that:</p>\n<blockquote>\n<p>Our proof is extensible in the sense that, for each new Rust library that uses\nunsafe features, we can say what verification condition it must satisfy in\norder for it to be deemed a safe extension to the language.</p>\n</blockquote>\n<p>Very good!</p>\n<p>The paper begins by talking about why verifying Rust is such a challenge:</p>\n<blockquote>\n<p>Consequently, to overcome this restriction, the implementations of Rust’s\nstandard libraries make widespread use of unsafe operations, such as “raw\npointer” manipulations for which aliasing is not tracked. The developers of\nthese libraries claim that their uses of unsafe code have been properly\n“encapsulated”, meaning that if programmers make use of the APIs exported by\nthese libraries but otherwise avoid the use of unsafe operations themselves,\nthen their programs should never exhibit any unsafe/undefined behaviors. In\neffect, these libraries extend the expressive power of Rust’s type system by\nloosening its ownership discipline on aliased mutable state in a modular,\ncontrolled fashion</p>\n</blockquote>\n<p>This is basically the question asked at the start of this post: can you honestly\nsay that a function is safe if it contains <code>unsafe</code> code as part of its\nimplementation?</p>\n<p>They go on to describe the first challenge for the project: choosing the correct\nlogic to model Rust in (some information cut for clarity):</p>\n<blockquote>\n<p>Iris is a language-generic framework for higher-order concurrent separation\nlogic which in the past year has been equipped with tactical support for\nconducting machine-checked proofs of programs in Coq. By virtue of being a\nseparation logic, Iris comes with built-in support for reasoning modularly\nabout ownership. Moreover, the main selling point of Iris is its support for\nderiving custom program logics for different domains using only a small set of\nprimitive mechanisms. In the case of RustBelt, we used Iris to derive a novel\nlifetime logic, whose primary feature is a notion of borrow propositions that\nmirrors the “borrowing” mechanism for tracking aliasing in Rust.</p>\n</blockquote>\n<p>Separation logic!</p>\n<p>So how does this work? There are three parts to the proof. I’m going to\nsummarize them because I’m already quoting extensively, please go read the paper\nif you want the exact details, it is well written.</p>\n<ol>\n<li>Verify that the typing rules are sound when interpreted semantically.</li>\n<li>Verify that if a program is well typed semantically, it does not exhibit\nunsafe behavior.</li>\n<li>For any library that uses unsafe, check that it satisfies the semantics that\nits interface requires.</li>\n</ol>\n<p>If all three of these things are true, then the program is safe to execute. This\nis also exciting because, thanks to 3, when someone writes new unsafe code, they\ncan get RustBelt to let them know which properties they need to satisfy to make\nsure that their unsafe code doesn’t cause problems.</p>\n<h3 id=\"weaknesses\">Weaknesses</h3>\n<p>Of course, there is more to do following up on RustBelt:</p>\n<blockquote>\n<p>We do not model (1) more relaxed forms of atomic accesses, which Rust uses for\nefficiency in libraries like Arc; (2) Rust’s trait objects (comparable to\ninterfaces in Java), which can pose safety issues due to their interactions\nwith lifetimes; or (3) stack unwinding when a panic occurs, which causes\nissues similar to exception safety in C++.</p>\n</blockquote>\n<p>However, the results were good enough to find some soundness bugs in the\nstandard library, and if I remember correctly, widen one of the APIs in a sound\nmanner as well. Another interesting thing about RustBelt is that it was\npublished before Rust gained Non-lexical lifetimes, however, it modeled them,\nsince folks knew they were coming down the pipeline.</p>\n<h2 id=\"the-theoretical-vs-the-empirical\">The theoretical vs the empirical</h2>\n<p>So, we have some amount of actual proof that unsafe code in Rust doesn’t\nundermine Rust’s guarantees: what it does is allow us to extend Rust’s\nsemantics, just like FFI would allow us to extend a program written in\na language that supports it. As long as our unsafe code is semantically\ncorrect, then we’re all good.</p>\n<p>… but what if it’s not semantically correct?</p>\n<h3 id=\"when-things-go-wrong\">When things go wrong</h3>\n<p>At some point maybe I’ll write a post about the specifics here, but this post is\nalready incredibly long. I’ll cut to the chase: you can get undefined behavior,\nwhich means anything can happen. You don’t have a real Rust program. It’s bad.</p>\n<p>But at least it’s scoped to some degree. However, many people get this scope a\nbit wrong. They’ll say something like:</p>\n<blockquote>\n<p>You only need to verify the unsafe blocks.</p>\n</blockquote>\n<p>This is true, but also a bit misleading. Before we get into the unsafe stuff, I\nwant you to consider an example. Is this Rust code okay?</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Foo</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> bar</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> usize</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">impl</span><span style=\"color:#B392F0\"> Foo</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> set_bar</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&mut</span><span style=\"color:#79B8FF\"> self</span><span style=\"color:#E1E4E8\">, bar</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> usize</span><span style=\"color:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> self</span><span style=\"color:#F97583\">.</span><span style=\"color:#E1E4E8\">bar </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> bar;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>No unsafe shenanigans here. This code is perfectly safe, if a bit useless.</p>\n<p>Let’s talk about unsafe. The canonical example of unsafe code being affected\noutside of unsafe itself is the implementation of <code>Vec<T></code>. Vecs look something\nlike this (the real code is different for reasons that don’t really matter in\nthis context):</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Vec</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">T</span><span style=\"color:#E1E4E8\">> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> ptr</span><span style=\"color:#F97583\">:</span><span style=\"color:#F97583\"> *mut</span><span style=\"color:#B392F0\"> T</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> len</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> usize</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> cap</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> usize</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>The pointer is to a bunch of <code>T</code>s in a row, the length is the current number of <code>T</code>s\nthat are valid, and the capacity is the total number of <code>T</code>s. The length and the\ncapacity are different so that memory allocation is amortized; the capacity is\nalways greater than or equal to the length.</p>\n<p>That property is very important! If the length is greater than the capacity,\nwhen we try and index into the Vec, we’d be accessing random memory.</p>\n<p>So now, this function, which is the same as <code>Foo::set_bar</code>, is no longer okay:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">impl</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">T</span><span style=\"color:#E1E4E8\">> </span><span style=\"color:#B392F0\">Vec</span><span style=\"color:#E1E4E8\"><</span><span style=\"color:#B392F0\">T</span><span style=\"color:#E1E4E8\">> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> fn</span><span style=\"color:#B392F0\"> set_len</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&mut</span><span style=\"color:#79B8FF\"> self</span><span style=\"color:#E1E4E8\">, len</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> usize</span><span style=\"color:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\"> self</span><span style=\"color:#F97583\">.</span><span style=\"color:#E1E4E8\">len </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> len;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>This is because the unsafe code inside of other methods of <code>Vec<T></code> need to be\nable to rely on the fact that <code>len <= capacity</code>. And so you’ll find that\n<code>Vec<T>::set_len</code> in Rust is marked as unsafe, even though it doesn’t contain\nunsafe code.</p>\n<p>And this is why the module being the privacy boundary matters: the only way to\nset len directly in safe Rust code is code within the same privacy boundary as\nthe <code>Vec<T></code> itself. And so, that’s the same module, or its children.</p>\n<p>This is still ultimately better than any line of code in the whole codebase, but\nit’s not quite as small as you may think at first.</p>\n<h3 id=\"how-can-we-check-that-things-havent-gone-wrong\">How can we check that things haven’t gone wrong?</h3>\n<p>Okay, so while RustBelt could give you some idea if your code is correct, I doubt\nyou’re about to jump into Coq and write some proofs. What can you do? Well,\nRust provides a tool, <code>miri</code>, that can interpret your Rust code and let you know\nif you’ve violated some unsafe rules. But it’s not complete, that is, miri can\ntell you if your code is wrong, but it cannot tell you if your code is right. It’s\nstill quite useful.</p>\n<p>To get miri, you can install it with Rust nightly:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>$ rustup +nightly component add miri</span></span></code></pre>\n<p>And then run your test suite under miri with</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>$ cargo +nightly miri test</span></span></code></pre>\n<p>For more details, consult <a href=\"https://github.com/rust-lang/miri\">miri’s documentation</a>.</p>\n<h3 id=\"do-things-go-wrong\">Do things go wrong?</h3>\n<p>Do people write good unsafe code, or bad?</p>\n<p>Well, the obvious answer to the above question is “yes.” Anyone who says all\nunsafe code in Rust is sound is obviously lying. But the absolute isn’t what’s\ninteresting here, it’s the shade of grey. Is unsafe pervasive? Does Rust have\nmemory related bugs at the same rate as other MSLs, or closer to memory-unsafe\nlanguages?</p>\n<p>It’s still sort of early days here, but we do have some preliminary results that\nare helpful. In 2022, <a href=\"https://security.googleblog.com/2022/12/memory-safe-languages-in-android-13.html\">Google reported</a> that:</p>\n<blockquote>\n<p>To date, there have been zero memory safety vulnerabilities discovered in\nAndroid’s Rust code.</p>\n</blockquote>\n<p>I am very interested to see a more up to date report about how that’s changed,\nbut as they say:</p>\n<blockquote>\n<p>We don’t expect that number to stay zero forever, but given the volume of new\nRust code across two Android releases, and the security-sensitive components\nwhere it’s being used, it’s a significant result. It demonstrates that Rust is\nfulfilling its intended purpose of preventing Android’s most common source of\nvulnerabilities. Historical vulnerability density is greater than 1/kLOC (1\nvulnerability per thousand lines of code) in many of Android’s C/C++ components\n(e.g. media, Bluetooth, NFC, etc). Based on this historical vulnerability\ndensity, it’s likely that using Rust has already prevented hundreds of\nvulnerabilities from reaching production.</p>\n</blockquote>\n<p>We’ll see if these results replicate themselves, in the future, and also outside\nof Google. But it at least looks like in practice, most unsafe code has not led\nto problems. At least so far!</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lkqjlfxypk2d\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Does unsafe undermine <a target=\"_blank\" href=\"https://bsky.app/hashtag/rustlang\" class=\"hashtag s-1lecfnd\">#rustlang</a> 's guarantees? <a target=\"_blank\" href=\"https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/does...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/does-unsafe-undermine-rusts-guarantees/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Does unsafe undermine Rust's guarantees?</p> <p class=\"description s-rtbqd8\"></p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-03-19T15:43:41.710Z\" class=\"date s-hik11q\">March 19, 2025 at 3:43 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"138 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>138</span></span> <span class=\"stat s-hik11q\" title=\"41 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>41</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lkqjlfxypk2d\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 17 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/choosing-languages/",
"title": "Choosing Languages",
"description": "I tried to write this post yesterday, but I didn’t like my approach there. Last night, I had a conversation with one of my best friends about it, and he encouraged me to write this, but in a different way. I think this way is better, so thanks, dude. The other day, Microsoft…",
"url": "https://steveklabnik.com/writing/choosing-languages/",
"published": "2025-03-13T00:00:00.000Z",
"updated": "2025-03-13T00:00:00.000Z",
"content": "<p>I tried to write this post yesterday, but I didn’t like my approach there. Last\nnight, I had a conversation with one of my best friends about it, and he\nencouraged me to write this, but in a different way. I think this way is better,\nso thanks, dude.</p>\n<hr/>\n<p>The other day, Microsoft announced <a href=\"https://devblogs.microsoft.com/typescript/typescript-native-port/\">that they are re-writing the TypeScript\ncompiler in Go</a>. A lot of people had a lot of feelings about this. I did\ntoo, but in a different way than many other people.</p>\n<p>The biggest questions people had were “Why not C#?” and “Why not Rust?”. To\nbe clear, I do think that asking why someone chooses a programming language\ncan be valuable; as professionals, we need to make these sorts of decisions\non a regular basis, and seeing how others make those decisions can be useful,\nto get an idea of how other people think about these things.</p>\n<p>But it can also… I dunno. Sometimes, I think people have little imagination\nwhen it comes to choosing a particular technology stack.</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lk6p4clry22p\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Here’s my opinion on the new typescript compiler:\n\nYou should write programs in the language you want to.</p> <time datetime=\"2025-03-12T13:34:42.008Z\" class=\"date s-hik11q\">March 12, 2025 at 1:34 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"385 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>385</span></span> <span class=\"stat s-hik11q\" title=\"43 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>43</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lk6p4clry22p\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 22 replies on Bluesky</span></a></div></div></div> </bluesky-post>\n<p>It is true that sometimes, there are hard requirements that means that choosing\na particular technology is inappropriate. But I’ve found that often, these\nsorts of requirements are often more <em>contextually</em> requirements than ones that\nwould be the case in the abstract. For example, <a href=\"https://en.wikipedia.org/wiki/Lisp_machine\">you can write an OS in\nLisp</a> if you want. But in practice, you’re probably not working with\nthis hardware, or willing to port the environment to bare metal.</p>\n<p>But it’s also the case that you often don’t know what others’ contextual\nsituation actually is. I would have never guessed the main reason that Microsoft\nchose Go for, which is roughly “we are porting the existing codebase, rather\nthan re-writing it, and our existing code looks kinda like Go, making it easy to\nport.” That’s a very good reason! I hadn’t really thought about the differences\nbetween porting and re-writing in this light before, and it makes perfect sense\nto me.</p>\n<p>At the same time,</p>\n<bluesky-post src=\"at://did:plc:4eukmtg5kmyjmp6qw3xkpite/app.bsky.feed.post/3lk6t4a4xa22w\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"post s-12mzi62\"><svg class=\"logo s-12mzi62\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg> <div class=\"aside s-12mzi62\"><a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite\" class=\"avatar-wrapper s-12mzi62\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:4eukmtg5kmyjmp6qw3xkpite/bafkreie5tfrdul6a3oyl34g6ughguakxf6gxc7ua6c2n4ysuggx4cak6ya\" alt=\"\" class=\"avatar s-12mzi62\"/></a> <div class=\"line s-12mzi62\"></div></div> <div class=\"main s-12mzi62\"><div class=\"meta s-12mzi62\"><a href=\"https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite\" target=\"_blank\" class=\"name-wrapper s-12mzi62\"><bdi class=\"display-name-wrapper s-12mzi62\"><span class=\"display-name s-12mzi62\">Jake Bailey</span></bdi> <span class=\"handle s-12mzi62\">@jakebailey.dev</span></a> <span aria-hidden=\"true\" class=\"dot s-12mzi62\">·</span> <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite/post/3lk6t47wxt22w\" title=\"March 12, 2025 at 2:46 PM\" class=\"date s-12mzi62\"><time datetime=\"2025-03-12T14:46:14.196Z\">Mar 12, 2025</time></a></div> <p class=\"context s-12mzi62\">Replying to <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" dir=\"auto\" class=\"s-12mzi62\">Steve Klabnik</a></p> <p class=\"rich-text is-small s-1lecfnd\">Funnily I'm pretty sure I'm the only team member who could actually claim their favorite language is Go, but I tried so hard to prove rust out early on. It sure wasn't my decision either, Go just kept working the best as the hacking continued</p> </div></div><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:4eukmtg5kmyjmp6qw3xkpite/bafkreie5tfrdul6a3oyl34g6ughguakxf6gxc7ua6c2n4ysuggx4cak6ya\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Jake Bailey</span></bdi> <span class=\"handle s-hik11q\">@jakebailey.dev</span></a> </div> <p class=\"rich-text is-large s-1lecfnd\">I think it's going well, though, besides the terrifying infohazard that is the \"Why Go?\" GitHub discussion thread, which contains some of the weirdest and most frustrating takes I've ever seen</p> <time datetime=\"2025-03-12T14:46:14.197Z\" class=\"date s-hik11q\">March 12, 2025 at 2:46 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"37 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>37</span></span> <span class=\"stat s-hik11q\" title=\"1 repost\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>1</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite/post/3lk6t4a4xa22w\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>\n<p>people are going <em>way</em> overboard here.</p>\n<bluesky-post src=\"at://did:plc:x2p6i7jmdhk4xj2ej3rflp7a/app.bsky.feed.post/3lk6was5n5227\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"post s-12mzi62\"><svg class=\"logo s-12mzi62\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg> <div class=\"aside s-12mzi62\"><a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite\" class=\"avatar-wrapper s-12mzi62\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:4eukmtg5kmyjmp6qw3xkpite/bafkreie5tfrdul6a3oyl34g6ughguakxf6gxc7ua6c2n4ysuggx4cak6ya\" alt=\"\" class=\"avatar s-12mzi62\"/></a> <div class=\"line s-12mzi62\"></div></div> <div class=\"main s-12mzi62\"><div class=\"meta s-12mzi62\"><a href=\"https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite\" target=\"_blank\" class=\"name-wrapper s-12mzi62\"><bdi class=\"display-name-wrapper s-12mzi62\"><span class=\"display-name s-12mzi62\">Jake Bailey</span></bdi> <span class=\"handle s-12mzi62\">@jakebailey.dev</span></a> <span aria-hidden=\"true\" class=\"dot s-12mzi62\">·</span> <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite/post/3lk6t4a4xa22w\" title=\"March 12, 2025 at 2:46 PM\" class=\"date s-12mzi62\"><time datetime=\"2025-03-12T14:46:14.197Z\">Mar 12, 2025</time></a></div> <p class=\"context s-12mzi62\">Replying to <a target=\"_blank\" href=\"https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite\" dir=\"auto\" class=\"s-12mzi62\">Jake Bailey</a></p> <p class=\"rich-text is-small s-1lecfnd\">I think it's going well, though, besides the terrifying infohazard that is the \"Why Go?\" GitHub discussion thread, which contains some of the weirdest and most frustrating takes I've ever seen</p> </div></div><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:x2p6i7jmdhk4xj2ej3rflp7a\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:x2p6i7jmdhk4xj2ej3rflp7a/bafkreifxfhmm3eggyodwvoqxsh7iawqkpfhuh6opfac3e27jfzlnx7dikm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:x2p6i7jmdhk4xj2ej3rflp7a\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Michael Pratt</span></bdi> <span class=\"handle s-hik11q\">@prattmic.com</span></a> </div> <p class=\"rich-text is-large s-1lecfnd\">I peeked briefly yesterday (and quickly ran away). Just took another look. 370 comments in 24 hours?? Wow. The proposal to add generics to Go (mildly contentious, to put it lightly) only got ~450 comments over a few months.</p> <time datetime=\"2025-03-12T15:42:28.732Z\" class=\"date s-hik11q\">March 12, 2025 at 3:42 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"11 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>11</span></span> <span class=\"stat s-hik11q\" title=\"0 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>0</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:x2p6i7jmdhk4xj2ej3rflp7a/post/3lk6was5n5227\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 1 reply on Bluesky</span></a></div></div></div> </bluesky-post>\n<p>There’s some sort of balance here to be struck. But at the same time, I hate\n“choose the right tool for the job” as a suggestion. Other than art projects,\nhas anyone ever deliberately chosen the wrong tool? Often, something that\nseems like the wrong tool is chosen simply because the contextual requirements\nweren’t obvious to you, the interlocutor.</p>\n<p>I think there’s two other reasons why this situation is making me feel\ncomplicated things. The first is RIIR, and the second is a situation from a long\ntime ago that changed my life.</p>\n<p>Many of you have probably heard of the “re-write it in Rust” meme. The idea is\nthat there’s this plague of programmers who are always suggesting that every\nsingle thing needs to be re-written in Rust. While I am sure this happens from\ntime to time, I have yet to see real evidence that it is actually widespread. I\nalmost even wrote a blog post about this, trying to actually quantify the effect\nhere. But on some level, perception is reality, and if people believe it’s true,\nit’s real on some level, even if that’s not actually the case. Regardless of the\ntruth of the number of people who suggest this, on every thread about Rust,\npeople end up complaining about this effect, and so it has an effect on the\noverall discourse regardless. And while I don’t believe that this effect is real\noverall, it absolutely has happened in this specific situation. A bunch of\npeople have been very aggressively suggesting that this should have been in\nRust, and not Go, and I get where they’re coming from, but also: really? Go is\nan absolutely fine choice here. It’s fine. Why do you all have to reinforce a\nstereotype? It’s frustrating.</p>\n<p>The second one is something that’s been on my mind for over a decade, but I’ve\nonly started talking about it in places other than conversations with close\nfriends. Long-time readers may remember <a href=\"/writing/node/\">that one time where I was an\nasshole</a>. I mean, I’ve been an asshole more than once in my life, but\nthis one was probably the time that affected me the most. The overall shape of\nthe situation is this:</p>\n<ol>\n<li>Someone wrote <code>grep</code>, but in Node.</li>\n<li>I made fun of it on the internet, because why would someone write a CLI tool in Node?</li>\n<li>The author found out that I did and felt bad about it.</li>\n</ol>\n<p>Looking back at my apology, I don’t think it’s necessarily the best one. It was\nbetter than the first draft or two of what I wrote, but I’ve gotten better at\napologizing since then. Regardless, this incident changed the trajectory of my\nlife, and if you happen to read this, Heather, I am truly, unequivocally, sorry.</p>\n<p>There was a couple of things that happened here. The first is just simply, in\n2013, I did not understand that the things I said had meaning. I hate talking\nabout this because it makes me seem more important than I am, but it’s also\nimportant to acknowledge. I saw myself at the time as just Steve, some random\nguy. If I say something on the internet, it’s like I’m talking to a friend in\nreal life, my words are just random words and I’m human and whatever. It is what\nit is.</p>\n<p>But at that time in my life, that wasn’t actually the case. I was on the Rails\nteam, I was speaking at conferences, and people were reading my blog and tweets.\nI was an “influencer,” for better or worse. But I hadn’t really internalized\nthat change in my life yet. And so I didn’t really understand that if I\ncriticized something, it was something thousands of people would see. To me, I’m\njust Steve. This situation happened before we talked about “cancel culture” and\nall of that kind of stuff, but when the mob came for me, I realized: they were\nright, actually. I was being an asshole, and I should not have been. And I\nresolved myself to not being an asshole like that ever again.</p>\n<p>And to do that, I had to think long and hard about why I was like that. I love\nprogramming languages! I love people writing programs that they find\ninteresting! I don’t think that it’s stupid to write a tool in a language that\nI wouldn’t expect it to be written in! So why did I feel the reason to make fun\nof this in that situation?</p>\n<p>The answer? A pretty classic situation: the people I was hanging out with were\naffecting me, and in ways that, when I examined it, I didn’t like very much.</p>\n<p>You see, in the Rails world at the time, there was a huge <a href=\"https://blog.aurynn.com/2015/12/16-contempt-culture\">contempt\nculture</a> at the time, especially around JavaScript and Node. And, when\nyou’re around people who talk shit all the time, you find yourself talking shit\ntoo. And once I realized that, well, I wanted to stop it. But there was a big\nproblem: my entire professional, and most of my social, life was built around\nRuby and Rails. So if I wanted to escape that culture… what should I do?</p>\n<p>If you clicked on the link to my apology above, you probably didn’t make note of\nthe date: Jan 23 2013. Something had just happened to me, in December of 2012: I\nhad decided to check out this little programming language I had heard of called\nRust.</p>\n<p>One of the things that struck me about the “Rust community” at the time, which\nwas like forty people in an IRC room, was how nice they were. When I had trouble\ngetting Hello World to compile, and I typed <code>/join #rust</code>, I fully expected an\nRTFM and flames. But instead, I was welcomed with open arms. People were chill.\nAnd it was really nice.</p>\n<p>And so, a month and a day later, I had this realization about the direction my\nlife was heading, and how I wasn’t really happy about it. And I thought about\nhow much I enjoyed the Rust stuff so far… and combined with a few other\nfactors, maybe of which I’ll write about someday, this is when I decided that I\nwanted to make Rust a thing, and dedicated myself to achieving that.</p>\n<p>And part of that was being conscious about the culture, and how it developed,\nand the ways that it would evolve over time. I didn’t want Rust to end up the\nway that Rails had ended up. And that meant a few things, but first of all,\ntruly internalizing that I had to change. And understanding that I had to be\nintentional with what I did and said. And what that led to, among other things,\nwas a “we talk about Rust on its own merits, not by trash talking other languages”\nculture. (That’s also partially because of Nietzsche, but I’m already pretty far\nafield of the topic for this post, maybe someday.)</p>\n<p>Anyway, the Eternal September comes for us all. The “Rust Community,” if it ever\nwere a coherent concept, doesn’t really make sense any more. And it is by no\nmeans perfect. I have a lot of criticism about how things turned out. But I do\nthink that it’s not a coincidence that “Rust makes you trans,” for example, is\na meme. I am deeply proud of what we all built.</p>\n<p>So yeah, anyway: people choose programming languages for projects based on a\nvariety of reasons. And sure, there may be better or worse choices. But you\nprobably have a different context than someone else, and so when someone\nmakes a choice you don’t agree with, there’s no reason to be a jerk about it.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3lkblqoytls22\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">Choosing Languages <a target=\"_blank\" href=\"https://steveklabnik.com/writing/choosing-languages/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/choo...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/choosing-languages/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">Choosing Languages</p> <p class=\"description s-rtbqd8\"></p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-03-13T17:12:30.356Z\" class=\"date s-hik11q\">March 13, 2025 at 5:12 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"304 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>304</span></span> <span class=\"stat s-hik11q\" title=\"91 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>91</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3lkblqoytls22\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 29 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/a-happy-day-for-rust/",
"title": "A Happy Day for Rust",
"description": "A while back, I wrote A sad day for Rust about the Actix-web unsafe controversy. The short of it was, Actix-web was using some unsound unsafe code. When this was discovered, people responded pretty harshly, and the maintainer quit. Others picked up the work, but it still wasn’t…",
"url": "https://steveklabnik.com/writing/a-happy-day-for-rust/",
"published": "2025-03-05T00:00:00.000Z",
"updated": "2025-03-05T00:00:00.000Z",
"content": "<p>A while back, I wrote <a href=\"/writing/a-sad-day-for-rust/\">A sad day for Rust</a> about the Actix-web unsafe\ncontroversy. The short of it was, Actix-web was using some unsound unsafe code.\nWhen this was discovered, people responded pretty harshly, and the maintainer\nquit. Others picked up the work, but it still wasn’t great:</p>\n<blockquote>\n<p>I’m not sure where we go from here, and I’m not sure what we could have done\nto prevent this from happening. But I feel like this is a failure, and it’s set\nRust back a bit, and I’m just plain sad.</p>\n</blockquote>\n<p>While the Rust community surely isn’t perfect, today I’d like to share with you\na different story: one where there was some controversy, but it was handled in\nthe complete opposite way.</p>\n<p><a href=\"https://rustup.rs/\">Rustup</a> is the official installer and toolchain manager for Rust. It\nhas a much smaller team than Rust itself does, and releases on a much slower\nschedule.</p>\n<p>Back in August of last year, it was decided to <a href=\"https://github.com/rust-lang/rustup/pull/3985\">change some behavior</a>.\nThe details aren’t super important, what matters is that it’s a breaking change\nto a critical component of the ecosystem. I’m not here to say if this change is\ngood or bad; they had good motivations, there’s also good reasons to keep the\nprevious behavior, it’s one of those classic “you can’t make everyone happy”\nsort of deals.</p>\n<p>The team knew that they’d need to let people know that this had happened:</p>\n<blockquote>\n<p>Yes, you’ve understood it correctly, and this breakage is intentional: #3635 (comment)</p>\n<p>Before the actual release, we’ll definitely clearly communicate this change with\nthe community, and we’ll use the beta phase to collect user feedback.</p>\n</blockquote>\n<p>And they did <a href=\"https://internals.rust-lang.org/t/seeking-beta-testers-for-rustup-v1-28-0/22060\">make a post on internals</a> about the change, seeking\nfeedback. This also landed in a This Week in Rust, in my understanding.</p>\n<p>The problem is, not everyone reads internals, and not everyone reads This Week\nin Rust. And so yesterday, some folks woke up to a broken CI, leading to <a href=\"https://github.com/rust-lang/rustup/issues/4211\">this\nbug report</a> among other upset posts elsewhere on the internet.</p>\n<p>I have been on the other side of this before. It’s frustrating when you try and\ncommunicate something, and people don’t hear about it, and then they find out\nlater and get mad at you about it. I also remember the Actix debacle, and many\nother controversies in the broader open source ecosystem.</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3ljiqodr7q22f\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">at one point i questioned my desire to help people get into open source\n\nimage unrelated</p> <div class=\"embeds s-azdpbr\"><div class=\"image-embed is-bordered is-aligned s-t3k6fc\"><div class=\"single-item tl tr bl br is-standalone s-t3k6fc\" style=\"aspect-ratio: 1024/766\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreignn6nf4mlkc2yfpj3y4dwo5j3dyuod3gbcw4rolpcsca6ujbbo5y\" alt=\"a tweet by @primawesome\n\nMy neighbor told me coyotes keep eating his outdoor cats so I asked how many cats he has and he said he just goes to the shelter and gets a new cat afterwards so I said it sounds like he's just feeding shelter cats to coyotes and then his daughter started crying\" class=\"image s-t3k6fc\"/> <div class=\"placeholder s-t3k6fc\"></div></div></div></div> <time datetime=\"2025-03-03T20:04:06.711Z\" class=\"date s-hik11q\">March 3, 2025 at 8:04 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"1,156 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>1,156</span></span> <span class=\"stat s-hik11q\" title=\"86 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>86</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3ljiqodr7q22f\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 13 replies on Bluesky</span></a></div></div></div> </bluesky-post>\n<p>However, this time, things went differently. As you can see, the issue is very\nrespectful, and the comments are also generally respectful. The team responded\n<a href=\"https://github.com/rust-lang/rustup/issues/4211#issuecomment-2695620307\">graciously</a>, and decided to put in the work to <a href=\"https://blog.rust-lang.org/2025/03/04/Rustup-1.28.1.html\">release a new\nversion</a> to restore the previous behavior.</p>\n<p>I was kind of worried yesterday that this would end up being another Actix\nsituation. The Rust community has changed a lot over time, and while it’s still\npretty good overall, sometimes things can go poorly, just like with any random\ngroup of thousands of people on the internet. But today, I’m grateful that\nfeedback was given constructively, it was taken with the spirit that it was\nintended, and everyone should end up happy.</p>\n<p>If only every controversy went this way, maybe I wouldn’t be wondering about\ncoyotes.</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3ljnctebzec25\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">A happy day for <a target=\"_blank\" href=\"https://bsky.app/hashtag/rustlang\" class=\"hashtag s-1lecfnd\">#rustlang</a> <a target=\"_blank\" href=\"https://steveklabnik.com/writing/a-happy-day-for-rust/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/a-ha...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/a-happy-day-for-rust/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreibb62uzqrzuu7nzx4c2s46anoc6fztalrgpm5qucnoofgdizmwtbu\" alt=\"\" class=\"thumbnail s-rtbqd8\"/> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">A Happy Day for Rust</p> <p class=\"description s-rtbqd8\">I’m not sure where we go from here, and I’m not sure what we could have done to prevent this from happening. But I feel like this is a failure, and it’s set Rust back a bit, and I’m just plain sad.</p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2025-03-05T15:39:41.341Z\" class=\"date s-hik11q\">March 5, 2025 at 3:39 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"185 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>185</span></span> <span class=\"stat s-hik11q\" title=\"23 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>23</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3ljnctebzec25\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 4 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
},
{
"id": "https://steveklabnik.com/writing/when-should-i-use-string-vs-str/",
"title": "When should I use String vs &str?",
"description": "Rust has two main string types: String and &str . Sometimes, people argue that these two types make Rust code difficult to write, because you have to think about which one you should be using in a given situation. My experience of writing Rust is that I don’t really think about…",
"url": "https://steveklabnik.com/writing/when-should-i-use-string-vs-str/",
"published": "2024-10-16T00:00:00.000Z",
"updated": "2024-10-16T00:00:00.000Z",
"content": "<p>Rust has two main string types: <code>String</code> and <code>&str</code>. Sometimes, people argue\nthat these two types make Rust code difficult to write, because you have to\nthink about which one you should be using in a given situation. My experience\nof writing Rust is that I don’t really think about this very much, and this\npost is about some rules of thumb that you can use to be like me.</p>\n<h2 id=\"level-1-dont-think-about-it-at-all\">Level 1: Don’t think about it at all</h2>\n<p>The very first thing you can do is follow the simplest rule:</p>\n<blockquote>\n<p>Always use <code>String</code>, never use <code>&str</code>.</p>\n</blockquote>\n<p>That looks like this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Person</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> name</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> String</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> first_word</span><span style=\"color:#E1E4E8\">(words</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> String</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">-></span><span style=\"color:#B392F0\"> String</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> words</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">split_whitespace</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">next</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">expect</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"words should not be empty\"</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">to_string</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>This style means you may need to add <code>.to_string()</code> or <code>.clone()</code>\nfor things to work sometimes:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> sentence </span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\"> \"Hello, world!\"</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word</span><span style=\"color:#E1E4E8\">(sentence</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">to_string</span><span style=\"color:#E1E4E8\">()));</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> owned </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> String</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">from</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"A string\"</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\"> // if we don't clone here, we can't use owned the second time</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word</span><span style=\"color:#E1E4E8\">(owned</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">clone</span><span style=\"color:#E1E4E8\">()));</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word</span><span style=\"color:#E1E4E8\">(owned));</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>But that’s okay, the compiler will let you know when you need to:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>error[E0382]: use of moved value: `owned`</span></span>\n<span class=\"line\"><span> --> src/main.rs:21:31</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>18 | let owned = String::from(\"A string\");</span></span>\n<span class=\"line\"><span> | ----- move occurs because `owned` has type `String`, which does not implement the `Copy` trait</span></span>\n<span class=\"line\"><span>19 |</span></span>\n<span class=\"line\"><span>20 | println!(\"{}\", first_word(owned));</span></span>\n<span class=\"line\"><span> | ----- value moved here</span></span>\n<span class=\"line\"><span>21 | println!(\"{}\", first_word(owned));</span></span>\n<span class=\"line\"><span> | ^^^^^ value used here after move</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>note: consider changing this parameter type in function `first_word` to borrow instead if owning the value isn't necessary</span></span>\n<span class=\"line\"><span> --> src/main.rs:5:22</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>5 | fn first_word(words: String) -> String {</span></span>\n<span class=\"line\"><span> | ---------- ^^^^^^ this parameter takes ownership of the value</span></span>\n<span class=\"line\"><span> | |</span></span>\n<span class=\"line\"><span> | in this function</span></span>\n<span class=\"line\"><span>help: consider cloning the value if the performance cost is acceptable</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>20 | println!(\"{}\", first_word(owned.clone()));</span></span>\n<span class=\"line\"><span> | ++++++++</span></span></code></pre>\n<p>Hey, compiler, that’s a great idea. Let’s move on to level 2.</p>\n<h2 id=\"level-2-prefer-str-for-function-parameters\">Level 2: prefer <code>&str</code> for function parameters</h2>\n<p>A rule that’s a little better is this one:</p>\n<blockquote>\n<p>Always use <code>String</code> in structs, and for functions, use <code>&str</code> for parameters\nand <code>String</code> types for return values.</p>\n</blockquote>\n<p>This is what the compiler error for level 1 was suggesting we do instead of <code>.clone</code>.</p>\n<p>That results in code that looks like this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Person</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> name</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> String</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> first_word</span><span style=\"color:#E1E4E8\">(words</span><span style=\"color:#F97583\">:</span><span style=\"color:#F97583\"> &</span><span style=\"color:#B392F0\">str</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">-></span><span style=\"color:#B392F0\"> String</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> words</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">split_whitespace</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">next</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">expect</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"words should not be empty\"</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">to_string</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> sentence </span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\"> \"Hello, world!\"</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word</span><span style=\"color:#E1E4E8\">(sentence));</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> owned </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> String</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">from</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"A string\"</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&</span><span style=\"color:#E1E4E8\">owned));</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&</span><span style=\"color:#E1E4E8\">owned));</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>We’re now doing much less copying. We do need to add a <code>&</code> on\nto <code>String</code> values that we wish to pass into <code>first_word</code>, but\nthat’s not too bad, the compiler will help us when we forget:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>error[E0308]: mismatched types</span></span>\n<span class=\"line\"><span> --> src/main.rs:20:31</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>20 | println!(\"{}\", first_word(owned));</span></span>\n<span class=\"line\"><span> | ---------- ^^^^^ expected `&str`, found `String`</span></span>\n<span class=\"line\"><span> | |</span></span>\n<span class=\"line\"><span> | arguments to this function are incorrect</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>note: function defined here</span></span>\n<span class=\"line\"><span> --> src/main.rs:5:4</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>5 | fn first_word(words: &str) -> String {</span></span>\n<span class=\"line\"><span> | ^^^^^^^^^^ -----------</span></span>\n<span class=\"line\"><span>help: consider borrowing here</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>20 | println!(\"{}\", first_word(&owned));</span></span>\n<span class=\"line\"><span> | +</span></span></code></pre>\n<p>Following this rule will get you through 95% of situations successfully. Yes,\nthat number was found via the very scientific process of “I made it up, but it\nfeels correct after writing Rust for the last twelve years.”</p>\n<p>For 4% of that last 5%, we can go to level 3:</p>\n<h2 id=\"level-3-return-str-sometimes\">Level 3: return <code>&str</code> sometimes</h2>\n<p>Here’s a slightly more advanced rule for certain circumstances:</p>\n<blockquote>\n<p>Always use <code>String</code> in structs, and for functions, use <code>&str</code> for parameters.\nIf the return type of your function is derived from an argument and isn’t\nmutated by the body, return <code>&str</code>. If you run into any trouble here, return\n<code>String</code> instead.</p>\n</blockquote>\n<p>That would look like this:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">struct</span><span style=\"color:#B392F0\"> Person</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> name</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> String</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// we're returning a substring of words, so &str is appropriate</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> first_word</span><span style=\"color:#E1E4E8\">(words</span><span style=\"color:#F97583\">:</span><span style=\"color:#F97583\"> &</span><span style=\"color:#B392F0\">str</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">-></span><span style=\"color:#F97583\"> &</span><span style=\"color:#B392F0\">str</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> words</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">split_whitespace</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">next</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">expect</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"words should not be empty\"</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> sentence </span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\"> \"Hello, world!\"</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word</span><span style=\"color:#E1E4E8\">(sentence));</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> owned </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> String</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">from</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"A string\"</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&</span><span style=\"color:#E1E4E8\">owned));</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&</span><span style=\"color:#E1E4E8\">owned));</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>This lets us remove a copy, we no longer have a <code>.to_string</code> in the body of\n<code>first_word</code>.</p>\n<p>Sometimes, we can’t do that though:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#6A737D\">// because we are going to uppercase the first word, our return type can't</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// be &str anymore, because we aren't actually returning a substring: we are</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// creating our own new string.</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> first_word_uppercase</span><span style=\"color:#E1E4E8\">(words</span><span style=\"color:#F97583\">:</span><span style=\"color:#F97583\"> &</span><span style=\"color:#B392F0\">str</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">-></span><span style=\"color:#B392F0\"> String</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\"> words</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">split_whitespace</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">next</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">expect</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"words should not be empty\"</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">to_uppercase</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> sentence </span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\"> \"Hello, world!\"</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word_uppercase</span><span style=\"color:#E1E4E8\">(sentence));</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\"> let</span><span style=\"color:#E1E4E8\"> owned </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> String</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">from</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"A string\"</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word_uppercase</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&</span><span style=\"color:#E1E4E8\">owned));</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\"> println!</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"{}\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">first_word_uppercase</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#F97583\">&</span><span style=\"color:#E1E4E8\">owned));</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>How do you know that this is the case? Well, in this specific case,\n<code>to_uppercase</code> already returns a <code>String</code>. So that’s a great hint.\nIf we tried to return a <code>&str</code>, we’d get an error:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#6A737D\">// this can't work</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">fn</span><span style=\"color:#B392F0\"> first_word_uppercase</span><span style=\"color:#E1E4E8\">(words</span><span style=\"color:#F97583\">:</span><span style=\"color:#F97583\"> &</span><span style=\"color:#B392F0\">str</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">-></span><span style=\"color:#F97583\"> &</span><span style=\"color:#B392F0\">str</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> &</span><span style=\"color:#E1E4E8\">words</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">split_whitespace</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">next</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">expect</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"words should not be empty\"</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\"> .</span><span style=\"color:#B392F0\">to_uppercase</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>would give us</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>error[E0515]: cannot return reference to temporary value</span></span>\n<span class=\"line\"><span> --> src/main.rs:7:5</span></span>\n<span class=\"line\"><span> |</span></span>\n<span class=\"line\"><span>7 | &words</span></span>\n<span class=\"line\"><span> | ______^-</span></span>\n<span class=\"line\"><span> | | ______|</span></span>\n<span class=\"line\"><span>8 | || .split_whitespace()</span></span>\n<span class=\"line\"><span>9 | || .next()</span></span>\n<span class=\"line\"><span>10 | || .expect(\"words should not be empty\")</span></span>\n<span class=\"line\"><span>11 | || .to_uppercase()</span></span>\n<span class=\"line\"><span> | || ^</span></span>\n<span class=\"line\"><span> | ||_______________________|</span></span>\n<span class=\"line\"><span> | |_______________________returns a reference to data owned by the current function</span></span>\n<span class=\"line\"><span> | temporary value created here</span></span></code></pre>\n<p>And that’s really it. Following this rule will get you through virtually every\nscenario where you need to wonder about <code>String</code> and <code>&str</code>. With some practice,\nyou’ll internalize these rules, and when you feel comfortable with a level, you\ncan go up to the next one.</p>\n<p>What about that last 1% though? Well, there is a next level…</p>\n<h2 id=\"level-4-when-to-use-str-in-a-struct\">Level 4: When to use <code>&str</code> in a struct</h2>\n<p>Here’s the rule for level 4:</p>\n<blockquote>\n<p>Should you use a <code>&str</code> in a struct? If you’re asking that question, use\n<code>String</code>. When you need to use <code>&str</code> in a struct, you’ll know.</p>\n</blockquote>\n<p>Storing references in structs is useful, for sure, and it’s good that Rust\nsupports it. But you’re only going to need it in fairly specific scenarios,\nand if you feel like you’re worrying about <code>String</code> vs <code>&str</code>, you’re just\nnot in the position to be worrying about the complexity of storing a <code>&str</code>\nin a struct yet.</p>\n<p>In fact, some people believe in this rule so strongly that they’re working\non a language where storing references in structs isn’t even possible, and\nit’s a language I’ve found very interesting lately:\n<a href=\"https://www.hylo-lang.org/\">Hylo</a>. They go a bit farther than that, even:\nin Hylo, you think of everything as being values, rather than having references\nat all. They think that you can write meaningful programs with this model.\nBut this isn’t a post about Hylo. I’ll write one of those eventually. My point\nis just that you can really, truly, get away with not storing <code>&str</code>s in structs\nfor a <em>lot</em> of useful Rust programs. So it’s not really worth spending mental\nenergy on, until you determine that you must do so. That is the case when\nyou’ve profiled your program and determined that copying strings to and from\nyour struct is a big enough issue to bother with lifetimes.</p>\n<p>I hope you’ve found this helpful, and if you have any other rules of thumb\nlike this, I’d love to hear about them!</p>\n<hr/>\n<p>Here’s my post about this post on BlueSky:</p>\n<bluesky-post src=\"at://did:plc:3danwc67lo7obz2fmdg6jxcr/app.bsky.feed.post/3l6nkx54sij2w\" data-pagefind-ignore=\"true\"> <div class=\"bluesky-embed s-3olstj\"><div class=\"highlighted-post s-hik11q\"><div class=\"meta s-hik11q\"><a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"avatar-wrapper s-hik11q\"><img loading=\"lazy\" src=\"https://cdn.bsky.app/img/avatar/plain/did:plc:3danwc67lo7obz2fmdg6jxcr/bafkreidffwgsdli2xp56t3kfemk7bryow3mygu7zbvgcsxgsu3ntnp4hwm\" alt=\"\" class=\"avatar s-hik11q\"/></a> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr\" target=\"_blank\" class=\"name-wrapper s-hik11q\"><bdi class=\"display-name-wrapper s-hik11q\"><span class=\"display-name s-hik11q\">Steve Klabnik</span></bdi> <span class=\"handle s-hik11q\">@steveklabnik.com</span></a> <svg class=\"logo s-hik11q\" fill=\"none\" viewBox=\"0 0 320 286\"><path fill=\"#0A7AFF\" d=\"M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z\"></path></svg></div> <p class=\"rich-text is-large s-1lecfnd\">When should you use String vs &str? \n\nSome <a target=\"_blank\" href=\"https://bsky.app/hashtag/rustlang\" class=\"hashtag s-1lecfnd\">#rustlang</a> rules of thumb\n\n<a target=\"_blank\" href=\"https://steveklabnik.com/writing/when-should-i-use-string-vs-str/\" rel=\"noopener nofollow\" class=\"link s-1lecfnd\">steveklabnik.com/writing/when...</a></p> <div class=\"embeds s-azdpbr\"><a target=\"_blank\" href=\"https://steveklabnik.com/writing/when-should-i-use-string-vs-str/\" rel=\"noopener noreferrer nofollow\" class=\"external-embed s-rtbqd8\"> <div class=\"meta s-rtbqd8\"><p class=\"title s-rtbqd8\">When should I use String vs &str?</p> <p class=\"description s-rtbqd8\"></p> <div class=\"domain s-rtbqd8\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-width=\"2\" d=\"m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z\"></path></svg> <span class=\"domain-name\">steveklabnik.com</span></div></div></a></div> <time datetime=\"2024-10-16T18:27:49.871Z\" class=\"date s-hik11q\">October 16, 2024 at 6:27 PM</time> <div class=\"stats s-hik11q\"><span class=\"stat s-hik11q\" title=\"287 likes\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-width=\"2\" d=\"M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z\"></path></svg> <span>287</span></span> <span class=\"stat s-hik11q\" title=\"43 reposts\"><svg class=\"icon\" fill=\"none\" viewBox=\"0 0 24 24\"><path stroke=\"currentColor\" stroke-linecap=\"square\" stroke-width=\"2\" d=\"m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15\"></path></svg> <span>43</span></span> <div class=\"gap s-hik11q\"></div> <a href=\"https://bsky.app/profile/did:plc:3danwc67lo7obz2fmdg6jxcr/post/3l6nkx54sij2w\" target=\"_blank\" class=\"permalink s-hik11q\"><span>Read 30 replies on Bluesky</span></a></div></div></div> </bluesky-post>",
"image": null,
"media": [],
"authors": [],
"categories": []
}
]
}