<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Error-Handling on Selective Creativity</title>
    <link>https://blog.sebastiansastre.co/tags/error-handling/</link>
    <description>Recent content in Error-Handling on Selective Creativity</description>
    <generator>Hugo -- 0.154.5</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 30 Aug 2026 00:00:00 -0300</lastBuildDate>
    <atom:link href="https://blog.sebastiansastre.co/tags/error-handling/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Ban From on your Rust Error Variants</title>
      <link>https://blog.sebastiansastre.co/posts/ban-from-on-your-rust-error-variants/</link>
      <pubDate>Sun, 30 Aug 2026 00:00:00 -0300</pubDate>
      <guid>https://blog.sebastiansastre.co/posts/ban-from-on-your-rust-error-variants/</guid>
      <enclosure url="https://blog.sebastiansastre.co/img/ban-from-on-your-rust-error-variants.webp" type="image/webp" />
      <description><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/ban-from-on-your-rust-error-variants.webp" alt=""" loading="lazy" /></figure><p>An alert lands. You were eight hours intensely into something completely unrelated to that alert. You have this alert now, and you are not just tired. You are spent.</p>
<p>The log says the sequencer failed. You open the crate that returns <code>EngineError</code>. You search for <code>EngineError::Sequencer</code>.</p>
<p>Zero call sites.</p>
<p>The conversion happened. The compiler was happy. You are not. The <code>?</code> that made some method in that engine code look clean is the line that hides what you cannot find when the unexpected alert hits you right after you emptied the tank on a problem that had nothing to do with this matching engine.</p>
<p>Many things push you to use it. <code>From</code> is idiomatic. The book teaches it, and <code>thiserror</code> will derive it for you if you put <code>#[from]</code> on a variant. Then <code>?</code> does what people hired Rust for: in the most compact way for the codebase, the inner error becomes the outer error and the function body stays a list of happy-path calls.</p>
<p>The ecosystem rewards this. Less typing. One <code>impl From</code> per variant, generated, correct, and erased in the sense that you never write the wrap. The standard library is full of <code>From</code>. String from <code>&amp;str</code>. <code>io::Error</code> from a long list of OS failures. Conversion is the language&rsquo;s way of saying <em>this is that</em>.</p>
<p>If you object, you can feel someone appeal to brevity and paint you as wanting ceremony for the sake of it.</p>
<p>But in that case, that someone is telling you the compiler already inlined the <code>From</code> impl while you are arguing about a line helpful for diagnosing, a line coded for understanding, a line that does not survive codegen. They are thinking in cycle costs. Chances are they are pointing at the wrong bill.</p>
<p>The bill is not paid when the binary runs. It is paid when you are exhausted, the alert is unrelated to the work you just did, and the wrap has no name you can search.</p>
<p>In that prod alert, which function wrapped <code>EngineError::Sequencer</code>?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[derive(Debug, Error)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">enum</span> <span class="nc">EngineError</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[error(transparent)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Config</span><span class="p">(</span><span class="cp">#[from]</span><span class="w"> </span><span class="n">ConfigError</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[error(transparent)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Sequencer</span><span class="p">(</span><span class="cp">#[from]</span><span class="w"> </span><span class="n">sequencer</span>::<span class="n">Error</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">start</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">config</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Config</span>::<span class="n">load</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">bind</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">ingest</span><span class="p">(</span><span class="n">command</span>: <span class="nc">EngineCommand</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">enqueue</span><span class="p">(</span><span class="n">command</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">recover</span><span class="p">(</span><span class="n">from</span>: <span class="nc">Sequence</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">replay</span><span class="p">(</span><span class="n">from</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>In that snippet we have three, so it is tempting to shrug because the number is low. That thinking scales poorly. At 10 or 20 you get exponential disambiguation costs.</p>
<p>That friction to diagnose is a causality cost. <code>#[from]</code> hides it for cleanness. You got the cleaner source. You are paying for it in missing origin evidence.</p>
<p>I <a href="https://blog.sebastiansastre.co/posts/cost-of-indirection-in-rust">already made that argument</a> for extracting a function. Named calls are usually free. This is the sibling intuition, just pointed at where errors happen: <em>do not add a line; <code>?</code> is enough, cleaner, more compact.</em> Same reflex as &ldquo;saving an extra call.&rdquo;</p>
<p>But <em>the site is the evidence</em> we need to find fast and under pressure.</p>
<p><code>?</code> still sits on a source line. A backtrace, if you have one, will often point at <code>Config::load()?</code>. Production does not always give you that backtrace. Operators get a Display string. Teammates get a git grep. AI agents get a constructor they can search.</p>
<p><code>#[from]</code> puts the only <code>EngineError::Sequencer(...)</code> in a derived <code>From</code> impl you do not read. The call sites are just <code>?</code>. Find-references on the variant finds the enum. It does not find the three places (or 20) that actually wrapped a <code>sequencer::Error</code>. You cannot tell a sequencer bind from a config miss without opening every callee and checking its <code>Err</code> type. The function body flattened them into the same punctuation.</p>
<p>If we start unpacking it, using <code>.map_err(EngineError::from)?</code> gives us the same hide with extra steps. You wrote <code>from</code> and threw away the variant name on purpose.</p>
<p>The alternative is to use the symbol to mark the spot: name that variant at the conversion point.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[derive(Debug, Error)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">enum</span> <span class="nc">EngineError</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[error(</span><span class="s">&#34;configuration error: {0}&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Config</span><span class="p">(</span><span class="cp">#[source]</span><span class="w"> </span><span class="n">ConfigError</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[error(</span><span class="s">&#34;sequencer error: {0}&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Sequencer</span><span class="p">(</span><span class="cp">#[source]</span><span class="w"> </span><span class="n">sequencer</span>::<span class="n">Error</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">start</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">config</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Config</span>::<span class="n">load</span><span class="p">().</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">Config</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">bind</span><span class="p">().</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">Sequencer</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">ingest</span><span class="p">(</span><span class="n">command</span>: <span class="nc">EngineCommand</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">enqueue</span><span class="p">(</span><span class="n">command</span><span class="p">).</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">Sequencer</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">recover</span><span class="p">(</span><span class="n">from</span>: <span class="nc">Sequence</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">replay</span><span class="p">(</span><span class="n">from</span><span class="p">).</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">Sequencer</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>#[source]</code> keeps the chain. <code>.map_err(EngineError::Sequencer)</code> is the line you will search for. It is also the line you can breakpoint. It is also helping you <a href="https://blog.sebastiansastre.co/posts/explain-your-rules">explain the rule</a> in a sentence: <em>this is where a sequencer failure becomes an engine failure,</em> which is a reminder of what job <a href="https://blog.sebastiansastre.co/posts/the-accelerator-and-the-brake">your program was hired for</a>.</p>
<p>That sentence does not belong in a derive. It belongs where the program decides.</p>
<p>The same rule holds at every layer of your application. With one coding convention, the whole program is much more diagnosable when it is in operations. The site you can walk is the constructor, so you will find it fast and make sense of that alert without friction.</p>
<p>And the runtime cost is zero.</p>
<p>Someone will still see <code>.map_err</code> and think you added a call. <code>?</code> already wraps. That is the whole trick.</p>
<p><code>result?</code> in a function that returns <code>Result&lt;T, EngineError&gt;</code> desugars to <code>From::from</code> on the inner error. A <code>#[from]</code> on <code>Sequencer</code> generates an impl whose body is <code>EngineError::Sequencer(inner)</code>. <code>.map_err(EngineError::Sequencer)</code> is that same constructor, written where the program decides. <code>.map_err(EngineError::from)</code> is the hide with the extra word <code>from</code>.</p>
<p>So the three forms are the same wrap. You can check it the same way as the <a href="https://blog.sebastiansastre.co/posts/cost-of-indirection-in-rust">indirection post</a>: <code>#[no_mangle]</code>, optimized assembly, look.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="nb">From</span><span class="o">&lt;</span><span class="n">SequencerError</span><span class="o">&gt;</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">EngineError</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">from</span><span class="p">(</span><span class="n">inner</span>: <span class="nc">SequencerError</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">EngineError</span>::<span class="n">Sequencer</span><span class="p">(</span><span class="n">inner</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">wrap_question</span><span class="p">(</span><span class="n">result</span>: <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">SequencerError</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">wrap_map_err</span><span class="p">(</span><span class="n">result</span>: <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">SequencerError</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="p">.</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">Sequencer</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">wrap_from</span><span class="p">(</span><span class="n">result</span>: <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">SequencerError</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="p">.</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">from</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rustc --edition <span class="m">2021</span> -O --crate-type lib --emit asm lib.rs
</span></span></code></pre></div><p>I compiled a two-variant <code>EngineError</code> (Config and Sequencer) on aarch64. LLVM emitted one function and aliased the other two names to it. Your mnemonics may differ. The aliasing is the point:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">_wrap_from:
</span></span><span class="line"><span class="cl">        mov     w8, #2
</span></span><span class="line"><span class="cl">        sub     x0, x8, x0
</span></span><span class="line"><span class="cl">        ret
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">_wrap_map_err = _wrap_from
</span></span><span class="line"><span class="cl">_wrap_question = _wrap_from
</span></span></code></pre></div><p>The wrap itself is a discriminant tweak. So the <em>delta</em> of writing <code>.map_err(EngineError::Sequencer)</code> instead of <code>?</code> plus <code>From</code> is not &ldquo;small.&rdquo; It is the same machine code. Folks are arguing about a distinction that does not survive compilation.</p>
<p>A green compile on the <code>#[from]</code> version proves the <code>From</code> impls exist. It does not prove you will be able to walk the failure.</p>
<p><a href="https://blog.sebastiansastre.co/posts/the-green-build-and-the-hermeneutic-gap">Consistency is cheap</a>. Locating the wrap is the expensive half, and it is a source location problem. You still use <code>?</code>. You are refusing to let the type system narrate a wrap that humans have to reconstruct later, at the worst moment, for a human cost that is certainly not zero.</p>
<p>If three calls all produce <code>sequencer::Error</code>, all three conversions still use <code>EngineError::Sequencer</code>. The constructor at the call site still gives you three hits instead of none. That is enough to start addressing that alert. If <code>bind</code>, <code>enqueue</code>, and <code>replay</code> were not in the flow where the alert happens, there is no ambiguity. You know it comes from a different site, without diagnosing delays.</p>
<p>Ban <code>From</code> on error types at the boundary you control. Keep <code>From</code> for values that are actually the same idea in a different suit. <code>Price</code> from a tick count is a conversion. <code>EngineError</code> from <code>SequencerError</code> is a judgment.</p>
<p>The appeal to brevity is the costly one. It saves a constructor name and makes you pay later, when you cannot search. The appeal to a faster runtime if you keep <code>From</code> on the variant does not exist.</p>
<p>Adopt <code>#[source]</code> on your error variants plus the named variant at the call site: one motion, every crate you own, causality you can grep by design.</p>
<p>Write that judgment while you still have a full tank. The next alert will not wait for a fresh mind.</p>
<p>When it lands, you will search for <code>EngineError::Sequencer</code> again and understand the full flow. In no time you will know all the details, and what to do, before anyone asks what happened to the service.</p>
]]></description>
      <content:encoded><![CDATA[<figure><img src="https://blog.sebastiansastre.co/img/ban-from-on-your-rust-error-variants.webp" alt=""" loading="lazy" /></figure><p>An alert lands. You were eight hours intensely into something completely unrelated to that alert. You have this alert now, and you are not just tired. You are spent.</p>
<p>The log says the sequencer failed. You open the crate that returns <code>EngineError</code>. You search for <code>EngineError::Sequencer</code>.</p>
<p>Zero call sites.</p>
<p>The conversion happened. The compiler was happy. You are not. The <code>?</code> that made some method in that engine code look clean is the line that hides what you cannot find when the unexpected alert hits you right after you emptied the tank on a problem that had nothing to do with this matching engine.</p>
<p>Many things push you to use it. <code>From</code> is idiomatic. The book teaches it, and <code>thiserror</code> will derive it for you if you put <code>#[from]</code> on a variant. Then <code>?</code> does what people hired Rust for: in the most compact way for the codebase, the inner error becomes the outer error and the function body stays a list of happy-path calls.</p>
<p>The ecosystem rewards this. Less typing. One <code>impl From</code> per variant, generated, correct, and erased in the sense that you never write the wrap. The standard library is full of <code>From</code>. String from <code>&amp;str</code>. <code>io::Error</code> from a long list of OS failures. Conversion is the language&rsquo;s way of saying <em>this is that</em>.</p>
<p>If you object, you can feel someone appeal to brevity and paint you as wanting ceremony for the sake of it.</p>
<p>But in that case, that someone is telling you the compiler already inlined the <code>From</code> impl while you are arguing about a line helpful for diagnosing, a line coded for understanding, a line that does not survive codegen. They are thinking in cycle costs. Chances are they are pointing at the wrong bill.</p>
<p>The bill is not paid when the binary runs. It is paid when you are exhausted, the alert is unrelated to the work you just did, and the wrap has no name you can search.</p>
<p>In that prod alert, which function wrapped <code>EngineError::Sequencer</code>?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[derive(Debug, Error)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">enum</span> <span class="nc">EngineError</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[error(transparent)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Config</span><span class="p">(</span><span class="cp">#[from]</span><span class="w"> </span><span class="n">ConfigError</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[error(transparent)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Sequencer</span><span class="p">(</span><span class="cp">#[from]</span><span class="w"> </span><span class="n">sequencer</span>::<span class="n">Error</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">start</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">config</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Config</span>::<span class="n">load</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">bind</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">ingest</span><span class="p">(</span><span class="n">command</span>: <span class="nc">EngineCommand</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">enqueue</span><span class="p">(</span><span class="n">command</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">recover</span><span class="p">(</span><span class="n">from</span>: <span class="nc">Sequence</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">replay</span><span class="p">(</span><span class="n">from</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>In that snippet we have three, so it is tempting to shrug because the number is low. That thinking scales poorly. At 10 or 20 you get exponential disambiguation costs.</p>
<p>That friction to diagnose is a causality cost. <code>#[from]</code> hides it for cleanness. You got the cleaner source. You are paying for it in missing origin evidence.</p>
<p>I <a href="https://blog.sebastiansastre.co/posts/cost-of-indirection-in-rust">already made that argument</a> for extracting a function. Named calls are usually free. This is the sibling intuition, just pointed at where errors happen: <em>do not add a line; <code>?</code> is enough, cleaner, more compact.</em> Same reflex as &ldquo;saving an extra call.&rdquo;</p>
<p>But <em>the site is the evidence</em> we need to find fast and under pressure.</p>
<p><code>?</code> still sits on a source line. A backtrace, if you have one, will often point at <code>Config::load()?</code>. Production does not always give you that backtrace. Operators get a Display string. Teammates get a git grep. AI agents get a constructor they can search.</p>
<p><code>#[from]</code> puts the only <code>EngineError::Sequencer(...)</code> in a derived <code>From</code> impl you do not read. The call sites are just <code>?</code>. Find-references on the variant finds the enum. It does not find the three places (or 20) that actually wrapped a <code>sequencer::Error</code>. You cannot tell a sequencer bind from a config miss without opening every callee and checking its <code>Err</code> type. The function body flattened them into the same punctuation.</p>
<p>If we start unpacking it, using <code>.map_err(EngineError::from)?</code> gives us the same hide with extra steps. You wrote <code>from</code> and threw away the variant name on purpose.</p>
<p>The alternative is to use the symbol to mark the spot: name that variant at the conversion point.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[derive(Debug, Error)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">enum</span> <span class="nc">EngineError</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[error(</span><span class="s">&#34;configuration error: {0}&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Config</span><span class="p">(</span><span class="cp">#[source]</span><span class="w"> </span><span class="n">ConfigError</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[error(</span><span class="s">&#34;sequencer error: {0}&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Sequencer</span><span class="p">(</span><span class="cp">#[source]</span><span class="w"> </span><span class="n">sequencer</span>::<span class="n">Error</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">start</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">config</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Config</span>::<span class="n">load</span><span class="p">().</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">Config</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">bind</span><span class="p">().</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">Sequencer</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">ingest</span><span class="p">(</span><span class="n">command</span>: <span class="nc">EngineCommand</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">enqueue</span><span class="p">(</span><span class="n">command</span><span class="p">).</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">Sequencer</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">recover</span><span class="p">(</span><span class="n">from</span>: <span class="nc">Sequence</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sequencer</span>::<span class="n">replay</span><span class="p">(</span><span class="n">from</span><span class="p">).</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">Sequencer</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>#[source]</code> keeps the chain. <code>.map_err(EngineError::Sequencer)</code> is the line you will search for. It is also the line you can breakpoint. It is also helping you <a href="https://blog.sebastiansastre.co/posts/explain-your-rules">explain the rule</a> in a sentence: <em>this is where a sequencer failure becomes an engine failure,</em> which is a reminder of what job <a href="https://blog.sebastiansastre.co/posts/the-accelerator-and-the-brake">your program was hired for</a>.</p>
<p>That sentence does not belong in a derive. It belongs where the program decides.</p>
<p>The same rule holds at every layer of your application. With one coding convention, the whole program is much more diagnosable when it is in operations. The site you can walk is the constructor, so you will find it fast and make sense of that alert without friction.</p>
<p>And the runtime cost is zero.</p>
<p>Someone will still see <code>.map_err</code> and think you added a call. <code>?</code> already wraps. That is the whole trick.</p>
<p><code>result?</code> in a function that returns <code>Result&lt;T, EngineError&gt;</code> desugars to <code>From::from</code> on the inner error. A <code>#[from]</code> on <code>Sequencer</code> generates an impl whose body is <code>EngineError::Sequencer(inner)</code>. <code>.map_err(EngineError::Sequencer)</code> is that same constructor, written where the program decides. <code>.map_err(EngineError::from)</code> is the hide with the extra word <code>from</code>.</p>
<p>So the three forms are the same wrap. You can check it the same way as the <a href="https://blog.sebastiansastre.co/posts/cost-of-indirection-in-rust">indirection post</a>: <code>#[no_mangle]</code>, optimized assembly, look.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="nb">From</span><span class="o">&lt;</span><span class="n">SequencerError</span><span class="o">&gt;</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">EngineError</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">from</span><span class="p">(</span><span class="n">inner</span>: <span class="nc">SequencerError</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">EngineError</span>::<span class="n">Sequencer</span><span class="p">(</span><span class="n">inner</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">wrap_question</span><span class="p">(</span><span class="n">result</span>: <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">SequencerError</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">wrap_map_err</span><span class="p">(</span><span class="n">result</span>: <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">SequencerError</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="p">.</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">Sequencer</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cp">#[no_mangle]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">wrap_from</span><span class="p">(</span><span class="n">result</span>: <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">SequencerError</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">EngineError</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">result</span><span class="p">.</span><span class="n">map_err</span><span class="p">(</span><span class="n">EngineError</span>::<span class="n">from</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rustc --edition <span class="m">2021</span> -O --crate-type lib --emit asm lib.rs
</span></span></code></pre></div><p>I compiled a two-variant <code>EngineError</code> (Config and Sequencer) on aarch64. LLVM emitted one function and aliased the other two names to it. Your mnemonics may differ. The aliasing is the point:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">_wrap_from:
</span></span><span class="line"><span class="cl">        mov     w8, #2
</span></span><span class="line"><span class="cl">        sub     x0, x8, x0
</span></span><span class="line"><span class="cl">        ret
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">_wrap_map_err = _wrap_from
</span></span><span class="line"><span class="cl">_wrap_question = _wrap_from
</span></span></code></pre></div><p>The wrap itself is a discriminant tweak. So the <em>delta</em> of writing <code>.map_err(EngineError::Sequencer)</code> instead of <code>?</code> plus <code>From</code> is not &ldquo;small.&rdquo; It is the same machine code. Folks are arguing about a distinction that does not survive compilation.</p>
<p>A green compile on the <code>#[from]</code> version proves the <code>From</code> impls exist. It does not prove you will be able to walk the failure.</p>
<p><a href="https://blog.sebastiansastre.co/posts/the-green-build-and-the-hermeneutic-gap">Consistency is cheap</a>. Locating the wrap is the expensive half, and it is a source location problem. You still use <code>?</code>. You are refusing to let the type system narrate a wrap that humans have to reconstruct later, at the worst moment, for a human cost that is certainly not zero.</p>
<p>If three calls all produce <code>sequencer::Error</code>, all three conversions still use <code>EngineError::Sequencer</code>. The constructor at the call site still gives you three hits instead of none. That is enough to start addressing that alert. If <code>bind</code>, <code>enqueue</code>, and <code>replay</code> were not in the flow where the alert happens, there is no ambiguity. You know it comes from a different site, without diagnosing delays.</p>
<p>Ban <code>From</code> on error types at the boundary you control. Keep <code>From</code> for values that are actually the same idea in a different suit. <code>Price</code> from a tick count is a conversion. <code>EngineError</code> from <code>SequencerError</code> is a judgment.</p>
<p>The appeal to brevity is the costly one. It saves a constructor name and makes you pay later, when you cannot search. The appeal to a faster runtime if you keep <code>From</code> on the variant does not exist.</p>
<p>Adopt <code>#[source]</code> on your error variants plus the named variant at the call site: one motion, every crate you own, causality you can grep by design.</p>
<p>Write that judgment while you still have a full tank. The next alert will not wait for a fresh mind.</p>
<p>When it lands, you will search for <code>EngineError::Sequencer</code> again and understand the full flow. In no time you will know all the details, and what to do, before anyone asks what happened to the service.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
