<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Heiko Seeberger</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://heikoseeberger.de/</id>
  <link href="https://heikoseeberger.de/" rel="alternate"/>
  <link href="https://heikoseeberger.de/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Heiko Seeberger</rights>
  <subtitle>It is not the answer that enlightens, but the quest(ion)</subtitle>
  <title>Heiko's Blog</title>
  <updated>2026-10-19T15:37:12.625Z</updated>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="actors" scheme="https://heikoseeberger.de/tags/actors/"/>
    <category term="tellus" scheme="https://heikoseeberger.de/tags/tellus/"/>
    <content>
      <![CDATA[<p><strong>Updated (2026-10-19)</strong>: Thanks to feedback on <a href="https://www.reddit.com/r/rust/comments/1x9vivy/why_tellus_message_handlers_are_synchronous/">r&#x2F;rust</a>, the database example now uses <code>context.pipe_to_self</code> instead of a hand-spawned Tokio task, which made the pattern look heavier than it is. The same feedback pointed out that tellus has no stash yet, so the costs now mention that, too; <a href="https://github.com/tellus-rs/tellus/issues/150">issue #150</a> tracks adding one.</p><p>In <a href="https://tellus.rs/">tellus</a>, <code>receive</code> is a plain function, not an <code>async fn</code>. I mentioned that briefly in the <a href="https://heikoseeberger.de/2026-08-24-tellus-1/">first episode</a> of the tellus series, and it is the design decision people ask about most. Most Rust actor libraries, <a href="https://crates.io/crates/kameo">kameo</a> and <a href="https://crates.io/crates/ractor">ractor</a> among them, have async message handlers, so why does tellus not? Usually the question comes in a more practical form: how do I call my database then? In this post I make the case for synchronous handlers properly, and of course we will also answer the database question.</p><h2 id="What-an-actor-does"><a href="#What-an-actor-does" class="headerlink" title="What an actor does"></a>What an actor does</h2><p>Carl Hewitt <a href="https://en.wikipedia.org/wiki/Actor_model#Fundamental_concepts">defined an actor</a> by what it may do in response to a message. It may send a finite number of messages to other actors, create a finite number of new actors, and designate the behavior to be used for the next message. Let’s look at a group actor from the <a href="https://tellus.rs/docs/tutorials/getting-started/">getting started tutorial</a> which records a temperature reading for one of its devices:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">GroupMessage::Record &#123;</span><br><span class="line">    device_name,</span><br><span class="line">    degrees,</span><br><span class="line">&#125; =&gt; &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">device</span> = devices</span><br><span class="line">        .<span class="title function_ invoke__">entry</span>(device_name)</span><br><span class="line">        .<span class="title function_ invoke__">or_insert_with</span>(|| context.<span class="title function_ invoke__">spawn</span>(Device));</span><br><span class="line">    device.<span class="title function_ invoke__">tell</span>(DeviceMessage::<span class="title function_ invoke__">Record</span>(degrees));</span><br><span class="line">    <span class="title function_ invoke__">Ok</span>(Control::<span class="title function_ invoke__">Continue</span>(devices))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Here we find all three of Hewitt’s verbs. <code>context.spawn</code> creates an actor for a device we have not seen before, <code>tell</code> sends it a <code>Record</code> message carrying the degrees, and <code>Control::Continue(devices)</code> designates the state for the next message. Then <code>receive</code> returns.</p><p>But wait, Hewitt speaks of behavior, and we return a state? In tellus the behavior is <code>receive</code> applied to the current state, and <code>receive</code> itself never changes. So designating the next state designates the next behavior. If we want an actor to behave completely differently from one message to the next, we use an enum as state and let <code>receive</code> match on its variants.</p><p>Notice what the definition does not contain: a step where the actor pauses halfway through its reaction and waits for something. So the reaction to one message is a unit, and we should treat it as one. A synchronous <code>receive</code> is the most literal translation of that definition into Rust I can think of.</p><h2 id="Awaiting-in-a-handler"><a href="#Awaiting-in-a-handler" class="headerlink" title="Awaiting in a handler"></a>Awaiting in a handler</h2><p>Why should we care about being literal? Because an <code>await</code> inside a handler has to mean something for the actor, and none of the options is pleasant. Let’s look at a hypothetical async handler, not the API of any particular library, for a bank account which asks a fraud service before it pays out:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">fn</span> <span class="title function_">handle</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, <span class="title function_ invoke__">Withdraw</span>(amount): Withdraw) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;(), Rejected&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">self</span>.balance &lt; amount &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_ invoke__">Err</span>(Rejected);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">self</span>.fraud_service.<span class="title function_ invoke__">approve</span>(amount).<span class="keyword">await</span>?;</span><br><span class="line">    <span class="keyword">self</span>.balance -= amount;</span><br><span class="line">    <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>While this handler awaits the fraud service, one of two things happens. Either the actor handles no other message until the call returns. Then the account is blocked, but we cannot see that from the outside: it looks alive, its mailbox fills up, and if the fraud service hangs, so does the account. Or the actor handles further messages in the meantime. Then a second <code>Withdraw</code> sees the same balance, passes the same check, and both withdrawals go through. Well, that is an overdraft the check was supposed to prevent.</p><p>There is a third question lurking: what happens if the actor is stopped while it awaits? The future is dropped at the <code>await</code>, the fraud service may have approved the payment, and the balance was never reduced. Whatever the framework does here, we have to know it and code against it, at every <code>await</code> in every handler.</p><p>With a synchronous <code>receive</code>, we do not have to ask any of these questions. The actor handles one message from start to finish, nothing else sees its state in between, and tellus only ever stops or restarts an actor between two messages. In the <a href="https://heikoseeberger.de/2026-08-26-tellus-2/">second episode</a> we already saw the related property under failure: a <code>receive</code> which fails cannot leave a half-updated state behind.</p><p>To be fair, async handlers buy something real: we can write I&#x2F;O in direct style, right where we need it. kameo and ractor are not wrong to make that trade. I just think that for actors, the other side of the trade is worth more.</p><h2 id="Waiting-as-a-message"><a href="#Waiting-as-a-message" class="headerlink" title="Waiting as a message"></a>Waiting as a message</h2><p>So how do we wait for something in a tellus actor? In the actor model, a result we wait for arrives as a later message, and tellus makes that the only way. When an actor asks another one, <code>context.reply_to</code> creates a <code>ReplyTo</code> which delivers the reply into the asker’s own mailbox, converted into its message type. The reply then arrives through <code>receive</code> like any other message.</p><p>What do we get from that? Waiting becomes part of the state. The query actor from the tutorial asks all devices of a group for their last reading, and its state says exactly what it is waiting for:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">QueryState</span> &#123;</span><br><span class="line">    Starting,</span><br><span class="line"></span><br><span class="line">    Running &#123;</span><br><span class="line">        pending: HashMap&lt;ActorId, ActorRef&lt;DeviceMessage&gt;&gt;,</span><br><span class="line">        sum: <span class="type">f64</span>,</span><br><span class="line">        count: <span class="type">u32</span>,</span><br><span class="line">        reply_to: ReplyTo&lt;<span class="type">Option</span>&lt;<span class="type">f64</span>&gt;&gt;,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Each reading we receive removes a device from <code>pending</code>. The query also watches every device, so a device which terminates before it answers is removed when its terminated signal arrives. And thanks to the <a href="https://heikoseeberger.de/2026-08-27-tellus-3/">ordering guarantee</a>, that signal proves its reading can no longer come. When <code>pending</code> is empty, the query replies and stops. What an async handler would hide in a suspended future is a plain value here, which we can print, match on and test with <code>TestContext</code>, without running an actor system.</p><h2 id="Calling-a-database"><a href="#Calling-a-database" class="headerlink" title="Calling a database"></a>Calling a database</h2><p>All right, but what if we really need to await, for a database query or an HTTP request? We pipe the future to the actor itself with <code>context.pipe_to_self</code>. Here is a sketch, not code from the repo, of an actor which looks up a customer:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Incoming::<span class="title function_ invoke__">Message</span>(Message::<span class="title function_ invoke__">Lookup</span>(id)) =&gt; &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">pool</span> = <span class="keyword">self</span>.pool.<span class="title function_ invoke__">clone</span>();</span><br><span class="line">    context.<span class="title function_ invoke__">pipe_to_self</span>(</span><br><span class="line">        <span class="keyword">async</span> <span class="keyword">move</span> &#123; <span class="title function_ invoke__">fetch_customer</span>(&amp;pool, id).<span class="keyword">await</span> &#125;,</span><br><span class="line">        Message::Customer,</span><br><span class="line">    );</span><br><span class="line">    <span class="title function_ invoke__">Ok</span>(Control::<span class="title function_ invoke__">Continue</span>(state))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>pipe_to_self</code> runs the future on its own Tokio task, and meanwhile our actor keeps handling messages. When the result is ready, <code>Message::Customer</code> turns it into a message, and <code>receive</code> decides what to do with it, knowing exactly what has happened in the meantime. Blocking or CPU-heavy work goes into <code>tokio::task::spawn_blocking</code>, whose <code>JoinHandle</code> is a future, too, so we pipe it the same way. The how-to <a href="https://tellus.rs/docs/how-to/call-async-code/">Call async code from an actor</a> shows both in running code.</p><p>The strongest evidence that this works is event sourcing. If any actor needs I&#x2F;O, it is one which persists events, yet in tellus its <code>handle</code> is synchronous, too. As we saw in the <a href="https://heikoseeberger.de/2026-08-31-tellus-persistence/">persistence post</a>, <code>handle</code> returns an <code>Effect</code> which names the events to persist. tellus appends them to the store, applies them to the state and only then runs continuations like a reply. The I&#x2F;O happens in tellus, outside of our code, and our code stays a function from a command and a state to an effect.</p><h2 id="The-costs"><a href="#The-costs" class="headerlink" title="The costs"></a>The costs</h2><p>Of course we do not get synchronous handlers for free. A <code>receive</code> which blocks occupies a Tokio worker thread until it returns, so we keep it short and move slow work into a task. A <code>receive</code> which never returns keeps its actor from stopping, and with it all of its ancestors, because a parent only terminates after its children.</p><p>There is also more ceremony. Where an async handler simply awaits, we need an extra message and a state which remembers what we are waiting for. I am convinced that this is a feature rather than a cost, because the state machine was always there; async handlers only hide it in a future. But I admit that it means writing more code. And if an actor must not handle other messages while it waits, it has to buffer them in its state by hand, because tellus has no stash yet. <a href="https://github.com/tellus-rs/tellus/issues/150">Issue #150</a> tracks adding one.</p><p>What about performance? Synchronous handlers avoid allocating and polling a future per message. In the <a href="https://tellus.rs/tellus/comparison/tellus-v0.3.3/">comparison benchmarks for tellus 0.3.3</a> against kameo and ractor, tellus handles messages between 1.4 and 2.9 times as fast; the <a href="https://tellus.rs/tellus/comparison/latest/">latest run</a> has the current numbers. But the synchronous handlers are not the only reason: tellus’s mailbox is statically typed, while the others erase message types, which costs them an allocation and a dynamic dispatch per message. So these numbers tell us that tellus is fast, not how much of that we owe to synchronous handlers.</p><p>If you want to see the patterns from this post in running code, work through the <a href="https://tellus.rs/docs/tutorials/getting-started/">getting started tutorial</a> or take a look at its <a href="https://github.com/tellus-rs/tellus/tree/tellus-v0.3.3/tellus/examples/tutorials/getting_started">examples on GitHub</a>. What do you think about this approach? I am looking forward to your comments.</p><p><em>My thoughts, written with AI assistance.</em></p>]]>
    </content>
    <id>https://heikoseeberger.de/2026-10-18-why-sync-handlers/</id>
    <link href="https://heikoseeberger.de/2026-10-18-why-sync-handlers/"/>
    <published>2026-10-17T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p><strong>Updated (2026-10-19)</strong>: Thanks to feedback on <a href="https://www.reddit.com/r/rust/comments/1x9vivy/why_tellus_message_h]]>
    </summary>
    <title>Why tellus message handlers are synchronous</title>
    <updated>2026-10-19T15:37:12.625Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="actors" scheme="https://heikoseeberger.de/tags/actors/"/>
    <category term="tellus" scheme="https://heikoseeberger.de/tags/tellus/"/>
    <category term="tokio" scheme="https://heikoseeberger.de/tags/tokio/"/>
    <content>
      <![CDATA[<p><a href="https://github.com/hseeberger/tellus">tellus</a> 0.2 is out, and it brings the first of the two extensions I promised at the end of the <a href="https://heikoseeberger.de/2026-08-24-tellus-1/">series about its core</a>: persistence, in the shape of event-sourced actors, behind the off-by-default <code>persistence</code> feature.</p><p>Every actor keeps its state in memory. That is what makes it fast, but it is gone when the process terminates. Saving that state is the obvious answer, but every save overwrites the one before it: what survives is a summary, and how we got there is gone.</p><p>Instead, an event-sourced actor appends events, immutable facts about what happened, and never overwrites anything. The state is the fold of these events, so we get it back by replaying them after a crash, a restart or a redeployment. And the history is still there to audit, or to answer a question we had not thought of yet.</p><h2 id="Deciding-and-applying"><a href="#Deciding-and-applying" class="headerlink" title="Deciding and applying"></a>Deciding and applying</h2><p>In <a href="https://heikoseeberger.de/2026-08-26-tellus-2/">episode 2</a> we saw an actor as a function from the current state plus one incoming to the next state. Event sourcing cuts that function in two: one half decides what happened, the other folds it into the state. The rest of the design follows from that split. Let’s look at a counter which outlives the process it runs in:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">impl</span> <span class="title class_">EventSourced</span> <span class="keyword">for</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Command</span> = Command;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Event</span> = Increased;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">State</span> = <span class="type">u64</span>;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Snapshot</span> = Nothing;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span> = Infallible;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">persistence_id</span>(&amp;<span class="keyword">self</span>) <span class="punctuation">-&gt;</span> PersistenceId &#123;</span><br><span class="line">        PersistenceId::<span class="title function_ invoke__">new</span>(<span class="string">&quot;counter&quot;</span>, <span class="string">&quot;example&quot;</span>).<span class="title function_ invoke__">expect</span>(<span class="string">&quot;the segments are valid&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">init</span>(&amp;<span class="keyword">self</span>) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;<span class="keyword">Self</span>::State, <span class="keyword">Self</span>::Error&gt; &#123;</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(<span class="number">0</span>)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">apply</span>(&amp;<span class="keyword">self</span>, state: <span class="keyword">Self</span>::State, Increased: <span class="keyword">Self</span>::Event) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span>::State &#123;</span><br><span class="line">        state + <span class="number">1</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Command</code> is what we receive, <code>Event</code> is what we store, and <code>State</code> is what we never store: <code>Snapshot</code> is <code>Nothing</code> here, so the count is only ever the fold of the <code>Increased</code> events over the seed <code>0</code>. <code>apply</code> is the half of the old function which performs the transition, and it is the only transition there is, taking the state by value and returning the next one, just like <code>receive</code>. The other half, deciding, is <code>handle</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">handle</span>(</span><br><span class="line">    &amp;<span class="keyword">self</span>,</span><br><span class="line">    _: &amp;ActorContext&lt;<span class="keyword">Self</span>::Command&gt;,</span><br><span class="line">    incoming: Incoming&lt;<span class="keyword">Self</span>::Command&gt;,</span><br><span class="line">    _: &amp;<span class="keyword">Self</span>::State,</span><br><span class="line">) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;Effect&lt;<span class="keyword">Self</span>&gt;, <span class="keyword">Self</span>::Error&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> incoming &#123;</span><br><span class="line">        Incoming::<span class="title function_ invoke__">Message</span>(Command::<span class="title function_ invoke__">Increment</span>(reply_to)) =&gt; &#123;</span><br><span class="line">            <span class="title function_ invoke__">Ok</span>(Effect::<span class="title function_ invoke__">persist</span>(Increased).<span class="title function_ invoke__">then</span>(<span class="keyword">move</span> |count| reply_to.<span class="title function_ invoke__">reply</span>(*count)))</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Incoming::<span class="title function_ invoke__">Message</span>(Command::Stop) =&gt; <span class="title function_ invoke__">Ok</span>(Effect::<span class="title function_ invoke__">stop</span>()),</span><br><span class="line"></span><br><span class="line">        Incoming::<span class="title function_ invoke__">Terminated</span>(_) =&gt; <span class="title function_ invoke__">Ok</span>(Effect::<span class="title function_ invoke__">none</span>()),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Here we only get the state by reference, so we cannot change anything; all <code>handle</code> does is validate the command against the state and name the events it causes. What it returns is an <code>Effect</code>: which events to persist, whether to stop and, via <code>then</code>, what to do afterwards.</p><p>That continuation deserves a closer look, because <code>handle</code> never computes the new count. It hands <code>reply_to.reply</code> to <code>then</code> and is called back with whatever <code>apply</code> produced. Why the detour? Because settling one command runs in a fixed order: we encode the events, append them to the store, apply them only if the append succeeded, and then run the continuations. So a <code>then</code> runs only once its events are durable, and it never runs during replay, which is what makes it a safe place for outward-facing actions such as replying or telling another actor.</p><h2 id="Plugging-in-a-store"><a href="#Plugging-in-a-store" class="headerlink" title="Plugging in a store"></a>Plugging in a store</h2><p>Stores are pluggable behind two small traits: <code>EventStore</code> with <code>append</code> and <code>read</code>, and <code>SnapshotStore</code> with <code>save</code> and <code>load</code>, all of them over encoded bytes. tellus 0.2 ships one implementation of both, <code>PostgresStore</code> in the new <a href="https://crates.io/crates/tellus-persistence-postgres">tellus-persistence-postgres</a> crate, which brings its own schema along:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">store</span> = PostgresStore::<span class="title function_ invoke__">new</span>(pool);</span><br><span class="line">store.<span class="title function_ invoke__">migrate</span>().<span class="keyword">await</span>?;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="variable">system</span> = ActorSystem::<span class="title function_ invoke__">event_sourced</span>(Counter, Persistence::<span class="title function_ invoke__">new</span>(store));</span><br></pre></td></tr></table></figure><p><code>Persistence</code> is the wiring: an event store, a snapshot store (none unless we set one) and a codec (CBOR unless we set one). The call above spawns the root; a child comes from <code>ActorContext::spawn_event_sourced</code>, which takes the same two arguments. Everything else we know from the core, mailboxes, supervision, death watch and termination, works exactly as before.</p><p>Writing a store of our own is a small job, except for fencing, which is easy to get subtly wrong. Every append is conditional on the sequence number we expect it to take, and a store which does not reject a stale expectation lets two incarnations of one stream interleave. So tellus ships its contract test suite as a feature of its own, <code>persistence-tests</code>, for a backend crate to run against a real server.</p><h2 id="What-this-buys-us"><a href="#What-this-buys-us" class="headerlink" title="What this buys us"></a>What this buys us</h2><p>The full contract is in <a href="https://github.com/hseeberger/tellus/blob/main/docs/persistence.md">docs&#x2F;persistence.md</a>; the short version:</p><ul><li>Events of one effect are appended atomically and are durable before they are applied, and a command is completely settled before the next one is taken from the mailbox.</li><li>Effects are at-most-once: appended events are never lost, but a crash between the append and the continuation loses the continuation, just like a <code>tell</code>.</li><li>Replay equals live execution: <code>apply</code> is pure and total, and <code>handle</code> does not run during recovery. The stored events go to <code>apply</code>, one by one and in order, exactly as the live actor applied them, so recovery lands on the same state.</li><li>Failures go to supervision as always, and <code>Restart</code> recovers by replaying from the store.</li><li>Every stored payload carries a manifest and a schema version outside itself, so we upcast old versions on read instead of migrating the store.</li></ul><p>Both examples run against the <code>postgres</code> service of the repository’s <code>docker-compose.yaml</code>: <a href="https://github.com/tellus-rs/tellus/blob/tellus-v0.3.2/tellus-persistence-postgres/examples/event_sourced_counter.rs">event_sourced_counter.rs</a> picks up counting where the last run left off, and <a href="https://github.com/tellus-rs/tellus/blob/tellus-v0.3.2/tellus-persistence-postgres/examples/event_sourced_supervision.rs">event_sourced_supervision.rs</a> is the flaky loader from the series, now recovering its count by replay instead of starting over at zero.</p><p><em>My thoughts, written with AI assistance.</em></p>]]>
    </content>
    <id>https://heikoseeberger.de/2026-08-31-tellus-persistence/</id>
    <link href="https://heikoseeberger.de/2026-08-31-tellus-persistence/"/>
    <published>2026-08-30T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p><a href="https://github.com/hseeberger/tellus">tellus</a> 0.2 is out, and it brings the first of the two extensions I promised at the end]]>
    </summary>
    <title>tellus 0.2: event sourcing for actors</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="actors" scheme="https://heikoseeberger.de/tags/actors/"/>
    <category term="tellus" scheme="https://heikoseeberger.de/tags/tellus/"/>
    <category term="tokio" scheme="https://heikoseeberger.de/tags/tokio/"/>
    <content>
      <![CDATA[<p>In the <a href="https://heikoseeberger.de/2026-08-27-tellus-3/">previous episode</a> we ended on a detail I had leaned on without explaining: the terminated signal is the last thing a terminating actor does. In this episode we look at why it has to be last, and at the sequence that gives the signal its meaning. It is the final episode on the core of <a href="https://github.com/hseeberger/tellus">tellus</a>.</p><p>The claim we need to justify is this one. Receiving a terminated signal does not only mean that the actor has stopped taking messages. It means the actor and every one of its descendants are gone, destructors and all.</p><h2 id="The-sequence"><a href="#The-sequence" class="headerlink" title="The sequence"></a>The sequence</h2><p>Let’s look at what happens when an actor stops, whether by returning <code>Control::Stop</code>, by failing under the <code>Stop</code> strategy or because its parent is stopping. In all three cases we get the same sequence:</p><ol><li>The state is dropped, so its destructors run.</li><li>The mailbox is drained and disconnected, and the destructors of the queued messages run.</li><li>The actor deregisters itself from everything it still watches.</li><li>The children are stopped, recursively, and the actor waits until every one of them has terminated.</li><li>The actor value is dropped, so its destructors run.</li><li>The watchers are signaled.</li></ol><p>Each position in that list is a decision, and four of them are worth walking through, so let’s take them in turn.</p><h2 id="Dropping-the-state"><a href="#Dropping-the-state" class="headerlink" title="Dropping the state"></a>Dropping the state</h2><p>Notice that we drop the state before we stop the children, not after. It is consumed by the <code>receive</code> which decided to stop, or dropped right where the parent’s stop signal arrives, and either way that happens while the children are still running.</p><p>We should spell out the consequence, because it can surprise us: the destructors of the <strong>state</strong> run while the children may still be working. Children never see the state, of course; whatever a child holds we gave it, in the actor value we passed to <code>spawn</code> or in a message. So this only bites when what the child holds does not keep the resource alive: a path into a <code>TempDir</code> we own, or a <code>CancellationToken</code> whose <code>DropGuard</code> sits in our state. A cloneable handle like a connection pool is unaffected, since the child’s clone keeps the pool open whenever we drop ours. If we do hold a guard whose drop has to wait for the whole subtree, we put it in the <strong>actor value</strong>, which is dropped in step 5, after the last descendant has terminated.</p><p>This is the same split we saw in <a href="https://heikoseeberger.de/2026-08-26-tellus-2/">episode 2</a>, seen from its other side. There the actor value was what a restart keeps and the state was what a restart rebuilds. Here the actor value is what outlives the subtree and the state is what does not. One split, two payoffs, and in both cases we ask the same question: does this belong to the actor, or to its current state?</p><h2 id="The-barrier"><a href="#The-barrier" class="headerlink" title="The barrier"></a>The barrier</h2><p>Step 4 is the interesting one, because “wait until every descendant has terminated” is exactly the kind of thing we would usually implement with a counter, a registry or an acknowledgement protocol. Here we need none of them.</p><p>Every actor owns a Tokio watch channel. Its children hold receiver clones of it, and every child task selects on that receiver. Let’s look at the receiving side first:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">incoming</span> = <span class="built_in">select!</span> &#123;</span><br><span class="line">    biased;</span><br><span class="line"></span><br><span class="line">    _ = &amp;<span class="keyword">mut</span> stopped_by_parent =&gt; &#123;</span><br><span class="line">        debug!(%actor_id, <span class="string">&quot;stopping, because parent stopped this actor&quot;</span>);</span><br><span class="line">        <span class="title function_ invoke__">drop_containing_panic</span>(actor_id, STATE_FAILED_TO_DROP, state);</span><br><span class="line">        <span class="keyword">break</span> <span class="symbol">&#x27;run</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    incoming = mailbox.<span class="title function_ invoke__">recv</span>() =&gt; &#123;</span><br><span class="line">        incoming.<span class="title function_ invoke__">expect</span>(<span class="string">&quot;self_ref keeps a mailbox handle alive&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Notice the <code>biased</code>, which makes the stop branch win against a message arriving at the same moment, and the <code>drop_containing_panic</code>, which is step 1 of our sequence happening right here. Now the sending side, which is all it takes to stop the children:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">fn</span> <span class="title function_">stop_children</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> (next_stopping_tx, next_stopping_rx) = watch::<span class="title function_ invoke__">channel</span>(());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">stopping_tx</span> = mem::<span class="title function_ invoke__">replace</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>.stopping_tx, next_stopping_tx);</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">_</span> = stopping_tx.<span class="title function_ invoke__">send</span>(());</span><br><span class="line">    <span class="keyword">self</span>.stopping_rx = next_stopping_rx;</span><br><span class="line"></span><br><span class="line">    stopping_tx.<span class="title function_ invoke__">closed</span>().<span class="keyword">await</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We send the stop signal, then await <code>closed()</code>, which resolves once every receiver has been dropped. A child drops its receiver clone only when its task ends, and its task ends only after it has run this very same sequence for its own children. So what we get from the channel closing is a recursive completion proof: no descendant of any depth is still alive. Notice that we did not build that barrier, we inherited it from ownership: the channel closes because the receivers are dropped, and they are dropped because the tasks holding them have ended.</p><p>Why the fresh channel swapped in at the top? Not just as bookkeeping for restarts. The context holds a receiver clone of its own, so unless we install the next generation’s receiver first, <code>closed()</code> would be waiting on the actor itself and never resolve. That we also end up with a working channel for a new generation of children is what makes the same function serve restarts, where the actor stops the current generation and then keeps running with a new one.</p><h2 id="Draining-early-signaling-late"><a href="#Draining-early-signaling-late" class="headerlink" title="Draining early, signaling late"></a>Draining early, signaling late</h2><p>Steps 2 and 6 sit at opposite ends for opposite reasons.</p><p>We drain and disconnect the mailbox right at the start, before the children are even told to stop. That way a send is rejected rather than queued into a mailbox nobody will ever read, and it is rejected from that moment on, not only once the whole subtree is gone. What the sender makes of the rejection differs: a <code>tell</code> reports nothing to its caller, so the message is dropped and logged as a dead letter, while an <code>ask</code> gets <code>AskError::ActorTerminated</code> back instead of waiting out its timeout.</p><p>Draining also runs the destructors of the messages which were still queued, and that matters more than it sounds: a queued request carries a <code>ReplyTo</code>, and dropping it resolves the corresponding <code>ask</code> as <code>NoReply</code> instead of leaving it pending until its timeout. A send racing with the drain can still slip past it, and such a message is retained until its last sender is dropped, so this detection is best-effort, which is why we give every <code>ask</code> a timeout anyway.</p><p>The watchers, in contrast, are signaled at the very end, after the actor value has been dropped. This is what makes the guarantee from the previous episode worth having. If we sent the signal when the actor stopped receiving, it would prove only that: no more messages. Sent last, it proves that the state is gone, every descendant has terminated and the actor’s own destructors have run.</p><h2 id="Awaiting-the-actor-system"><a href="#Awaiting-the-actor-system" class="headerlink" title="Awaiting the actor system"></a>Awaiting the actor system</h2><p>So is <code>ActorSystem::terminated</code> special framework machinery? It is not. <code>spawn_root</code> spawns the root actor through exactly the same code path as any child, and then registers an ordinary watcher in the root’s watcher registry. That watcher’s sink resolves the oneshot we await in <code>terminated()</code>, and it also owns the sender of the root’s stop channel, which is what keeps the root running until its own termination has signaled its watchers.</p><p>So when we await an actor system, we are doing a death watch like any other, and it inherits the meaning we established above: it resolves exactly when the entire tree is gone. Registration is race-free the same way too, since a root which has already terminated fails the registration and the oneshot is resolved right away.</p><p>One consequence is worth spelling out, because it catches people: dropping the <code>ActorSystem</code> does not stop anything. The root stops on its own terms; dropping merely forfeits our ability to await it.</p><h2 id="When-destructors-panic"><a href="#When-destructors-panic" class="headerlink" title="When destructors panic"></a>When destructors panic</h2><p>All of this rests on destructors running, so what happens when one panics? Every drop path the framework controls is contained: the state, the queued messages and the actor value are all dropped under <code>catch_unwind</code>, so the panic is logged and termination completes, watchers included. If a panicking destructor could abandon the sequence, one bad <code>Drop</code> would leave us with a permanently stuck tree.</p><p>One case is out of reach, and it is a Rust one rather than a tellus one. If <code>init</code> or <code>receive</code> panics and, while that panic unwinds, a destructor of a value still alive in the frame panics too, Rust aborts the process. That happens below <code>catch_unwind</code> and hence below supervision, so there is nothing tellus can do about it. Well, we just have to keep our destructors panic-free.</p><h2 id="The-end-of-the-core"><a href="#The-end-of-the-core" class="headerlink" title="The end of the core"></a>The end of the core</h2><p>Over four episodes we have seen that an actor is a state machine, that its mailbox is one FIFO queue carrying both messages and signals, and that its termination is bottom-up, with the terminated signal sent last so it proves something worth knowing. The rest of tellus is built from those three ideas.</p><p>What comes next is not core but extension, and both are feature gated so tellus stays purely local and free of their dependencies by default: persistence, in the shape of event sourced actors, and remoting, where serializable actor refs let actors on different nodes message and watch each other through the same API. The second one will be an interesting post to write, because a network is precisely the thing that does not give us one FIFO queue.</p><p>And of course the code is on GitHub: <a href="https://github.com/hseeberger/tellus">tellus</a> itself, its <a href="https://github.com/hseeberger/tellus/tree/main/tellus/examples">examples</a>, and <a href="https://github.com/hseeberger/tellus/blob/main/docs/actors.md">docs&#x2F;actors.md</a>, which explains the core top-down with links into the implementation. Thank you for staying around until here, and let me know if you have any questions.</p><p><em>My thoughts, written with AI assistance.</em></p>]]>
    </content>
    <id>https://heikoseeberger.de/2026-08-27-tellus-4/</id>
    <link href="https://heikoseeberger.de/2026-08-27-tellus-4/"/>
    <published>2026-08-27T11:35:00.000Z</published>
    <summary>
      <![CDATA[<p>In the <a href="https://heikoseeberger.de/2026-08-27-tellus-3/">previous episode</a> we ended on a detail I had leaned on without explain]]>
    </summary>
    <title>tellus, episode 4: terminating a tree, bottom-up</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="actors" scheme="https://heikoseeberger.de/tags/actors/"/>
    <category term="tellus" scheme="https://heikoseeberger.de/tags/tellus/"/>
    <category term="tokio" scheme="https://heikoseeberger.de/tags/tokio/"/>
    <content>
      <![CDATA[<p>In the <a href="https://heikoseeberger.de/2026-08-26-tellus-2/">previous episode</a> we looked at an actor from the inside, as a state machine. In this one we look at it from the outside, at the mailbox and the guarantee I called the most valuable thing in <a href="https://github.com/hseeberger/tellus">tellus</a>: receiving a terminated signal for a watched actor proves that the watcher has seen every message from that actor it will ever see.</p><p>Why does that not need any coordination? Because it falls out of a single design decision.</p><h2 id="One-channel-for-both"><a href="#One-channel-for-both" class="headerlink" title="One channel for both"></a>One channel for both</h2><p>A mailbox is a FIFO channel of <code>Incoming&lt;M&gt;</code>, and <code>Incoming</code> is exactly the enum <code>receive</code> gets: either a message or a terminated signal. We split that channel into a sending half, which is cloned into every <code>ActorRef</code> pointing at the actor, and a receiving half owned by the actor’s run loop.</p><p>The decision is that terminated signals are not a side channel. They ride the very same queue as ordinary messages. So if a watched actor delivers three messages to its watcher and then terminates, those three messages and the signal enter the watcher’s queue in that order, and its run loop takes them out in that order. We have nothing else to synchronize, because a queue is already the synchronization.</p><p>With a separate signal channel we would need a rendezvous between two channels to reconstruct exactly this order. One queue gets it right by construction.</p><h2 id="Bounded-mailboxes-unbounded-channel"><a href="#Bounded-mailboxes-unbounded-channel" class="headerlink" title="Bounded mailboxes, unbounded channel"></a>Bounded mailboxes, unbounded channel</h2><p>That decision immediately collides with a second requirement. A mailbox can be bounded, and a full one drops messages. But we must never drop a terminated signal: it is what proves termination, and a lost one leaves a watcher waiting forever.</p><p>If the bound lived in the channel, those two requirements would be in direct conflict, so we do not put it there. The channel underneath is always unbounded, and we enforce the capacity with a reservation counter sitting in front of it. Let’s look at the counted side: sending a message reserves capacity first and only then enqueues, and receiving one releases the reservation:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_ invoke__">pub</span>(<span class="keyword">crate</span>) <span class="keyword">fn</span> <span class="title function_">try_send_counted</span>(&amp;<span class="keyword">self</span>, item: T) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;(), CountedSendError&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span>.quota.<span class="title function_ invoke__">reserve</span>() &#123;</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(reservation) =&gt; &#123;</span><br><span class="line">            <span class="keyword">self</span>.item_tx.<span class="title function_ invoke__">send</span>(item).<span class="title function_ invoke__">map_err</span>(|_| Disconnected)?;</span><br><span class="line">            reservation.<span class="title function_ invoke__">commit</span>();</span><br><span class="line">            <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br></pre></td></tr></table></figure><p>A terminated signal takes the other door into the same channel:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_ invoke__">pub</span>(<span class="keyword">crate</span>) <span class="keyword">fn</span> <span class="title function_">try_send_uncounted</span>(&amp;<span class="keyword">self</span>, item: T) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;(), Disconnected&gt; &#123;</span><br><span class="line">    <span class="keyword">self</span>.item_tx.<span class="title function_ invoke__">send</span>(item).<span class="title function_ invoke__">map_err</span>(|_| Disconnected)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Counted and uncounted, both ride one queue. The signal keeps its place in line behind everything sent before it, and no capacity check can ever reject it. So we have stopped capacity and ordering from being the same mechanism, and they stop fighting.</p><h2 id="What-a-watcher-actually-is"><a href="#What-a-watcher-actually-is" class="headerlink" title="What a watcher actually is"></a>What a watcher actually is</h2><p><code>ActorContext::watch(other)</code> reads as registering interest, but what we register is concrete: a watcher is a sending handle into the <em>watching</em> actor’s own mailbox, handed over to the watched actor, which keeps it in a registry.</p><p>When that actor terminates, the last thing it does is send the signal through those handles, into the same queue it has been sending messages to that watcher all along. That is the entire mechanism behind the ordering guarantee: we need no coordination and no happens-before argument spanning two channels, just a sender used twice.</p><p>Let’s look at what that buys us, in the <a href="https://github.com/tellus-rs/tellus/blob/tellus-v0.3.2/tellus/examples/scatter_gather.rs"><code>scatter_gather</code> example</a>. The root actor spawns workers, watches each and sends each a request whose <code>ReplyTo</code> delivers the partial result back as an ordinary message:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">match</span> incoming &#123;</span><br><span class="line">    Incoming::<span class="title function_ invoke__">Message</span>(<span class="title function_ invoke__">Partial</span>(sum)) =&gt; <span class="title function_ invoke__">Ok</span>(Control::<span class="title function_ invoke__">Continue</span>(Gathering &#123;</span><br><span class="line">        total: state.total + sum,</span><br><span class="line">        ..state</span><br><span class="line">    &#125;)),</span><br><span class="line"></span><br><span class="line">    <span class="comment">// The partial result of the terminated worker has already been added.</span></span><br><span class="line">    Incoming::<span class="title function_ invoke__">Terminated</span>(_) =&gt; &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">remaining</span> = state.remaining - <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> remaining &gt; <span class="number">0</span> &#123;</span><br><span class="line">            <span class="title function_ invoke__">Ok</span>(Control::<span class="title function_ invoke__">Continue</span>(Gathering &#123; remaining, ..state &#125;))</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="built_in">println!</span>(<span class="string">&quot;## Total is: &#123;&#125;&quot;</span>, state.total);</span><br><span class="line">            <span class="title function_ invoke__">Ok</span>(Control::Stop)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Notice the comment: that is the point. When the signal for a worker arrives, that worker’s partial result is already in <code>total</code>, because the worker replied before it stopped, and the reply went into this queue ahead of the signal. So counting terminated signals is all we need to know the sum is complete, and we need no timeout and no acknowledgement protocol.</p><h2 id="What-the-guarantee-does-not-say"><a href="#What-the-guarantee-does-not-say" class="headerlink" title="What the guarantee does not say"></a>What the guarantee does not say</h2><p>So is the watcher guaranteed to have seen everything the terminated actor sent? No, and we should state the difference precisely, because the guarantee is easy to overstate.</p><p>Delivery is at most once. A message told to a full bounded mailbox, or to an actor which has already terminated, is dropped and logged as a dead letter; it never enters the queue at all, and the signal cannot say anything about a message we never queued.</p><p>So the guarantee is not “the watcher has seen every message the terminated actor sent”. It is: <strong>the watcher has seen every message from that actor it will ever see</strong>, because each one either arrived ahead of the signal or was dropped as a dead letter and was never going to arrive. Nothing is in flight anymore. That is what lets us act on the signal as a completion marker, and it is exactly as strong as at-most-once delivery permits.</p><h2 id="Closing-two-smaller-races"><a href="#Closing-two-smaller-races" class="headerlink" title="Closing two smaller races"></a>Closing two smaller races</h2><p>Registration is race-free in both directions. The watcher registry is a map which termination takes and closes atomically, so an actor racing to watch a terminating actor either gets in before the close and is signaled, or fails registration and learns immediately that the actor has already terminated, in which case we deliver the signal right away. Either way we lose no watcher, and registering twice registers once.</p><p><code>unwatch</code> has a stronger contract than it appears to: after it returns, no terminated signal for that actor will be received, even if the actor has already terminated and its signal is already sitting in the queue. We cannot enforce that on the sending side, so we enforce it on the receiving side, in the run loop, before <code>receive</code> ever sees the signal:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="keyword">let</span> <span class="variable">Incoming</span>::<span class="title function_ invoke__">Terminated</span>(other) = &amp;incoming</span><br><span class="line">    &amp;&amp; !context.<span class="title function_ invoke__">take_watched_for</span>(*other)</span><br><span class="line">&#123;</span><br><span class="line">    debug!(</span><br><span class="line">        %actor_id,</span><br><span class="line">        other_id = %*other,</span><br><span class="line">        <span class="string">&quot;dropping terminated signal for an unwatched actor&quot;</span></span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">continue</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Notice that the watching actor keeps its own record of what it watches, and we drop a signal whose sender is no longer in that record. The same bookkeeping deregisters a terminating actor from everything it still watches, so dead watchers never accumulate.</p><h2 id="What-is-next"><a href="#What-is-next" class="headerlink" title="What is next"></a>What is next</h2><p>One thing above was quietly load bearing: the signal is the <em>last</em> thing a terminating actor does. Not the first, and not somewhere in the middle. In the next episode we look at why, and at the termination sequence that makes a terminated signal prove not just that the actor stopped receiving, but that it and its entire subtree are gone, destructors and all.</p><p>If you want to run this yourself, you can find the full code on GitHub: the example is <a href="https://github.com/tellus-rs/tellus/blob/tellus-v0.3.2/tellus/examples/scatter_gather.rs">scatter_gather.rs</a>, and the queue machinery is in <a href="https://github.com/hseeberger/tellus/blob/main/tellus/src/quota.rs">quota.rs</a> and <a href="https://github.com/hseeberger/tellus/blob/main/tellus/src/mailbox.rs">mailbox.rs</a>.</p><p><em>My thoughts, written with AI assistance.</em></p>]]>
    </content>
    <id>https://heikoseeberger.de/2026-08-27-tellus-3/</id>
    <link href="https://heikoseeberger.de/2026-08-27-tellus-3/"/>
    <published>2026-08-27T11:30:00.000Z</published>
    <summary>
      <![CDATA[<p>In the <a href="https://heikoseeberger.de/2026-08-26-tellus-2/">previous episode</a> we looked at an actor from the inside, as a state ma]]>
    </summary>
    <title>tellus, episode 3: one queue for messages and signals</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="actors" scheme="https://heikoseeberger.de/tags/actors/"/>
    <category term="tellus" scheme="https://heikoseeberger.de/tags/tellus/"/>
    <category term="tokio" scheme="https://heikoseeberger.de/tags/tokio/"/>
    <content>
      <![CDATA[<p>In the <a href="https://heikoseeberger.de/2026-08-24-tellus-1/">previous episode</a> I introduced <a href="https://github.com/hseeberger/tellus">tellus</a>, an actor framework for Rust built on Tokio, and claimed that its actors are state machines rather than mutable objects. In this episode we unpack that claim: what the <code>Actor</code> trait actually asks of us, how we model the state and what we get from that shape when things go wrong.</p><h2 id="Three-associated-types"><a href="#Three-associated-types" class="headerlink" title="Three associated types"></a>Three associated types</h2><p>An actor is defined by implementing <code>Actor</code>. Let’s start with its three associated types:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">impl</span> <span class="title class_">Actor</span> <span class="keyword">for</span> <span class="title class_">Manager</span> &#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Message</span> = RequestJob;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">State</span> = Managing;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span> = Infallible;</span><br></pre></td></tr></table></figure><p><code>Message</code> is what this actor accepts. We cannot send it anything else, because sends go through an <code>ActorRef&lt;M&gt;</code>: <code>tell</code> takes an <code>M</code>, and <code>ask</code> takes a function which builds one from a <code>ReplyTo</code>.</p><p><code>State</code> is what it carries between messages. Notice what we do not get: no fields on the actor value itself that change over time. <code>Manager</code> is a unit struct here, and everything mutable lives in <code>Managing</code>.</p><p><code>Error</code> is how it can fail. Both <code>init</code> and <code>receive</code> return <code>Result&lt;_, Self::Error&gt;</code>, so what we get is an ordinary value rather than an escape hatch.</p><p>Each of the three has a degenerate case, and each of those is a real type rather than a convention. For a stateless actor we use <code>()</code>, for an infallible one <code>Infallible</code>, and for an actor which receives no messages at all, a pure supervisor for instance, we use <code>Nothing</code>, an uninhabited enum:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">impl</span> <span class="title class_">Actor</span> <span class="keyword">for</span> <span class="title class_">Overseer</span> &#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Message</span> = Nothing;</span><br></pre></td></tr></table></figure><p>Why bother with an uninhabited type? Because with <code>Nothing</code> as the message type, <code>Incoming::Message</code> cannot be constructed, so the only incoming such an actor can ever get is a terminated signal from an actor it watches. The compiler knows that, and so do we.</p><h2 id="Modeling-the-state"><a href="#Modeling-the-state" class="headerlink" title="Modeling the state"></a>Modeling the state</h2><p><code>init</code> creates the initial state and <code>receive</code> maps the current state plus one incoming to the next state. Let’s look at a work-pulling manager which hands out jobs to workers as they ask for them:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Managing</span> &#123;</span><br><span class="line">    jobs: <span class="type">Vec</span>&lt;&amp;<span class="symbol">&#x27;static</span> <span class="type">str</span>&gt;,</span><br><span class="line">    workers: <span class="type">usize</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">receive</span>(</span><br><span class="line">    &amp;<span class="keyword">self</span>,</span><br><span class="line">    _: &amp;ActorContext&lt;<span class="keyword">Self</span>::Message&gt;,</span><br><span class="line">    incoming: Incoming&lt;<span class="keyword">Self</span>::Message&gt;,</span><br><span class="line">    state: <span class="keyword">Self</span>::State,</span><br><span class="line">) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;Control&lt;<span class="keyword">Self</span>::State&gt;, <span class="keyword">Self</span>::Error&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> incoming &#123;</span><br><span class="line">        Incoming::<span class="title function_ invoke__">Message</span>(<span class="title function_ invoke__">RequestJob</span>(reply_to)) =&gt; &#123;</span><br><span class="line">            <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">jobs</span> = state.jobs;</span><br><span class="line">            <span class="keyword">match</span> jobs.<span class="title function_ invoke__">pop</span>() &#123;</span><br><span class="line">                <span class="title function_ invoke__">Some</span>(document) =&gt; reply_to.<span class="title function_ invoke__">reply</span>(Next::<span class="title function_ invoke__">Job</span>(document)),</span><br><span class="line">                <span class="literal">None</span> =&gt; reply_to.<span class="title function_ invoke__">reply</span>(Next::Drained),</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="title function_ invoke__">Ok</span>(Control::<span class="title function_ invoke__">Continue</span>(Managing &#123; jobs, ..state &#125;))</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Incoming::<span class="title function_ invoke__">Terminated</span>(_) =&gt; &#123;</span><br><span class="line">            <span class="keyword">let</span> <span class="variable">workers</span> = state.workers - <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> workers &gt; <span class="number">0</span> &#123;</span><br><span class="line">                <span class="title function_ invoke__">Ok</span>(Control::<span class="title function_ invoke__">Continue</span>(Managing &#123; workers, ..state &#125;))</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="built_in">println!</span>(<span class="string">&quot;## All documents processed&quot;</span>);</span><br><span class="line">                <span class="title function_ invoke__">Ok</span>(Control::Stop)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Notice that the state arrives by value, so we can simply move <code>state.jobs</code> out and mutate it locally; the struct update syntax then puts the next state together. We get no <code>&amp;mut self</code> and no interior mutability, yet nothing here is expensive: the <code>Vec</code> is moved, not cloned.</p><p>Two more things are visible in that snippet. <code>Control</code> is the return: <code>Continue</code> carries the state which will handle the next message; <code>Stop</code> ends the actor, which stops its children first and terminates once all descendants are gone. And <code>Incoming</code> has two variants, so messages and terminated signals arrive through the same <code>receive</code>, in one FIFO order. The manager watches every worker it spawns and stops itself once the last one has terminated; since a worker only stops after the job queue is drained, the last terminated signal also means all the work is done. What we get on top from that shared FIFO order, a terminated signal proving that the watcher has seen every message from that actor it will ever see, is the subject of the next episode.</p><h2 id="Failing-and-restarting"><a href="#Failing-and-restarting" class="headerlink" title="Failing and restarting"></a>Failing and restarting</h2><p>Because <code>Error</code> is an associated type, failing is just returning. Let’s look at a loader whose domain has one toxic value, so <code>type Error = ToxicValue;</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">match</span> message &#123;</span><br><span class="line">    <span class="comment">// The error escalates to supervision, which restarts the loader with backoff.</span></span><br><span class="line">    Message::<span class="title function_ invoke__">Load</span>(TOXIC) =&gt; <span class="title function_ invoke__">Err</span>(<span class="title function_ invoke__">ToxicValue</span>(TOXIC)),</span><br><span class="line"></span><br><span class="line">    Message::<span class="title function_ invoke__">Load</span>(value) =&gt; &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">count</span> = count + <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">println!</span>(<span class="string">&quot;## Loaded &#123;value&#125;, count is &#123;count&#125;&quot;</span>);</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(Control::<span class="title function_ invoke__">Continue</span>(count))</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Message::Finish =&gt; <span class="title function_ invoke__">Ok</span>(Control::Stop),</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Returning <code>Err</code> hands the failure to the actor’s supervision strategy, and inside <code>receive</code> a <code>?</code> does the same for anything that converts into <code>Error</code>. If we would rather handle a failure as part of the domain, we write an ordinary <code>match</code> which returns <code>Control::Continue</code> with whatever state we consider correct. So we choose between escalating and handling per call site, in code, with no framework-specific ceremony either way. Panics, incidentally, take the same route: they are caught and fed to supervision exactly like a returned error.</p><p>So why separate the actor value from the state at all? Here we get the payoff. Under the <code>Restart</code> strategy, a failure stops the actor’s children, waits out an exponentially growing backoff and re-runs <code>init</code>, until a streak exceeds <code>max_restarts</code> and the actor stops instead. What we rebuild is the state, and only the state. The actor value survives, which is why we put configuration and the refs an actor was constructed with there. The mailbox survives too, so the messages queued behind the failing one are processed by the restarted state, and messages told during the backoff simply queue up. The message which caused the failure is consumed and not redelivered, which is what keeps a toxic message from turning into a restart loop.</p><p>We need no rollback mechanism for any of this. The next state only comes into existence when <code>receive</code> returns it, so a <code>receive</code> which fails or panics halfway through has not written anything. Supervision never sees a half-updated state: the failed one is dropped, and <code>init</code> builds a fresh one. In a framework built on <code>&amp;mut self</code>, we would need either a transaction or a convention that nobody violates under deadline pressure. Well, I would rather have the compiler enforce it.</p><h2 id="What-is-next"><a href="#What-is-next" class="headerlink" title="What is next"></a>What is next</h2><p>So far we have seen the state machine from the inside. In the next episode we look at it from the outside: the mailbox, which is one FIFO queue carrying both messages and terminated signals, and the ordering guarantee that falls out of that single queue.</p><p>If you want to run these yourself, you can find the full code on GitHub: the manager is <a href="https://github.com/tellus-rs/tellus/blob/tellus-v0.3.2/tellus/examples/work_pulling.rs">work_pulling.rs</a> and the loader is <a href="https://github.com/tellus-rs/tellus/blob/tellus-v0.3.2/tellus/examples/supervision.rs">supervision.rs</a>.</p><p><em>My thoughts, written with AI assistance.</em></p>]]>
    </content>
    <id>https://heikoseeberger.de/2026-08-26-tellus-2/</id>
    <link href="https://heikoseeberger.de/2026-08-26-tellus-2/"/>
    <published>2026-08-25T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>In the <a href="https://heikoseeberger.de/2026-08-24-tellus-1/">previous episode</a> I introduced <a href="https://github.com/hseeberger/]]>
    </summary>
    <title>tellus, episode 2: an actor is a state machine</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="actors" scheme="https://heikoseeberger.de/tags/actors/"/>
    <category term="tellus" scheme="https://heikoseeberger.de/tags/tellus/"/>
    <category term="tokio" scheme="https://heikoseeberger.de/tags/tokio/"/>
    <content>
      <![CDATA[<p>Nine years ago I wrote about <a href="https://heikoseeberger.de/2017-10-02-actor-model/">the actor model in Akka and Akka Typed</a>. Since then Rust has become my language of choice, but the actor model has not become any less useful. So I built <a href="https://github.com/hseeberger/tellus">tellus</a>, an actor framework for Rust on top of <a href="https://tokio.rs/">Tokio</a>, which has just been published in version 0.1.0. This is the first of a series of posts about it.</p><p>A quick recap, because the model is what everything else follows from: an actor is an independent unit of state which processes one message at a time. It has an address, and anybody knowing that address can send it a message without being blocked. When handling a message, an actor can create new actors, send messages to actors it knows and designate how the next message is handled. That is all. This fits Rust particularly well. Sharing mutable state across tasks normally means putting it behind an <code>Arc&lt;Mutex&lt;...&gt;&gt;</code>: everybody holds a reference to the same data and has to take a lock before touching it. An actor does not guard the sharing, it removes it. The state has a single owner, and because the actor handles one message at a time, that owner is the only thing ever touching it. Other tasks do not reach into the state, they send a message and carry on. What you write inside an actor is therefore ordinary sequential code over owned data, with no lock to take and none to forget.</p><p>Here is the smallest possible tellus program, an actor which greets for the one message it receives and then stops:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[tokio::main]</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">fn</span> <span class="title function_">main</span>() <span class="punctuation">-&gt;</span> anyhow::<span class="type">Result</span>&lt;()&gt; &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">system</span> = ActorSystem::<span class="title function_ invoke__">new</span>(Greeter);</span><br><span class="line"></span><br><span class="line">    system.<span class="title function_ invoke__">root</span>().<span class="title function_ invoke__">tell</span>(<span class="title function_ invoke__">Greet</span>(<span class="string">&quot;Tellus&quot;</span>.<span class="title function_ invoke__">to_string</span>()));</span><br><span class="line"></span><br><span class="line">    system</span><br><span class="line">        .<span class="title function_ invoke__">terminated</span>()</span><br><span class="line">        .<span class="keyword">await</span></span><br><span class="line">        .<span class="title function_ invoke__">context</span>(<span class="string">&quot;awaiting actor system termination&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Greeter</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">Actor</span> <span class="keyword">for</span> <span class="title class_">Greeter</span> &#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Message</span> = Greet;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">State</span> = ();</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span> = Infallible;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">init</span>(&amp;<span class="keyword">self</span>, _: &amp;ActorContext&lt;<span class="keyword">Self</span>::Message&gt;) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;<span class="keyword">Self</span>::State, <span class="keyword">Self</span>::Error&gt; &#123;</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">receive</span>(</span><br><span class="line">        &amp;<span class="keyword">self</span>,</span><br><span class="line">        _: &amp;ActorContext&lt;<span class="keyword">Self</span>::Message&gt;,</span><br><span class="line">        incoming: Incoming&lt;<span class="keyword">Self</span>::Message&gt;,</span><br><span class="line">        _: <span class="keyword">Self</span>::State,</span><br><span class="line">    ) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;Control&lt;<span class="keyword">Self</span>::State&gt;, <span class="keyword">Self</span>::Error&gt; &#123;</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">let</span> <span class="variable">Incoming</span>::<span class="title function_ invoke__">Message</span>(<span class="title function_ invoke__">Greet</span>(name)) = incoming &#123;</span><br><span class="line">            <span class="built_in">println!</span>(<span class="string">&quot;Hello, &#123;name&#125;!&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(Control::Stop)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Greet</span>(<span class="type">String</span>);</span><br></pre></td></tr></table></figure><p>An actor is defined by implementing the <code>Actor</code> trait with three associated types: the <code>Message</code> it accepts, the <code>State</code> it carries and the <code>Error</code> it can fail with. <code>init</code> creates the initial state and <code>receive</code> handles one incoming message or signal.</p><h2 id="Why-another-one"><a href="#Why-another-one" class="headerlink" title="Why another one?"></a>Why another one?</h2><p>Rust already has actor libraries, <a href="https://crates.io/crates/kameo">kameo</a> and <a href="https://crates.io/crates/ractor">ractor</a> among them, and they are good. So the question is fair, and there are three answers.</p><p><strong>Message handling is not async.</strong> <code>Actor::receive</code> is a plain synchronous function, as the example above shows. There is no <code>async fn</code> in the trait, no future allocated and polled per message, no <code>Box&lt;dyn Future&gt;</code> and no handler future which must be <code>Send</code>, a bound which otherwise ripples into every local held across an await. That is the smaller half of the benefit though. The larger half is what cannot happen: actor code cannot await, so it cannot hold state across an await point, and there is no way to accidentally suspend in the middle of applying a message. One message at a time stops being a rule you have to respect and becomes a property of the type signature. This also removes an ambiguity that async handlers always carry, namely what the mailbox is doing while a handler awaits, a question a reader of your code should never have to ask.</p><p>What you give up is real and worth stating: an actor cannot be stopped while <code>receive</code> is running, so a <code>receive</code> which never returns keeps all of its ancestors from terminating. For long running or blocking work you spawn a Tokio task and send the result back to the actor as an ordinary message. That is a little more ceremony, and in exchange nothing can interrupt <code>receive</code>: there is no point inside it at which the actor can be suspended or cancelled, so handling a message is one uninterrupted step.</p><p><strong>Actors are state machines, not mutable objects.</strong> <code>receive</code> takes the current state by value and returns the state for the next message, wrapped in <code>Control::Continue</code>, or <code>Control::Stop</code> to stop the actor. No <code>&amp;mut self</code>, no interior mutability. Beyond reading nicely, this pays off under failure: a <code>receive</code> which panics or returns an error cannot leave a half-updated state behind, because the next state only exists once <code>receive</code> has returned it. Failures are values, so inside <code>receive</code> you use <code>?</code> to escalate a failure to supervision and an explicit <code>match</code> to handle it as part of your domain.</p><p><strong>Supervision trees and death watch, taken from Akka rather than bolted on.</strong> Actors form a tree: an <code>ActorSystem</code> spawns the root actor and every actor spawns children via <code>ActorContext::spawn</code>. Stopping an actor stops its subtree, children first, and only once all descendants have terminated does it terminate itself, which is what makes <code>ActorSystem::terminated</code> mean what you want it to mean. Failures, errors and panics alike, are handled by the actor’s supervision strategy, either stopping it or restarting it, which rebuilds the state but keeps the mailbox, limited and paced by exponential backoff.</p><p>Death watch comes with an ordering guarantee that I consider the most valuable thing in the library: <code>ActorContext::watch</code> delivers a terminated signal which is ordered behind every message the terminated actor has delivered to the watcher. Receiving the signal therefore proves that the watcher has seen every message from that actor it will ever see. That turns “am I done collecting results?” from a heuristic into a fact, and it is what the <code>scatter_gather</code> example is built on. How that guarantee is implemented is a good story on its own, and it will be the subject of a later episode.</p><h2 id="Telling-and-asking"><a href="#Telling-and-asking" class="headerlink" title="Telling and asking"></a>Telling and asking</h2><p>Sending a message is fire-and-forget: <code>ActorRef::tell</code> never blocks and delivers at most once. A message which cannot be delivered, whether because the actor has terminated or because its bounded mailbox is full, is dropped and logged as a dead letter. Sometimes you do want an answer though, and from outside the actor tree <code>ActorRef::ask</code> gives you one:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">system</span> = ActorSystem::<span class="title function_ invoke__">new</span>(Counter);</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> <span class="variable">_</span> <span class="keyword">in</span> <span class="number">0</span>..<span class="number">3</span> &#123;</span><br><span class="line">    system.<span class="title function_ invoke__">root</span>().<span class="title function_ invoke__">tell</span>(Message::Increment);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Queued behind the increments above, so the reply is the full count.</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">count</span> = system</span><br><span class="line">    .<span class="title function_ invoke__">root</span>()</span><br><span class="line">    .<span class="title function_ invoke__">ask</span>(Duration::<span class="title function_ invoke__">from_secs</span>(<span class="number">1</span>), Message::Get)</span><br><span class="line">    .<span class="keyword">await</span></span><br><span class="line">    .<span class="title function_ invoke__">context</span>(<span class="string">&quot;asking for the count&quot;</span>)?;</span><br><span class="line"><span class="built_in">println!</span>(<span class="string">&quot;The count is: &#123;count&#125;&quot;</span>);</span><br></pre></td></tr></table></figure><p><code>ask</code> takes a timeout and a function turning a <code>ReplyTo</code> into a message, which is why the enum variant constructor <code>Message::Get</code> can be passed directly. On the actor side a request is nothing special; the counter handles all its messages in the same <code>receive</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">match</span> message &#123;</span><br><span class="line">    Message::Increment =&gt; <span class="title function_ invoke__">Ok</span>(Control::<span class="title function_ invoke__">Continue</span>(count + <span class="number">1</span>)),</span><br><span class="line"></span><br><span class="line">    Message::<span class="title function_ invoke__">Get</span>(reply_to) =&gt; &#123;</span><br><span class="line">        reply_to.<span class="title function_ invoke__">reply</span>(count);</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(Control::<span class="title function_ invoke__">Continue</span>(count))</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Message::Stop =&gt; <span class="title function_ invoke__">Ok</span>(Control::Stop),</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The mailbox is FIFO, so the reply reflects every increment told before the ask. And no future shows up in the actor: awaiting happens on the calling side only. Between actors you do not await at all; <code>ActorContext::reply_to</code> creates a <code>ReplyTo</code> which delivers the reply into the asking actor’s own mailbox, where it arrives through <code>receive</code> like any other message.</p><h2 id="What-is-next"><a href="#What-is-next" class="headerlink" title="What is next"></a>What is next</h2><p>tellus is at 0.1.0; it is under active development and its API is still settling. Two extensions are in the works, both feature gated so that tellus stays purely local and free of their dependencies by default: persistence, in the shape of event sourced actors, and remoting, where serializable actor refs let actors on different nodes message and watch each other through the very same API.</p><p>The <a href="https://github.com/hseeberger/tellus">repository</a> has <a href="https://github.com/hseeberger/tellus/tree/main/tellus/examples">six examples</a>, from the greeter above to a version of Akka’s IoT device manager, and <a href="https://github.com/hseeberger/tellus/blob/main/docs/actors.md">docs&#x2F;actors.md</a> explains the core top-down with links into the implementation. In the next episode we will take a closer look at the actor as a state machine.</p><p><em>My thoughts, written with AI assistance.</em></p>]]>
    </content>
    <id>https://heikoseeberger.de/2026-08-24-tellus-1/</id>
    <link href="https://heikoseeberger.de/2026-08-24-tellus-1/"/>
    <published>2026-08-23T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Nine years ago I wrote about <a href="https://heikoseeberger.de/2017-10-02-actor-model/">the actor model in Akka and Akka Typed</a>. Sinc]]>
    </summary>
    <title>tellus, episode 1: actors for Rust, on solid ground</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="axum" scheme="https://heikoseeberger.de/tags/axum/"/>
    <content>
      <![CDATA[<p>There are several ways to handle API versioning and two of the most prominent are path-based and header-based. Path-based means to include the version as a prefix segment, e.g. <code>https://api.example.com/v1/test</code> and header-based means to convey the desired version as a HTTP header. There are pros and cons for both and as always the choice&#x2F;decision depends&#x2F;is hard.</p><p>In <a href="https://docs.rs/axum/latest/axum/">axum</a> it is quite simple to implement path-based versioning, e.g.:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">app</span> = Router::<span class="title function_ invoke__">new</span>()</span><br><span class="line">    .<span class="title function_ invoke__">route</span>(<span class="string">&quot;/&quot;</span>, <span class="title function_ invoke__">get</span>(ready))</span><br><span class="line">    .<span class="title function_ invoke__">route</span>(<span class="string">&quot;/v0/test&quot;</span>, <span class="title function_ invoke__">get</span>(ok_0))</span><br><span class="line">    .<span class="title function_ invoke__">route</span>(<span class="string">&quot;/v1/test&quot;</span>, <span class="title function_ invoke__">get</span>(ok_1));</span><br></pre></td></tr></table></figure><p>Here we define a readiness endpoint <code>/</code> without any version and a versioned <code>/test</code> endpoint for the versions “v0” and “v1”. Of course the <code>/test</code> endpoints can be called like this:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">http localhost:8080/v0/test</span><br><span class="line">http localhost:8080/v1/test</span><br></pre></td></tr></table></figure><p>But what if we (also) want to support header-based versioning, i.e. call like this:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">http localhost:8080/test x-api-version:v0</span><br><span class="line">http localhost:8080/test x-api-version:v1</span><br></pre></td></tr></table></figure><p>Well, this is not too hard to implement on your own, but I created a little library that helps you: welcome <code>api-version</code>!</p><p>All you have to do is</p><ul><li>define the valid versions which usually are some contiguous range and </li><li>wrap your router with the <code>api-version</code> middleware:</li></ul><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> API_VERSIONS: ApiVersions&lt;<span class="number">2</span>&gt; = ApiVersions::<span class="title function_ invoke__">new</span>([<span class="number">0</span>, <span class="number">1</span>]);</span><br><span class="line"><span class="keyword">let</span> <span class="variable">app</span> = Router::<span class="title function_ invoke__">new</span>()</span><br><span class="line">    .<span class="title function_ invoke__">route</span>(<span class="string">&quot;/&quot;</span>, <span class="title function_ invoke__">get</span>(ready))</span><br><span class="line">    .<span class="title function_ invoke__">route</span>(<span class="string">&quot;/v0/test&quot;</span>, <span class="title function_ invoke__">get</span>(ok_0))</span><br><span class="line">    .<span class="title function_ invoke__">route</span>(<span class="string">&quot;/v1/test&quot;</span>, <span class="title function_ invoke__">get</span>(ok_1));</span><br><span class="line"><span class="keyword">let</span> <span class="variable">app</span> = ApiVersionLayer::<span class="title function_ invoke__">new</span>(API_VERSIONS, ReadyFilter).<span class="title function_ invoke__">layer</span>(app);</span><br></pre></td></tr></table></figure><p>BTW, you do not have to use a <code>const</code> for the creation of the <code>ApiVersions</code>, but it will give you some compile time safety.</p><p>For more details take a look at <a href="https://github.com/hseeberger/api-version">api-version on GitHub</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2025-03-20-api-version/</id>
    <link href="https://heikoseeberger.de/2025-03-20-api-version/"/>
    <published>2025-03-19T22:00:00.000Z</published>
    <summary>
      <![CDATA[<p>There are several ways to handle API versioning and two of the most prominent are path-based and header-based. Path-based means to includ]]>
    </summary>
    <title>api-version: axum middleware for header based version selection</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="tokio" scheme="https://heikoseeberger.de/tags/tokio/"/>
    <content>
      <![CDATA[<p>A lot has been written about concurrency and parallelism, so we only briefly define what is needed for this article: <strong>Concurrent programming</strong> allows us to run two or more computations in overlapping periods of time and <strong>parallel execution</strong> means that two or more computations are actually executed at the same instant. So a concurrent program could be executed in parallel, but it need not be.</p><p>Tokio, Rust’s prevalent async runtime, provides <strong>task</strong>s as the main vehicle for concurrent programming. When using the multi-thread scheduler, tasks are executed on a thread pool. As most modern processors offer several CPU cores, a number of tasks – up to the number of CPU cores – can be executed in parallel. So parallel execution is a property of the hardware.</p><p>In Tokio, <strong>Future</strong>s – Rust’s abstraction for asynchronous computations – are executed within the context of a task which drives them to completion. It is possible to await two or more futures concurrently, but if they are driven by the same task, they are not executed in parallel.</p><p>Assuming we want our programs to be as performant as possible, should we then always spawn a task when awaiting concurrent futures? Not necessarily, because that depends on how much work these futures contain that can be parallized.</p><p>I&#x2F;O heavy futures mostly await low-level asyncronous and non-blocking operating system calls to get back, so a single task can easily drive a lot of these futures, i.e. these futures are executed “effectively in parallel”.</p><p>Yet when it comes to CPU heavy asynchronous computations, a single future effectively blocks its driving task and prevents other futures from making progress. Then is makes sense to spawn a task for each such future.</p><p>Let’s look at an example, extremely simplified, but with a realistic real-world background: a streaming pipeline, continuously fetching data from some remote endpoint, which is I&#x2F;O heavy, then transforming the retrieved data, which is CPU heavy. To keep things simple, we provide two dummy functions for these stages:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// A future that awaits some asyncronous and non-blocking, e.g. I/O related, work.</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">async</span> <span class="keyword">fn</span> <span class="title function_">fetch</span>() &#123;</span><br><span class="line">    <span class="comment">// No work is performed while awaiting.</span></span><br><span class="line">    <span class="title function_ invoke__">sleep</span>(ONE_SECOND).<span class="keyword">await</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// A future that is keeping the CPU busy.</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">async</span> <span class="keyword">fn</span> <span class="title function_">transform</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">end</span> = OffsetDateTime::<span class="title function_ invoke__">now_utc</span>() + ONE_SECOND;</span><br><span class="line">    <span class="comment">// As Tokio tasks are scheduled cooperatively, this busy spin runs uninterrupted to the end.</span></span><br><span class="line">    <span class="keyword">while</span> OffsetDateTime::<span class="title function_ invoke__">now_utc</span>() &lt;= end &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The basic streaming pipeline looks like this:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">pipeline</span> = stream::<span class="title function_ invoke__">repeat_with</span>(OffsetDateTime::now_utc)</span><br><span class="line">    .<span class="title function_ invoke__">then</span>(|_| <span class="title function_ invoke__">fetch</span>())</span><br><span class="line">    .<span class="title function_ invoke__">then</span>(|_| <span class="title function_ invoke__">transform</span>());</span><br></pre></td></tr></table></figure><p>Notice, that <code>then</code> – a stream combinator from <code>futures::StreamExt</code> – is sort of an asynchronous version of <code>map</code>, i.e. transforms the stream’s items by awaiting the given future.</p><p>In order to measure the performance – the time the pipeline needs to process four items – we use the following program:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[tokio::main]</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">start</span> = OffsetDateTime::<span class="title function_ invoke__">now_utc</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">pipeline</span> = ...</span><br><span class="line"></span><br><span class="line">    pipeline.<span class="title function_ invoke__">take</span>(<span class="number">4</span>).<span class="title function_ invoke__">count</span>().<span class="keyword">await</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">duration</span> = OffsetDateTime::<span class="title function_ invoke__">now_utc</span>() - start;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;Duration: &#123;duration&#125;&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Running this with the above pipeline will take eight seconds, because so far there are no concurrent stages and therefore items flow through the pipeline one by one, each taking one second for <code>fetch</code> and <code>transform</code>.</p><p>Concurrency can be introduced with the <code>buffered</code> combinator which expects the stream items to be futures. It awaits up to the given number of futures concurrently and emits their output into the stream in the original order.</p><p>Assuming <code>NUM_CPUS</code> is set to the actual number of available CPU cores, which could be determined using the <a href="https://crates.io/crates/num_cpus">num_cpus crate</a>, and also assuming that <code>NUM_CPUS</code> is larger or equal four (the number of items we take from the stream in the above program) the pipeline now looks like this:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">pipeline</span> = stream::<span class="title function_ invoke__">repeat_with</span>(OffsetDateTime::now_utc)</span><br><span class="line">    .<span class="title function_ invoke__">map</span>(|_| <span class="title function_ invoke__">fetch</span>())</span><br><span class="line">    .<span class="title function_ invoke__">buffered</span>(NUM_CPUS)</span><br><span class="line">    .<span class="title function_ invoke__">map</span>(|_| <span class="title function_ invoke__">transform</span>())</span><br><span class="line">    .<span class="title function_ invoke__">buffered</span>(NUM_CPUS);</span><br></pre></td></tr></table></figure><p>Notice that replace <code>then</code> with <code>map</code>, because we no longer want to await the <code>fetch</code> and <code>transform</code> futures, but we want to feed them into <code>buffered</code> which awaits them concurrently.</p><p>Now guess how long the above program takes. Still eight seconds? Or only two? Or maybe … five? It’s the latter. As explained above, <code>fetch</code> is executed “effectively in parallel”, so it only takes one second for all four items to pass that stage. Yet <code>transform</code> keeps the single executing task busy for one second for each item, hence it takes four seconds for that stage.</p><p>Therefore we need to spawn a task for each stream item to execute <code>transform</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">pipeline</span> = stream::<span class="title function_ invoke__">repeat_with</span>(OffsetDateTime::now_utc)</span><br><span class="line">    .<span class="title function_ invoke__">map</span>(|_| <span class="title function_ invoke__">fetch</span>())</span><br><span class="line">    .<span class="title function_ invoke__">buffered</span>(NUM_CPUS)</span><br><span class="line">    .<span class="title function_ invoke__">map</span>(|_| task::<span class="title function_ invoke__">spawn</span>(<span class="title function_ invoke__">transform</span>()))</span><br><span class="line">    .<span class="title function_ invoke__">buffered</span>(NUM_CPUS);</span><br></pre></td></tr></table></figure><p>Now we got the time to run the program down to only two seconds. And this is the optimum we can get. In particular it would not help to increase the argument for the second <code>buffered</code> call to some value larger than the number of CPU cores, because that hardware property limits the amount of parallelism we can get. Yet increasing the argument for the first <code>buffered</code> call might make sense, because prefetching some more items via I&#x2F;O could possibly pay off in cases of occasional I&#x2F;O bottlenecks.</p><p>If you want to check this out yourself, you can find the code at <a href="https://github.com/hseeberger/demo-concurrent-parallel-futures">demo-concurrent-parallel-futures on GitHub</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2025-01-15-concurrent-parallel-futures/</id>
    <link href="https://heikoseeberger.de/2025-01-15-concurrent-parallel-futures/"/>
    <published>2025-01-14T22:00:00.000Z</published>
    <summary>
      <![CDATA[<p>A lot has been written about concurrency and parallelism, so we only briefly define what is needed for this article: <strong>Concurrent p]]>
    </summary>
    <title>Concurrent and parallel future execution in Rust</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <content>
      <![CDATA[<h1 id="Or-“The-build-within-a-build-within-…”"><a href="#Or-“The-build-within-a-build-within-…”" class="headerlink" title="Or “The build within a build within …”"></a>Or “The build within a build within …”</h1><p>Recently my colleagues at <a href="https://www.scnd.com/">SCND</a> and myself found ourselves facing an interesting challenge: building a Docker image of an application written in Rust and depending on Rust libraries in private GitHub repositories; of course not locally, but via GitHub Actions.</p><p>So this can be broken down into a couple of smaller challenges:</p><ol><li>Build a Docker image of a Rust application in GitHub Actions.</li><li>Depend on a library in a private GitHub repository in GitHub Actions.</li><li>See how to integrate the two above.</li></ol><h2 id="Docker-build"><a href="#Docker-build" class="headerlink" title="Docker build"></a>Docker build</h2><p>There are fantastic publicly available GitHub Actions to build (and publish) a Docker image, e.g. <code>docker/metadata-action</code>, <code>docker/login-action</code> and in particular <code>build-push-action</code>. Given a <code>Dockerfile</code> and the necessary GitHub permissions, a partial workflow could look like this:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">release</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">tags:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">v*</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">release:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Docker</span> <span class="string">metadata</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/metadata-action@v5</span></span><br><span class="line">        <span class="attr">id:</span> <span class="string">meta</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">images:</span> <span class="string">ghcr.io/&lt;ORGANIZATION&gt;/$&#123;&#123;</span> <span class="string">github.event.repository.name</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">tags:</span> <span class="string">type=semver,pattern=&#123;&#123;version&#125;&#125;</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Login</span> <span class="string">to</span> <span class="string">GitHub</span> <span class="string">Container</span> <span class="string">Registry</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/login-action@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">registry:</span> <span class="string">ghcr.io</span></span><br><span class="line">          <span class="attr">username:</span> <span class="string">$&#123;&#123;</span> <span class="string">github.actor</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">password:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.GITHUB_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Docker</span> <span class="string">build</span> <span class="string">and</span> <span class="string">push</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/build-push-action@v5</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">tags:</span> <span class="string">$&#123;&#123;</span> <span class="string">steps.meta.outputs.tags</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">labels:</span> <span class="string">$&#123;&#123;</span> <span class="string">steps.meta.outputs.labels</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">push:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>And a <code>Dockerfile</code> could look like this:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ARG</span> RUST_VERSION=<span class="number">1.75</span>.<span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">FROM</span> rust:$&#123;RUST_VERSION&#125;-bookworm AS builder</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> \</span></span><br><span class="line"><span class="language-bash">  --mount=<span class="built_in">type</span>=cache,target=/app/target/ \</span></span><br><span class="line"><span class="language-bash">  --mount=<span class="built_in">type</span>=cache,target=/usr/local/cargo/registry/ \</span></span><br><span class="line"><span class="language-bash">  cargo build --release &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">  <span class="built_in">cp</span> ./target/release/&lt;APP&gt; /</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">FROM</span> debian:bookworm-slim AS final</span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> adduser \</span></span><br><span class="line"><span class="language-bash">  --disabled-password \</span></span><br><span class="line"><span class="language-bash">  --gecos <span class="string">&quot;&quot;</span> \</span></span><br><span class="line"><span class="language-bash">  --home <span class="string">&quot;/nonexistent&quot;</span> \</span></span><br><span class="line"><span class="language-bash">  --shell <span class="string">&quot;/sbin/nologin&quot;</span> \</span></span><br><span class="line"><span class="language-bash">  --no-create-home \</span></span><br><span class="line"><span class="language-bash">  --uid <span class="string">&quot;10001&quot;</span> \</span></span><br><span class="line"><span class="language-bash">  appuser</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /&lt;APP&gt; /usr/local/bin</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chown</span> appuser /usr/local/bin/&lt;APP&gt;</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /app/config /opt/&lt;APP&gt;/config</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chown</span> -R appuser /opt/&lt;APP&gt;</span></span><br><span class="line"><span class="keyword">USER</span> appuser</span><br><span class="line"><span class="keyword">ENV</span> RUST_LOG=<span class="string">&quot;&lt;APP&gt;=debug,info&quot;</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /opt/&lt;APP&gt;</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;&lt;APP&gt;&quot;</span>]</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span>/tcp</span><br></pre></td></tr></table></figure><p>Unless the application depends on libraries in private GitHub repositories, the <code>release</code> job will execute successfully.</p><h2 id="Dependencies-on-libraries-in-private-GitHub-repository"><a href="#Dependencies-on-libraries-in-private-GitHub-repository" class="headerlink" title="Dependencies on libraries in private GitHub repository"></a>Dependencies on libraries in private GitHub repository</h2><p>Locally we can easily depend on libraries in private GitHub repositories using Cargo’s <code>git</code> dependency feature with ssh URLs:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">foo = &#123; git = &quot;ssh://git@github.com/&lt;&lt;ORGANIZATION&gt;&gt;/foo&quot; &#125;</span><br></pre></td></tr></table></figure><p>For this to work, we just need to have our personal public SSH key registered at GitHub, what most every developer has. We also have to tweak a specific Cargo setting via the <code>.cargo/config.toml</code> file:</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[net]</span></span><br><span class="line"><span class="attr">git-fetch-with-cli</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>If we want CI to be able to build and test applications with such ssh dependencies, we have to put in some more effort. First we need to create a pair of SSH keys. Then we add the public one as deploy key to the GitHub repository which hosts the library dependency. Next we add the private key as action secret to the GitHub repository of the application. And finally we add the fantastic public <code>webfactory/ssh-agent</code> GitHub Action to our build:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">SSH</span> <span class="string">agent</span></span><br><span class="line">  <span class="attr">uses:</span> <span class="string">webfactory/ssh-agent@v0.8.0</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="attr">ssh-private-key:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.&lt;PRIVATE_KEY&gt;</span> <span class="string">&#125;&#125;</span></span><br></pre></td></tr></table></figure><p>With all that in place, we can write workflows with jobs&#x2F;steps to compile, test, etc. our application. But all of that takes place within the GitHub runner. It is still not possible to execute the above <code>release</code> job.</p><p>Why? Because the <code>webfactory/ssh-agent</code> GitHub Action does the “SSH magic”, but only within the context of the GitHub runner. When GitHub Actions starts the Docker build which then starts the build of the Rust application – notice the build within a build within … – neither the SSH public key of the GitHub server hosting the library nor the private key for accessing that repository are available.</p><h2 id="Integration"><a href="#Integration" class="headerlink" title="Integration"></a>Integration</h2><p>Luckily it is quite easy to solve the resulting challenge. There are numerous somewhat relevant examples in the internet, but none could be found for our exact use case. Yet we were able to put the pieces together.</p><p>First the SSH agent socket needs to be passed down to the Docker build, which can be achieved via the ssh option of the <code>docker/build-push-action</code> GitHub action:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Docker</span> <span class="string">build</span> <span class="string">and</span> <span class="string">push</span></span><br><span class="line">  <span class="attr">uses:</span> <span class="string">docker/build-push-action@v5</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="attr">tags:</span> <span class="string">$&#123;&#123;</span> <span class="string">steps.meta.outputs.tags</span> <span class="string">&#125;&#125;</span></span><br><span class="line">    <span class="attr">labels:</span> <span class="string">$&#123;&#123;</span> <span class="string">steps.meta.outputs.labels</span> <span class="string">&#125;&#125;</span></span><br><span class="line">    <span class="attr">push:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">ssh:</span> <span class="string">default=$&#123;&#123;</span> <span class="string">env.SSH_AUTH_SOCK</span> <span class="string">&#125;&#125;</span></span><br></pre></td></tr></table></figure><p>This essentially makes the private SSH key for the library in the private GitHub repository available within the Docker build. In order to also make it available one <del>dream</del> build level deeper, i.e. in the Rust build, the respective <code>RUN</code> needs to be given the <code>--mount=type=ssh</code> option:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="language-bash"> \</span></span><br><span class="line"><span class="language-bash">  --mount=<span class="built_in">type</span>=cache,target=/app/target/ \</span></span><br><span class="line"><span class="language-bash">  --mount=<span class="built_in">type</span>=cache,target=/usr/local/cargo/registry/ \</span></span><br><span class="line"><span class="language-bash">  --mount=<span class="built_in">type</span>=ssh \</span></span><br><span class="line"><span class="language-bash">  cargo build --release &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">  <span class="built_in">cp</span> ./target/release/&lt;APP&gt; /</span></span><br></pre></td></tr></table></figure><p>Now we are almost there. The last missing piece is to add the public key of the GitHub server to the <code>known_hosts</code> file in the builder layer in the Dockerfile, of course before the above build:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="language-bash"> \</span></span><br><span class="line"><span class="language-bash">  apt-get update &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">  apt-get install -y jq &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">  <span class="built_in">mkdir</span> -p -m 0700 ~/.ssh &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">  curl --silent https://api.github.com/meta  | jq --raw-output <span class="string">&#x27;&quot;github.com &quot;+.ssh_keys[]&#x27;</span> &gt;&gt; ~/.ssh/known_hosts &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">  <span class="built_in">chmod</span> 600 ~/.ssh/known_hosts</span></span><br></pre></td></tr></table></figure><p>Phew, we are finally there! So it turns out that the essence of making this work is to pass the private SSH key down from the “top level” build, where it is defined as an action secret, to the Docker build from where it needs to be passed down to the Rust build. Inception at its best! Of course we also must not forget to add the public key of the GitHub server to <code>known_hosts</code>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2024-01-06-inception-style-build/</id>
    <link href="https://heikoseeberger.de/2024-01-06-inception-style-build/"/>
    <published>2024-01-05T22:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="Or-“The-build-within-a-build-within-…”"><a href="#Or-“The-build-within-a-build-within-…”" class="headerlink" title="Or “The build wi]]>
    </summary>
    <title>Inception style builds with private GitHub dependencies</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <content>
      <![CDATA[<p>In Rust an <a href="https://doc.rust-lang.org/std/error/trait.Error.html"><code>Error</code></a> could have a <a href="https://doc.rust-lang.org/std/error/trait.Error.html#method.cause"><code>cause</code></a> which could also have a cause … Put another way: there is an error chain.</p><p>Sometimes it might be useful to print the whole error chain; probably not when showing errors to users&#x2F;clients, but most probably when logging in order to understand the root cause of an error.</p><p>In Rust we also have two very prominent “error libraries”: <a href="https://docs.rs/anyhow/latest/anyhow"><code>anyhow</code></a> and <a href="https://docs.rs/thiserror/latest/thiserror"><code>thiserror</code></a>. Simplified, the former is for binaries and the latter for libraries.</p><p><code>anyhow</code> has two very nice features. First, we can attach “context” to almost any error via the <code>context</code> extension method, which essentially creates an <code>anyhow::Error</code> with the error at hand as source. Second, we can use the alternate way of formatting – <code>&quot;&#123;:#&#125;&quot;</code> – to print the whole error chain:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">error!(error = <span class="built_in">format!</span>(<span class="string">&quot;&#123;error:#&#125;&quot;</span>), <span class="string">&quot;cannot do this&quot;</span>);</span><br></pre></td></tr></table></figure><p>This snippet is assuming the <code>tracing</code> library being used and some <code>error</code> of type <code>anyhow::Error</code> in scope.</p><p>All right, this works fine for binaries, i.e. when we can use <code>anyhow::Error</code>. But what if we want to print – using <code>tracing::error</code> or similar – errors in library code? There we typically do not use <code>anyhow::Error</code>, but instead some custom error types, probably derived by <code>thiserror</code>. Using the above snippet for such a custom error would just print the error and omit the sources.</p><p>To get the whole error chain printed, we could either write some custom <code>print_error_chain</code> function and invoke it every time we want to print an error in library code, or simply leverage <code>anyhow</code>. Not the full <code>anyhow</code> machinery, but just the <code>anyhow!</code> macro, which is able to transform any error into an <code>anyhow::Error</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">error!(error = <span class="built_in">format!</span>(<span class="string">&quot;&#123;:#&#125;&quot;</span>, anyhow!(error)), <span class="string">&quot;cannot do this&quot;</span>);</span><br></pre></td></tr></table></figure><p>This way we do not use <code>anyhow::Error</code> or <code>anyhow::Result</code> in any signatures (parameters or return types) in our library code, but we get the error chains printed very easily.</p><p>What do you think about this approach? I am looking forward to your comments.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2024-01-01-printing-errors/</id>
    <link href="https://heikoseeberger.de/2024-01-01-printing-errors/"/>
    <published>2023-12-31T22:00:00.000Z</published>
    <summary>
      <![CDATA[<p>In Rust an <a href="https://doc.rust-lang.org/std/error/trait.Error.html"><code>Error</code></a> could have a <a href="https://doc.rust-l]]>
    </summary>
    <title>Printing errors in Rust</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="tracing" scheme="https://heikoseeberger.de/tags/tracing/"/>
    <category term="opentelemetry" scheme="https://heikoseeberger.de/tags/opentelemetry/"/>
    <content>
      <![CDATA[<p>In the previous episodes we coverd <a href="https://heikoseeberger.de/2023-07-29-dist-tracing-1/">logging basics</a>, <a href="https://heikoseeberger.de/2023-08-18-dist-tracing-2/">tracing basics</a> and <a href="https://heikoseeberger.de/2023-08-28-dist-tracing-3">inter-service tracing</a>. Now it is time to finish up this series with explaining how to correlate logs and traces.</p><p>Why should we bother? Well, if we think about an ERROR level log event, we hopefully get a nice error message and some additional fields, but what about the bigger context? Wouldn’t it be useful to know the complete flow of execution through the system from end to end, i.e. the trace, during which the error occured, with all its additional fields, in particular from higher up in the call stack?</p><p>In order to achieve that goal, we simply have to add the trace ID as field to every log event. But of course we do not want to do that manually and luckily this is not necessary at all.</p><p>As the Tokio tracing framework only knows spans, we have to use <code>tracing-opentelemetry</code> to get from tracing spans to OpenTelemetry spans and from there to OpenTelemetry traces:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// Recorcd the OTel trace ID of the given request as &quot;trace_id&quot; field in the current span.</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">record_trace_id</span>(request: Request&lt;Body&gt;) <span class="punctuation">-&gt;</span> Request&lt;Body&gt; &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">span</span> = Span::<span class="title function_ invoke__">current</span>(); <span class="comment">// Tokio tracing span.</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">span</span> = span.<span class="title function_ invoke__">context</span>().<span class="title function_ invoke__">span</span>(); <span class="comment">// OpenTelemetry span via OpenTelemetrySpanExt.</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">trace_id</span> = span.<span class="title function_ invoke__">span_context</span>().<span class="title function_ invoke__">trace_id</span>(); <span class="comment">// OpenTelemetry trace ID.</span></span><br><span class="line">    span.<span class="title function_ invoke__">record</span>(<span class="string">&quot;trace_id&quot;</span>, trace_id.<span class="title function_ invoke__">to_string</span>());</span><br><span class="line"></span><br><span class="line">    request</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We can use this function taking a <code>Request</code> and returning it unchanged, thereby recording the OpenTelemetry trace ID as a span field, as an addtional layer for <code>hello-tracing-gateway</code> and likewise for <code>hello-tracing-backend</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ServiceBuilder::<span class="title function_ invoke__">new</span>()</span><br><span class="line">    .<span class="title function_ invoke__">layer</span>(TraceLayer::<span class="title function_ invoke__">new_for_http</span>().<span class="title function_ invoke__">make_span_with</span>(make_span))</span><br><span class="line">    .<span class="title function_ invoke__">map_request</span>(accept_trace)</span><br><span class="line">    .<span class="title function_ invoke__">map_request</span>(record_trace_id),</span><br></pre></td></tr></table></figure><p>In order to make this work, we have to reserve an empty <code>&quot;trace_id&quot;</code> field on each span, though. Hence we update our <code>make_span</code> functions:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">make_span</span>(request: &amp;Request&lt;Body&gt;) <span class="punctuation">-&gt;</span> Span &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">headers</span> = request.<span class="title function_ invoke__">headers</span>();</span><br><span class="line">    info_span!(<span class="string">&quot;incoming request&quot;</span>, ?headers, trace_id = field::Empty)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Finally we have to make sure that log events are written including the stack of spans within which they occurred. Actually that is already the default for the JSON formatter layer we are using; this behavior could be changed via <code>fmt::layer().json().with_span_list(...)</code>.</p><p>As a result of all this, log events are written like this:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2023-09-30T06:47:03.072244Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;DEBUG&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;received response from rpc Hello&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;msg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Hello, I&#x27;m built with Rust, Axum and tonic!&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;endpoint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;http://localhost:8090&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hello_tracing_gateway::backend&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;span&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hello-backend-client&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;spans&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;headers&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;\&quot;host\&quot;: \&quot;localhost:8080\&quot;, \&quot;accept-encoding\&quot;: \&quot;gzip, deflate\&quot;, \&quot;accept\&quot;: \&quot;*/*\&quot;, \&quot;connection\&quot;: \&quot;keep-alive\&quot;, \&quot;user-agent\&quot;: \&quot;HTTPie/3.2.2\&quot;&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;trace_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;a13cc1052ac7f4c3e07a4ef6022c27b1&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;incoming request&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hello-handler&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hello-backend-client&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Notice the <code>trace_id</code> field on the topmost span. This is already very helpful in “manually” correlating log events across the call stack and across different services. But, depending on the capabilities of the telemetry backend of choice, there should be a deep integration between logs and traces such that we can navigate from one to another.</p><p>Grafana Cloud – using Loki for logs and Tempo for traces – can do this out of the box without any further processing of the log or trace events:</p><p><img src="/img/hello-tracing-rs.png"></p><p>This picture – the same like in the first episode – shows a structured log event on the left and an associated trace on the right. Notice the blue button at the bottom of the left half, right of the trace ID, which is a link pointing to the traces. Similar links taking us to the corresponding logs exist for the visualization of traces if we drill down into a particular span to show its details.</p><p>We finally have arrived at the end of this series. Thank you for staying around until here. Let me know if you have any questions. And of course the example code can be found at <a href="https://github.com/hseeberger/hello-tracing-rs/">hello-tracing-rs on GitHub</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2023-09-30-dist-tracing-4/</id>
    <link href="https://heikoseeberger.de/2023-09-30-dist-tracing-4/"/>
    <published>2023-09-29T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>In the previous episodes we coverd <a href="https://heikoseeberger.de/2023-07-29-dist-tracing-1/">logging basics</a>, <a href="https://he]]>
    </summary>
    <title>Distributed Tracing in Rust, Episode 4: correlating logs and traces</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="tracing" scheme="https://heikoseeberger.de/tags/tracing/"/>
    <category term="opentelemetry" scheme="https://heikoseeberger.de/tags/opentelemetry/"/>
    <content>
      <![CDATA[<p>In the <a href="https://heikoseeberger.de/2023-08-18-dist-tracing-2/">previous episode</a> we were looking at the basics of tracing with the Tokio tracing framework and showed how to create traces inside a single service. Now let’s see how we can deal with inter-service communication.</p><p>In our example we have two services: when the <code>hello-tracing-gateway</code> service receives a HTTP request, it calls the <code>hello-tracing-backend</code> service via gRPC and then responds via HTTP.</p><p>Each service uses a <code>Subscriber</code> emitting spans – we will look at the respective configuration later. Obviously we somehow have to propagate and connect the spans – this is known as <a href="https://opentelemetry.io/docs/concepts/signals/traces/#context-propagation">trace context propagation</a> – and the way we want to do it is via the OpenTelemetry standard.</p><p>Luckily there are a couple of helpful Rust crates for this purpose:</p><ul><li><a href="https://crates.io/crates/opentelemetry"><code>opentelemetry</code></a>: the standard in Rust</li><li><a href="https://crates.io/crates/tracing-opentelemetry"><code>tracing-opentelemetry</code></a>: connect spans from multiple subscribers to an OpenTelemetry trace</li><li><a href="https://crates.io/crates/opentelemetry-http"><code>opentelemetry-http</code></a>: trace context propagation via HTTP</li></ul><p>For trace context propagation we need two sides: the sender and the receiver of the trace context.</p><h2 id="Receiving-the-Trace-Context"><a href="#Receiving-the-Trace-Context" class="headerlink" title="Receiving the Trace Context"></a>Receiving the Trace Context</h2><p>Let’s first look at the receiver, i.e. the <code>hello-tracing-backend</code> service. As the communication happens via gPRC which uses HTTP&#x2F;2 as transport protocol, we can make use of <code>opentelemetry-http</code> and its <code>HeaderExtractor</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// Trace context propagation: associate the current span with the OTel trace of the given request,</span></span><br><span class="line"><span class="comment">/// if any and valid.</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">accept_trace</span>(request: Request&lt;Body&gt;) <span class="punctuation">-&gt;</span> Request&lt;Body&gt; &#123;</span><br><span class="line">    <span class="comment">// Current context, if no or invalid data is received.</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">parent_context</span> = global::<span class="title function_ invoke__">get_text_map_propagator</span>(|propagator| &#123;</span><br><span class="line">        propagator.<span class="title function_ invoke__">extract</span>(&amp;<span class="title function_ invoke__">HeaderExtractor</span>(request.<span class="title function_ invoke__">headers</span>()))</span><br><span class="line">    &#125;);</span><br><span class="line">    Span::<span class="title function_ invoke__">current</span>().<span class="title function_ invoke__">set_parent</span>(parent_context);</span><br><span class="line"></span><br><span class="line">    request</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This function taking a <code>Request</code> and returning it unchanged, uses the request headers to create a <code>HeaderExtractor</code> which again is used in a call to <code>get_text_map_propagator</code> from the <code>opentelemetry</code> API. <code>propagator.extract</code> then returns an <code>opentelemetry::Context</code> which we use to set as the parent of the current <code>Span</code> from <code>tracing</code>. So if the headers contain valid OpenTelemetry trace context values, these are used to associate the current span with the overall OpenTelemetry trace.</p><p>We use this function in our <a href="https://crates.io/crates/tonic">tonic</a> based gRPC server in <code>hello-tracing-backend</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">async</span> <span class="keyword">fn</span> <span class="title function_">serve</span>(config: Config) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;()&gt; &#123;</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">app</span> = Server::<span class="title function_ invoke__">builder</span>()</span><br><span class="line">        .<span class="title function_ invoke__">layer</span>(</span><br><span class="line">            ServiceBuilder::<span class="title function_ invoke__">new</span>()</span><br><span class="line">                .<span class="title function_ invoke__">layer</span>(TraceLayer::<span class="title function_ invoke__">new_for_grpc</span>().<span class="title function_ invoke__">make_span_with</span>(make_span))</span><br><span class="line">                .<span class="title function_ invoke__">map_request</span>(accept_trace),</span><br><span class="line">        )</span><br><span class="line">        .<span class="title function_ invoke__">add_service</span>(v0::<span class="title function_ invoke__">hello</span>());</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Here we use <code>tower::ServiceBuilder</code> to add a layer before the actual gRPC service <code>v0::hello</code>. That layer not only adds – like explained in the <a href="https://heikoseeberger.de/2023-08-18-dist-tracing-2/">previous episode</a> – tracing to requests via the <code>TraceLayer</code>, but also uses the above <code>accept_trace</code> function in the <code>map_request</code> call to propagate the trace context.</p><h2 id="Sending-the-Trace-Context"><a href="#Sending-the-Trace-Context" class="headerlink" title="Sending the Trace Context"></a>Sending the Trace Context</h2><p>All right, now let’s look at the sender, i.e. the <code>hello-tracing-gateway</code>. This time we have to stick to the tonic API – namely an <code>Interceptor</code> – to send the trace context. Therefore we cannot use <code>opentelemetry-http</code> and its <code>HeaderInjector</code>, but we have to roll our own:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">MetadataInjector</span>&lt;<span class="symbol">&#x27;a</span>&gt;(&amp;<span class="symbol">&#x27;a</span> <span class="keyword">mut</span> MetadataMap);</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">Injector</span> <span class="keyword">for</span> <span class="title class_">MetadataInjector</span>&lt;<span class="symbol">&#x27;_</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">set</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, key: &amp;<span class="type">str</span>, value: <span class="type">String</span>) &#123;</span><br><span class="line">        <span class="keyword">match</span> MetadataKey::<span class="title function_ invoke__">from_bytes</span>(key.<span class="title function_ invoke__">as_bytes</span>()) &#123;</span><br><span class="line">            <span class="title function_ invoke__">Ok</span>(key) =&gt; <span class="keyword">match</span> MetadataValue::<span class="title function_ invoke__">try_from</span>(&amp;value) &#123;</span><br><span class="line">                <span class="title function_ invoke__">Ok</span>(value) =&gt; &#123;</span><br><span class="line">                    <span class="keyword">self</span>.<span class="number">0</span>.<span class="title function_ invoke__">insert</span>(key, value);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="title function_ invoke__">Err</span>(error) =&gt; warn!(value, error = <span class="built_in">format!</span>(<span class="string">&quot;&#123;error:#&#125;&quot;</span>), <span class="string">&quot;parse metadata value&quot;</span>),</span><br><span class="line">            &#125;,</span><br><span class="line"></span><br><span class="line">            <span class="title function_ invoke__">Err</span>(error) =&gt; warn!(key, error = <span class="built_in">format!</span>(<span class="string">&quot;&#123;error:#&#125;&quot;</span>), <span class="string">&quot;parse metadata key&quot;</span>),</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>With that <code>Injector</code> implementation we can define a function taking a <code>Request</code> and returning it unchanged and wrapped in an <code>Ok</code>. Again we use <code>get_text_map_propagator</code> from the <code>opentelemetry</code> API, this time to call <code>propagator.inject_context</code> with an OpenTelemetry trace context built from the current <code>Span</code> from <code>tracing</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// Trace context propagation: send the trace context by injecting it into the metadata of the given</span></span><br><span class="line"><span class="comment">/// request.</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">send_trace</span>&lt;T&gt;(<span class="keyword">mut</span> request: Request&lt;T&gt;) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;Request&lt;T&gt;, Status&gt; &#123;</span><br><span class="line">    global::<span class="title function_ invoke__">get_text_map_propagator</span>(|propagator| &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">context</span> = Span::<span class="title function_ invoke__">current</span>().<span class="title function_ invoke__">context</span>();</span><br><span class="line">        propagator.<span class="title function_ invoke__">inject_context</span>(&amp;context, &amp;<span class="keyword">mut</span> <span class="title function_ invoke__">MetadataInjector</span>(request.<span class="title function_ invoke__">metadata_mut</span>()))</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="title function_ invoke__">Ok</span>(request)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This <code>send_trace</code> is then used as tonic interceptor, such that for outgoing service calls the trace context is propagated:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">endpoint</span> = Endpoint::<span class="title function_ invoke__">from_str</span>(&amp;<span class="keyword">self</span>.config.endpoint)</span><br><span class="line">    .<span class="title function_ invoke__">with_context</span>(|| <span class="built_in">format!</span>(<span class="string">&quot;create endpoint &#123;&#125;&quot;</span>, <span class="keyword">self</span>.config.endpoint))?;</span><br><span class="line"><span class="keyword">let</span> <span class="variable">channel</span> = endpoint</span><br><span class="line">    .<span class="title function_ invoke__">connect</span>()</span><br><span class="line">    .<span class="keyword">await</span></span><br><span class="line">    .<span class="title function_ invoke__">with_context</span>(|| <span class="built_in">format!</span>(<span class="string">&quot;connect to endpoint &#123;&#125;&quot;</span>, <span class="keyword">self</span>.config.endpoint))?;</span><br><span class="line"><span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">client</span> = HelloClient::<span class="title function_ invoke__">with_interceptor</span>(channel, send_trace);</span><br></pre></td></tr></table></figure><h2 id="Configuring-the-Subscriber"><a href="#Configuring-the-Subscriber" class="headerlink" title="Configuring the Subscriber"></a>Configuring the <code>Subscriber</code></h2><p>In the <a href="https://heikoseeberger.de/2023-07-29-dist-tracing-1/">first episode</a> we introdued the tracing_subscriber crate and used it to set up a formatter layer logging JSON formatted representations of tracing events. Let’s now extend the setup and add a layer exporting tracing data via the <a href="https://opentelemetry.io/docs/specs/otlp/">OpenTelemetry Protocol (OTLP)</a> to a configurable backend which then can be used to visualize traces:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// Create an OTLP layer exporting tracing data.</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">otlp_layer</span>&lt;S&gt;(config: TracingConfig) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;<span class="keyword">impl</span> <span class="title class_">Layer</span>&lt;S&gt;&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    S: Subscriber + <span class="keyword">for</span>&lt;<span class="symbol">&#x27;span</span>&gt; LookupSpan&lt;<span class="symbol">&#x27;span</span>&gt;,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">exporter</span> = opentelemetry_otlp::<span class="title function_ invoke__">new_exporter</span>()</span><br><span class="line">        .<span class="title function_ invoke__">tonic</span>()</span><br><span class="line">        .<span class="title function_ invoke__">with_endpoint</span>(config.otlp_exporter_endpoint);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">trace_config</span> = trace::<span class="title function_ invoke__">config</span>().<span class="title function_ invoke__">with_resource</span>(Resource::<span class="title function_ invoke__">new</span>(<span class="built_in">vec!</span>[KeyValue::<span class="title function_ invoke__">new</span>(</span><br><span class="line">        <span class="string">&quot;service.name&quot;</span>,</span><br><span class="line">        config.service_name,</span><br><span class="line">    )]));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">tracer</span> = opentelemetry_otlp::<span class="title function_ invoke__">new_pipeline</span>()</span><br><span class="line">        .<span class="title function_ invoke__">tracing</span>()</span><br><span class="line">        .<span class="title function_ invoke__">with_exporter</span>(exporter)</span><br><span class="line">        .<span class="title function_ invoke__">with_trace_config</span>(trace_config)</span><br><span class="line">        .<span class="title function_ invoke__">install_batch</span>(runtime::Tokio)</span><br><span class="line">        .<span class="title function_ invoke__">context</span>(<span class="string">&quot;install tracer&quot;</span>)?;</span><br><span class="line"></span><br><span class="line">    <span class="title function_ invoke__">Ok</span>(tracing_opentelemetry::<span class="title function_ invoke__">layer</span>().<span class="title function_ invoke__">with_tracer</span>(tracer))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Here we are using the <a href="https://crates.io/crates/opentelemetry-otlp"><code>opentelemetry_otlp</code> crate</a> to create an exporter based on tonic&#x2F;gRPC with configurable endpoint and service name. With this exporter we create a <code>Tracer</code> from the <code>opentelemetry</code> API which is then used to create the layer.</p><p>All that is left to do is add this OTLP layer to the tracing_subscriber:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">tracing_subscriber::<span class="title function_ invoke__">registry</span>()</span><br><span class="line">    .<span class="title function_ invoke__">with</span>(EnvFilter::<span class="title function_ invoke__">from_default_env</span>())</span><br><span class="line">    .<span class="title function_ invoke__">with</span>(fmt::<span class="title function_ invoke__">layer</span>().<span class="title function_ invoke__">json</span>())</span><br><span class="line">    .<span class="title function_ invoke__">with</span>(<span class="title function_ invoke__">otlp_layer</span>(config)?)</span><br><span class="line">    .<span class="title function_ invoke__">try_init</span>()</span><br><span class="line">    .<span class="title function_ invoke__">context</span>(<span class="string">&quot;initialize tracing subscriber&quot;</span>)</span><br></pre></td></tr></table></figure><p>With this setup and a properly configured tracing backend – we use Grafana Tempo – we can visualize traces like already shown in the <a href="https://heikoseeberger.de/2023-08-18-dist-tracing-2/">previous episode</a>:</p><p><img src="/img/hello-tracing-rs-2.png"></p><p>Now we are almost there. What’s left to do is correlate logs and traces, but we’ll leave that to the next episode. As usual, the already fully fleshed out example code can be found at <a href="https://github.com/hseeberger/hello-tracing-rs/">hello-tracing-rs on GitHub</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2023-08-28-dist-tracing-3/</id>
    <link href="https://heikoseeberger.de/2023-08-28-dist-tracing-3/"/>
    <published>2023-08-27T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>In the <a href="https://heikoseeberger.de/2023-08-18-dist-tracing-2/">previous episode</a> we were looking at the basics of tracing with]]>
    </summary>
    <title>Distributed Tracing in Rust, Episode 3: inter-service tracing</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="tracing" scheme="https://heikoseeberger.de/tags/tracing/"/>
    <category term="opentelemetry" scheme="https://heikoseeberger.de/tags/opentelemetry/"/>
    <content>
      <![CDATA[<p>In the <a href="https://heikoseeberger.de/2023-07-29-dist-tracing-1/">previous episode</a> we were looking at the basics of logging with the Tokio tracing framework; now we are covering tracing.</p><p>As a quick recap, a trace represents a flow of execution through a system in some period of time. It is made up of at least a root span covering the complete flow and potentially one or more sequential sub spans which themselves can contain sub spans. More details and a formal specification can be found at <a href="https://opentelemetry.io/docs/concepts/observability-primer/">OpenTelemetry</a>.</p><p>The following picture shows a simple example of a trace visualized with Grafana Cloud involving two services:</p><p><img src="/img/hello-tracing-rs-2.png"></p><p>To understand this trace, we have to understand how to propagate trace information – the so called trace context – across services. But before that, let’s see how we can create spans in code, for which the <a href="https://crates.io/crates/tracing"><code>tracing</code></a> crate provides two ways: programatically and via the <code>instrument</code> attribute. The latter is easy to use and convenient to add spans to functions:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[instrument(name = <span class="string">&quot;hello-handler&quot;</span>, skip(app_state))]</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">fn</span> <span class="title function_">hello</span>(<span class="title function_ invoke__">State</span>(app_state): State&lt;AppState&gt;) <span class="punctuation">-&gt;</span> <span class="keyword">impl</span> <span class="title class_">IntoResponse</span> &#123;</span><br><span class="line">    app_state.backend.<span class="title function_ invoke__">hello</span>().<span class="keyword">await</span>.<span class="title function_ invoke__">map_err</span>(internal_error)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Here a span with <code>INFO</code> level (default unless overridden) named <code>hello-handler</code> (default is function name) is entered when the <code>hello</code> function is called and exited when it returns. By default all function arguments are recorded as span fields, but with <code>skip</code> we ignore <code>app_state</code>.</p><p>Sometimes it is necessary to create spans programatically. Our services use the <a href="https://crates.io/crates/tower-http"><code>tower-http</code></a> crate to add a <code>TraceLayer</code> to our <a href="https://crates.io/crates/axum"><code>axum</code></a> based web server. That <code>TraceLayer</code> creates a span for every received request which we customize the following way:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">TraceLayer::<span class="title function_ invoke__">new_for_http</span>().<span class="title function_ invoke__">make_span_with</span>(make_span)</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">make_span</span>(request: &amp;Request&lt;Body&gt;) <span class="punctuation">-&gt;</span> Span &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">headers</span> = request.<span class="title function_ invoke__">headers</span>();</span><br><span class="line">    info_span!(<span class="string">&quot;incoming request&quot;</span>, ?headers)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We use the <code>info_span!</code> macro to create a span with <code>INFO</code> level, we name it <code>&quot;incoming request&quot;</code> and we add the headers – its <code>Debug</code> implementation via <code>?</code> – as field.</p><p>Now we know how to create traces inside a single service. But what about inter-service communication? And how can we visualize traces? This will be covered in the next episodes. The already fully fleshed out example code can be found at <a href="https://github.com/hseeberger/hello-tracing-rs/">hello-tracing-rs on GitHub</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2023-08-18-dist-tracing-2/</id>
    <link href="https://heikoseeberger.de/2023-08-18-dist-tracing-2/"/>
    <published>2023-08-17T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>In the <a href="https://heikoseeberger.de/2023-07-29-dist-tracing-1/">previous episode</a> we were looking at the basics of logging with]]>
    </summary>
    <title>Distributed Tracing in Rust, Episode 2: tracing basics</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="tracing" scheme="https://heikoseeberger.de/tags/tracing/"/>
    <category term="opentelemetry" scheme="https://heikoseeberger.de/tags/opentelemetry/"/>
    <content>
      <![CDATA[<p>In order to gain insights into the inner workings of a system – put another way: to have <a href="https://opentelemetry.io/docs/concepts/observability-primer/#what-is-observability">obervability</a> – we can build upon the three pillars of observability: logs, traces and metrics.</p><p>In this series we will explore how we can implement the first two – i.e. logs and traces – in <a href="https://www.rust-lang.org/">Rust</a> with the <a href="https://github.com/tokio-rs/tracing">Tokio tracing framework</a> and <a href="https://opentelemetry.io/">OpenTelemetry</a>.</p><p>Let’s start with some <del>boring</del> basic terminology:</p><ul><li>A log event – aka log message – signifies something that has happened at some specific moment in time.</li><li>A span records a – likely partial – flow of execution through a system and hence represents a period of time. It also functions as context for log events as well as parent for sub spans.</li><li>A trace represents a complete flow of execution through a system from end to end, e.g. from receiving a request to sending a response. Hence it is also a span without parent spans, i.e. a root span.</li></ul><p>Log events give us valuable insights about <strong>what</strong> has happened, e.g. not being able to call a downstream service in a multi-service architecture. But without traces it often is hard to understand the context, i.e. the <strong>why</strong>, e.g. how the request looked like or what the various arguments in the call chain leading to the failing call were. Therefore it is very important to use both logs and traces and to make sure they work seamlessly together.</p><p>The following picture shows a screenshot from Grafana Cloud with a structured log event on the left and an associated trace on the right:</p><p><img src="/img/hello-tracing-rs.png"></p><p>In this episode we are looking at the basics of logs with the Tokio tracing framework. The next episodes will cover traces as well as correlating logs and traces.</p><p>To create a log event in code, the <a href="https://crates.io/crates/tracing"><code>tracing</code> crate</a> provides macros for the respective levels, e.g. <code>debug!</code> or <code>error!</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">debug!(?config, <span class="string">&quot;starting&quot;</span>);</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">let</span> <span class="variable">Err</span>(error) = &amp;result &#123;</span><br><span class="line">    error!(</span><br><span class="line">        error = <span class="built_in">format!</span>(<span class="string">&quot;&#123;error:#&#125;&quot;</span>),</span><br><span class="line">        backtrace = %error.<span class="title function_ invoke__">backtrace</span>(),</span><br><span class="line">        <span class="string">&quot;hello-tracing-gateway exited with ERROR&quot;</span></span><br><span class="line">    );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Beside the level and message we can add arbitrary fields and such get to rich and strucutred log events. To add a field we can use <code>name = value</code> or just <code>name</code>, if <code>name</code> is in scope. The supported types are most basic ones, e.g. <code>i32</code> and <code>String</code>; by using the <code>?</code> prefix, a type’s <code>Debug</code> implementation is used and <code>%</code> makes use of a type’s <code>Display</code> implementation.</p><p>In order to emit log events created in code like above, we use the <a href="https://crates.io/crates/tracing-subscriber"><code>tracing-subsciber</code> crate</a>. It’s <code>fmt</code> module offers various formatters out of which the JSON one is great for structured logging. To use it, we have to initialize the <code>tracing-subsciber</code> accordingly:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">tracing_subscriber::<span class="title function_ invoke__">registry</span>()</span><br><span class="line">    .<span class="title function_ invoke__">with</span>(EnvFilter::<span class="title function_ invoke__">from_default_env</span>())</span><br><span class="line">    .<span class="title function_ invoke__">with</span>(fmt::<span class="title function_ invoke__">layer</span>().<span class="title function_ invoke__">json</span>())</span><br><span class="line">    .<span class="title function_ invoke__">try_init</span>()</span><br><span class="line">    .<span class="title function_ invoke__">context</span>(<span class="string">&quot;initialize tracing subscriber&quot;</span>)</span><br></pre></td></tr></table></figure><p><code>tracing-subsciber</code> implements a pretty sophisticated layer architecture, which we will use later in greater depth, but for now the above only applies a filter layer which is initialized from environment variables – <code>RUST_LOG</code> can be used to set per module&#x2F;target levels – as well a formatter layer that logs JSON formatted representations of tracing events.</p><p>Here is an example of a log event created by the <code>tracing-subsciber</code> initialized like above, assuming <code>RUST_LOG</code> is either set globally to <code>debug</code> or at least for the given target <code>hello_tracing_gateway</code>:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2023-07-29T13:47:08.651846Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;DEBUG&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;starting&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;config&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Config &#123; api: Config &#123; addr: 0.0.0.0, port: 8080 &#125;, backend: Config &#123; endpoint: \&quot;http://localhost:8090\&quot; &#125;, tracing: TracingConfig &#123; service_name: \&quot;hello-tracing-gateway\&quot;, otlp_exporter_endpoint: \&quot;http://localhost:4317\&quot; &#125; &#125;&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hello_tracing_gateway&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>All right, we have arrived at the end of this episode. In the next one we will look at traces. If you are interested in – the already fully fleshed out – example code, take a look at <a href="https://github.com/hseeberger/hello-tracing-rs/">hello-tracing-rs on GitHub</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2023-07-29-dist-tracing-1/</id>
    <link href="https://heikoseeberger.de/2023-07-29-dist-tracing-1/"/>
    <published>2023-07-28T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>In order to gain insights into the inner workings of a system – put another way: to have <a href="https://opentelemetry.io/docs/concepts/]]>
    </summary>
    <title>Distributed Tracing in Rust, Episode 1: logging basics</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="tokio" scheme="https://heikoseeberger.de/tags/tokio/"/>
    <category term="tower" scheme="https://heikoseeberger.de/tags/tower/"/>
    <content>
      <![CDATA[<p>In the <a href="/2022/10/22/2022-10-23-tower-2">previous episode</a> we have seen how to call a Tower service. In particular we have discussed the contract between <code>poll_ready</code> and <code>call</code>: callers must first invoke <code>poll_ready</code> until it returns <code>Poll::Ready</code> before they invoke <code>call</code>, else <code>call</code> might panic.</p><p>Of course some services, e.g. the <code>EchoService</code> described in the <a href="/2022/10/20/2022-10-21-tower-1">first episode</a>, are always ready and therefore invokig <code>call</code> without <code>poll_ready</code> works fine.</p><p>But generally the internal state or external dependencies, e.g. database connections, can infuence the readiness of a service. Also, composing domain services with middleware, e.g. with a rate limit or a circuit breaker, naturally effect the readiness.</p><p>To demonstrate this, let’s take a look at a service which alternates between ready and not ready:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// AlternatingReady service, responding to a [AlternatingReadyRequest] with an</span></span><br><span class="line"><span class="comment">/// [AlternatingReadyResponse].</span></span><br><span class="line"><span class="meta">#[derive(Debug, Clone)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">AlternatingReadyService</span> &#123;</span><br><span class="line">    ready: <span class="type">bool</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// Tower `Service` implementation for [AlternatingReadyService]: readiness alternates between</span></span><br><span class="line"><span class="comment">/// pending and ready, calls never fail and responses are ready immediately.</span></span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">Service</span>&lt;AlternatingReadyRequest&gt; <span class="keyword">for</span> <span class="title class_">AlternatingReadyService</span> &#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Response</span> = AlternatingReadyResponse;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// This service never fails.</span></span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span> = Infallible;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// Responses of this service are ready immediately.</span></span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Future</span> = Ready&lt;<span class="type">Result</span>&lt;<span class="keyword">Self</span>::Response, <span class="keyword">Self</span>::Error&gt;&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// Alternately return `Poll::Pending` and `Poll::Ready`.</span></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">poll_ready</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, cx: &amp;<span class="keyword">mut</span> Context&lt;<span class="symbol">&#x27;_</span>&gt;) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt;&gt; &#123;</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">self</span>.ready &#123;</span><br><span class="line">            <span class="comment">// Wake up the task to make sure the executor calls `poll_ready` again.</span></span><br><span class="line">            cx.<span class="title function_ invoke__">waker</span>().<span class="title function_ invoke__">wake_by_ref</span>();</span><br><span class="line"></span><br><span class="line">            <span class="keyword">self</span>.ready = <span class="literal">false</span>;</span><br><span class="line">            Poll::Pending</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">self</span>.ready = <span class="literal">true</span>;</span><br><span class="line">            Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(()))</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// Always return [AlternatingReadyResponse].</span></span><br><span class="line">    <span class="comment">///</span></span><br><span class="line">    <span class="comment">/// # Panics</span></span><br><span class="line">    <span class="comment">/// Panics if called without prior calling `poll_ready`.</span></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">call</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, _req: AlternatingReadyRequest) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span>::Future &#123;</span><br><span class="line">        <span class="keyword">if</span> !<span class="keyword">self</span>.ready &#123;</span><br><span class="line">            <span class="built_in">panic!</span>(<span class="string">&quot;service not ready; poll_ready must be called first&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">self</span>.ready = <span class="literal">false</span>;</span><br><span class="line">        <span class="title function_ invoke__">ready</span>(<span class="title function_ invoke__">Ok</span>(AlternatingReadyResponse))</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The <code>AlternatingReadyService</code> tracks its readiness via the <code>ready</code> field. <code>poll_ready</code> is implemented such that, if the service has been ready before, it sets it as not ready and returns <code>Poll::Pending</code>; and vice versa. Also, <code>call</code> takes the <code>ready</code> field into account such that invoking <code>call</code> results in panicking if the service is not ready.</p><p>Of course this implementation is highly constructed, but it serves well to demonstrate the importance of the contract between <code>poll_ready</code> and <code>call</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">service</span> = AlternatingReadyService::<span class="title function_ invoke__">new</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="variable">service</span> = service.<span class="title function_ invoke__">ready</span>().<span class="keyword">await</span>?;</span><br><span class="line"><span class="keyword">let</span> <span class="variable">_response</span> = service.<span class="title function_ invoke__">call</span>(AlternatingReadyRequest).<span class="keyword">await</span>?;</span><br><span class="line"></span><br><span class="line"><span class="comment">// We should invoke `ready` once again before invoking `call`!</span></span><br><span class="line"><span class="comment">// service.ready().await?;</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">_response</span> = service.<span class="title function_ invoke__">call</span>(AlternatingReadyRequest).<span class="keyword">await</span>?;</span><br></pre></td></tr></table></figure><p>In this example the second <code>call</code> fails with a panic, because the first one, or rather the <code>ready</code> invocation, leaves the service as not ready. If you want to check this out yourself, you can find the full code at <a href="https://github.com/hseeberger/tower-experiments">tower-experiments on GitHub</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2022/11/07/2022-11-08-tower-3/</id>
    <link href="https://heikoseeberger.de/2022/11/07/2022-11-08-tower-3/"/>
    <published>2022-11-07T22:00:00.000Z</published>
    <summary>
      <![CDATA[<p>In the <a href="/2022/10/22/2022-10-23-tower-2">previous episode</a> we have seen how to call a Tower service. In particular we have disc]]>
    </summary>
    <title>Tower, Episode 3: Readiness</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="tokio" scheme="https://heikoseeberger.de/tags/tokio/"/>
    <category term="tower" scheme="https://heikoseeberger.de/tags/tower/"/>
    <content>
      <![CDATA[<p>In the <a href="/2022/10/20/2022-10-21-tower-1">previous episode</a> we have seen that a Tower service is made up of the two methods <code>poll_ready</code> and <code>call</code>, which are subject to the following contract: callers first must invoke <code>poll_ready</code> until it returns <code>Poll::Ready</code> before they can invoke <code>call</code>, else <code>call</code> might panic.</p><p>This is the signature of <code>poll_ready</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">poll_ready</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, cx: &amp;<span class="keyword">mut</span> Context&lt;<span class="symbol">&#x27;_</span>&gt;) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt;&gt;;</span><br></pre></td></tr></table></figure><p>In order to invoke <code>poll_ready</code>, we need a <code>std::task::Context</code>. If you are familiar with <code>Future</code>s, you will notice that its <code>poll</code> method also has such an argument. So do we have to implement a <code>Future</code> just to call a service?</p><p>Luckily not, because Tower already provides the <code>ServiceExt</code> trait with some helper methods to either invoke <code>poll_ready</code> or even chain invoking <code>poll_ready</code> and <code>call</code> according to their contracts.</p><p>Let’s first take a look at <code>ServiceExt::oneshot</code>. According to the <a href="https://docs.rs/tower/latest/tower/trait.ServiceExt.html#method.oneshot">documentation</a>, it “consumes this service, calling with the provided request once it is ready”.</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">service</span> = EchoService;</span><br><span class="line"><span class="keyword">let</span> <span class="variable">response</span> = service.<span class="title function_ invoke__">oneshot</span>(<span class="string">&quot;Hello, Tower!&quot;</span>.<span class="title function_ invoke__">into</span>()).<span class="keyword">await</span>?;</span><br><span class="line"><span class="built_in">println!</span>(<span class="string">&quot;Echo service responded with: &#123;response&#125;&quot;</span>);</span><br></pre></td></tr></table></figure><p>The <code>Future</code> returned by <code>oneshot</code> first invokes <code>poll_ready</code> when <code>poll</code>ed and then, if the service returns <code>Poll::Ready</code>, invokes <code>call</code>. Of course it’s not possible to invoke the service again, because <code>oneshot</code> consumes it by taking it as <code>self</code>. But it actually is the only way provided by Tower enforcing the contract between <code>poll_ready</code> and <code>call</code>.</p><p>Next let’s take a look at <code>ServiceExt::ready</code>, which, according to the <a href="https://docs.rs/tower/latest/tower/trait.ServiceExt.html#method.ready">documentation</a>, “yields a mutable reference to the service when it is ready to accept a request”.</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">service</span> = EchoService;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="variable">service</span> = service.<span class="title function_ invoke__">ready</span>().<span class="keyword">await</span>?;</span><br><span class="line"><span class="keyword">let</span> <span class="variable">response</span> = service.<span class="title function_ invoke__">call</span>(<span class="string">&quot;Hello, Tower!&quot;</span>.<span class="title function_ invoke__">into</span>()).<span class="keyword">await</span>?;</span><br><span class="line"><span class="built_in">println!</span>(<span class="string">&quot;Echo service responded with: &#123;response&#125;&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// We should invoke `ready` once again before invoking `call`!</span></span><br><span class="line"><span class="comment">// service.ready().await?;</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">response</span> = service.<span class="title function_ invoke__">call</span>(<span class="string">&quot;Hello again, Tower!&quot;</span>.<span class="title function_ invoke__">into</span>()).<span class="keyword">await</span>?;</span><br><span class="line"><span class="built_in">println!</span>(<span class="string">&quot;Echo service responded with: &#123;response&#125;&quot;</span>);</span><br></pre></td></tr></table></figure><p>The <code>Future</code> returned by <code>ready</code> returns a mutable reference to the service which then can be used to <code>call</code> it. As you can see from the above, we can even <code>call</code> the service several times without checking its readiness. Of course this is against the contract and we should instead invoke <code>ready</code> once again every time before we invoke <code>call</code>, i.e. in the above code snippet we should uncomment line 8.</p><p>Finally there is <code>ServiceExt::ready_oneshot</code>. It works like <code>ready</code>, but it consumes the service, yet returns it again.</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">service</span> = EchoService;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">service</span> = service.<span class="title function_ invoke__">ready_oneshot</span>().<span class="keyword">await</span>?;</span><br><span class="line"><span class="keyword">let</span> <span class="variable">response</span> = service.<span class="title function_ invoke__">call</span>(<span class="string">&quot;Hello, Tower!&quot;</span>.<span class="title function_ invoke__">into</span>()).<span class="keyword">await</span>?;</span><br><span class="line"><span class="built_in">println!</span>(<span class="string">&quot;Echo service responded with: &#123;response&#125;&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// We should invoke `ready_oneshot` once again before invoking `call`!</span></span><br><span class="line"><span class="comment">// let mut service = service.ready_oneshot().await?;</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">response</span> = service.<span class="title function_ invoke__">call</span>(<span class="string">&quot;Hello again, Tower!&quot;</span>.<span class="title function_ invoke__">into</span>()).<span class="keyword">await</span>?;</span><br><span class="line"><span class="built_in">println!</span>(<span class="string">&quot;Echo service responded with: &#123;response&#125;&quot;</span>);</span><br></pre></td></tr></table></figure><p>Like for <code>ready</code>, we can <code>call</code> the returned service several times without checking its readiness. Therefore I consider this method misnamed, because “oneshot” for me means, well, no more than one invocation of <code>call</code>.</p><p>You might now ask whether this service contract of always invoking <code>poll_ready</code> before <code>call</code> really matters that much. Well, not for our harmless <code>EchoService</code> which is always ready and therefore its <code>call</code> method never panics. Let’s look at some other service, behaving less nicely, in the <a href="/2022/11/07/2022-11-08-tower-3">next episode</a>. As usual the full code is available in <a href="https://github.com/hseeberger/tower-experiments">tower-experiments on GitHub</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2022/10/22/2022-10-23-tower-2/</id>
    <link href="https://heikoseeberger.de/2022/10/22/2022-10-23-tower-2/"/>
    <published>2022-10-22T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>In the <a href="/2022/10/20/2022-10-21-tower-1">previous episode</a> we have seen that a Tower service is made up of the two methods <cod]]>
    </summary>
    <title>Tower, Episode 2: calling a Tower Service</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="rust" scheme="https://heikoseeberger.de/tags/rust/"/>
    <category term="tokio" scheme="https://heikoseeberger.de/tags/tokio/"/>
    <category term="tower" scheme="https://heikoseeberger.de/tags/tower/"/>
    <content>
      <![CDATA[<p>When I first came across the <a href="https://docs.rs/tower/latest/tower/index.html">Tower</a> library, which is part of the <a href="https://tokio.rs/">Tokio</a> stack, I noticed its title line:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="title function_ invoke__">fn</span>(Request) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;Response, Error&gt;</span><br></pre></td></tr></table></figure><p>In the <a href="https://docs.rs/tower/latest/tower/index.html">documentation</a> this is explained as follows:</p><blockquote><p>Tower provides a simple core abstraction, the Service trait, which represents an asynchronous function taking a request and returning either a response or an error. This abstraction can be used to model both clients and servers.</p></blockquote><p>This immediately reminded the seasoned Scala developer in me of the <a href="https://monkey.org/~marius/funsrv.pdf">Finagle paper</a> titled “Your Server as a Function”, which defines that “Systems boundaries are represented by asynchronous functions called services”. It is always good to see when new projects make use of already existing knowledge.</p><p>While both Tower and Finagle services share the same core abstraction – the asynchronous function – there are two differences.</p><p>First, Tower does not necessarily interpret services as system boundaries. Instead services can also be composed “locally” which rather matches the Finagle “filters”:</p><blockquote><p>Generic components, like timeouts, rate limiting, and load balancing, can be modeled as Services that wrap some inner service and apply additional behavior before or after the inner service is called. This allows implementing these components in a protocol-agnostic, composable way. Typically, such services are referred to as middleware.</p></blockquote><p>Second, Tower enriches the core abstraction of an asynchronous function with the concept of readiness, as we can see form the definition of the <code>Service</code> trait:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">trait</span> <span class="title class_">Service</span>&lt;Request&gt; &#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Response</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Future</span>: Future&lt;Output = <span class="type">Result</span>&lt;<span class="keyword">Self</span>::Response, <span class="keyword">Self</span>::Error&gt;&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">poll_ready</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, cx: &amp;<span class="keyword">mut</span> Context&lt;<span class="symbol">&#x27;_</span>&gt;) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt;&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">call</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, req: Request) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span>::Future;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>At its core, the contract for callers of these service methods is as follows:</p><ul><li>First <code>poll_ready</code> must be invoked – potentially several times – until it returns <code>Poll::Ready</code>, then <code>call</code> can be invoked.</li><li><code>call</code> might panic if the above is not the case.</li></ul><p>Before going into the details of calling services in the next episode, let’s first take a look at a simple example for a service:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">EchoRequest</span>(<span class="type">String</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">EchoResponse</span>(<span class="type">String</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">EchoService</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// Tower `Service` implementation for [EchoService]: always ready, never fails and responds</span></span><br><span class="line"><span class="comment">/// immediately with an echo, i.e. a response with the same content like the request.</span></span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">Service</span>&lt;EchoRequest&gt; <span class="keyword">for</span> <span class="title class_">EchoService</span> &#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Response</span> = EchoResponse;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// This service never fails.</span></span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span> = Infallible;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// This service responds immediately.</span></span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Future</span> = Ready&lt;<span class="type">Result</span>&lt;<span class="keyword">Self</span>::Response, <span class="keyword">Self</span>::Error&gt;&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// Always return `Poll::Ready`: this service is always ready.</span></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">poll_ready</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, _cx: &amp;<span class="keyword">mut</span> Context&lt;<span class="symbol">&#x27;_</span>&gt;) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt;&gt; &#123;</span><br><span class="line">        Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(()))</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// Always return an echo, i.e. a response with the same content like the request.</span></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">call</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, req: EchoRequest) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span>::Future &#123;</span><br><span class="line">        <span class="title function_ invoke__">ready</span>(<span class="title function_ invoke__">Ok</span>(<span class="title function_ invoke__">EchoResponse</span>(req.<span class="number">0</span>)))</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>As this service is always ready, it is not necessary for <code>call</code> to panic if <code>poll_ready</code> has not been called before.</p><p>The full code is available in <a href="https://github.com/hseeberger/tower-experiments">tower-experiments on GitHub</a>. In the <a href="/2022/10/22/2022-10-23-tower-2">next episode</a> we will take a look at service clients.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2022/10/20/2022-10-21-tower-1/</id>
    <link href="https://heikoseeberger.de/2022/10/20/2022-10-21-tower-1/"/>
    <published>2022-10-20T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>When I first came across the <a href="https://docs.rs/tower/latest/tower/index.html">Tower</a> library, which is part of the <a href="htt]]>
    </summary>
    <title>Tower, Episode 1: your Service as a Function</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="scala" scheme="https://heikoseeberger.de/tags/scala/"/>
    <content>
      <![CDATA[<p>In Scala 2 – like in Java – we have universal equality which means that we can compare two objects – each of any type – for equality:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">scala&gt; <span class="keyword">final</span> <span class="keyword">case</span> <span class="class"><span class="keyword">class</span> <span class="title">Foo</span>(<span class="params"></span>)</span></span><br><span class="line"></span><br><span class="line">scala&gt; <span class="type">Foo</span>() == <span class="type">Foo</span>()</span><br><span class="line"><span class="keyword">val</span> res0: <span class="type">Boolean</span> = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">scala&gt; <span class="type">Foo</span>() == <span class="type">Option</span>(<span class="type">Foo</span>()) <span class="comment">// Pointless!</span></span><br><span class="line"><span class="keyword">val</span> res1: <span class="type">Boolean</span> = <span class="literal">false</span></span><br></pre></td></tr></table></figure><p>As shown above, universal equality can lead to pointless equality comparisons. The reason for this unfortunate behavior is the <code>equals</code> method, which is defined for every class – either inherited from <code>Any</code> or overwritten – and has a parameter of type <code>Any</code>:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">equals</span></span>(other: <span class="type">Any</span>): <span class="type">Boolean</span></span><br></pre></td></tr></table></figure><p>Scala 3 adds the <code>strictEquality</code> language feature which only allows using <code>==</code> and its counterpart <code>!=</code> in the presence of a given <code>Eql</code> instance:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">scala&gt; <span class="keyword">import</span> scala.language.strictEquality</span><br><span class="line"></span><br><span class="line">scala&gt; <span class="type">Foo</span>() == <span class="type">Foo</span>() <span class="comment">// Does not compile yet ...</span></span><br><span class="line"><span class="number">1</span> |<span class="type">Foo</span>() == <span class="type">Foo</span>()</span><br><span class="line">  |^^^^^^^^^^^^^^</span><br><span class="line">  |<span class="type">Values</span> of types <span class="type">Foo</span> and <span class="type">Foo</span> cannot be compared <span class="keyword">with</span> == or !=</span><br><span class="line"></span><br><span class="line">scala&gt; <span class="keyword">given</span> <span class="type">Eql</span>[<span class="type">Foo</span>, <span class="type">Foo</span>] = <span class="type">Eql</span>.derived</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">given_Eql_Foo_Foo</span></span>: <span class="type">Eql</span>[<span class="type">Foo</span>, <span class="type">Foo</span>]</span><br><span class="line"></span><br><span class="line">scala&gt; <span class="type">Foo</span>() == <span class="type">Foo</span>() <span class="comment">// ... now it compiles ;-)</span></span><br><span class="line"><span class="keyword">val</span> res2: <span class="type">Boolean</span> = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">scala&gt; <span class="type">Foo</span>() == <span class="type">Option</span>(<span class="type">Foo</span>()) <span class="comment">// Still does not compile, which is what we want!</span></span><br><span class="line"><span class="number">1</span> |<span class="type">Foo</span>() == <span class="type">Option</span>(<span class="type">Foo</span>())</span><br><span class="line">  |^^^^^^^^^^^^^^^^^^^^^^</span><br><span class="line">  |<span class="type">Values</span> of types <span class="type">Foo</span> and <span class="type">Option</span>[<span class="type">Foo</span>] cannot be compared <span class="keyword">with</span> == or !=</span><br></pre></td></tr></table></figure><p>As we can see above, after activating strict equality – also called multiversal equality – we can no longer compare two <code>Foo</code> instances for equality. After providing a given <code>Eql[Foo, Foo]</code> instance this becomes possible, but we still cannot perform the pointless comparison of <code>Foo()</code> and <code>Option(Foo())</code>, which is exactly what we want in most cases (see below).</p><p>Instead of “manually” providing given <code>Eql[A, A]</code> instances to compare certain types <code>A</code> with themselves for equality, we can simply derive them:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">scala&gt; <span class="keyword">final</span> <span class="keyword">case</span> <span class="class"><span class="keyword">class</span> <span class="title">Bar</span>(<span class="params"></span>) <span class="title">derives</span> <span class="title">Eql</span></span></span><br><span class="line"></span><br><span class="line">scala&gt; <span class="type">Bar</span>() == <span class="type">Bar</span>()</span><br><span class="line"><span class="keyword">val</span> res3: <span class="type">Boolean</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>It is worth noting, that <code>Eql</code> is not used at runtime – the actual equality comparison is still carried out through calling good old <code>equals</code>.</p><p>Also noteworthy, we can still compare objects of different type for equality. All we need to do is provide the necessary given <code>Eql</code> instances – we need two, one for each “direction”:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">scala&gt; <span class="keyword">given</span> <span class="type">Eql</span>[<span class="type">Foo</span>, <span class="type">Bar</span>] = <span class="type">Eql</span>.derived</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">given_Eql_Foo_Bar</span></span>: <span class="type">Eql</span>[<span class="type">Foo</span>, <span class="type">Bar</span>]</span><br><span class="line"></span><br><span class="line">scala&gt; <span class="keyword">given</span> <span class="type">Eql</span>[<span class="type">Bar</span>, <span class="type">Foo</span>] = <span class="type">Eql</span>.derived</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">given_Eql_Bar_Foo</span></span>: <span class="type">Eql</span>[<span class="type">Bar</span>, <span class="type">Foo</span>]</span><br><span class="line"></span><br><span class="line">scala&gt; <span class="type">Foo</span>() == <span class="type">Bar</span>()</span><br><span class="line"><span class="keyword">val</span> res4: <span class="type">Boolean</span> = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">scala&gt; <span class="type">Bar</span>() == <span class="type">Foo</span>()</span><br><span class="line"><span class="keyword">val</span> res5: <span class="type">Boolean</span> = <span class="literal">false</span></span><br></pre></td></tr></table></figure><p>The Scala standard library provides some of these bidirectional <code>Eql</code> instances for numeric types:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">scala&gt; <span class="number">1</span>L == <span class="number">1</span></span><br><span class="line"><span class="keyword">val</span> res6: <span class="type">Boolean</span> = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">scala&gt; <span class="number">1</span> == <span class="number">1.0</span></span><br><span class="line"><span class="keyword">val</span> res7: <span class="type">Boolean</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>If backwards compatibility is needed, we simply not use the <code>strictEquality</code> language feature. This leads to the compiler providing the needed given <code>Eql</code> instance – if necessary – by using the <code>eqlAny</code> method, which itself is not defined as given:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">eqlAny</span></span>[<span class="type">L</span>, <span class="type">R</span>]: <span class="type">Eql</span>[<span class="type">L</span>, <span class="type">R</span>] = <span class="type">Eql</span>.derived</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://heikoseeberger.de/2020-01-07-dotty-4/</id>
    <link href="https://heikoseeberger.de/2020-01-07-dotty-4/"/>
    <published>2020-01-06T22:00:00.000Z</published>
    <summary>
      <![CDATA[<p>In Scala 2 – like in Java – we have universal equality which means that we can compare two objects – each of any type – for equality:</p>]]>
    </summary>
    <title>Learn myself some Scala 3, episode 4: strict equality</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="scala" scheme="https://heikoseeberger.de/tags/scala/"/>
    <content>
      <![CDATA[<p><strong>Update</strong>: Thanks to feedback from <a href="https://twitter.com/kai_nyasha">@kai_nyasha</a> and <a href="https://twitter.com/loic_d">@loic_d</a> I have removed the now unnecessary separate type class syntax stuff.</p><p>Type classes are implemented as a three-part pattern in Scala 2: we use a polymorphic trait with one or more abstract methods for the type class and different applications of the <code>implicit</code> keyword to provide the type class syntax and the type class instances:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The type class is a polymorphic trait</span></span><br><span class="line"><span class="class"><span class="keyword">trait</span> <span class="title">SemiGroup</span>[<span class="type">A</span>] </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">combine</span></span>(a1: <span class="type">A</span>, a2: <span class="type">A</span>): <span class="type">A</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// The type class syntax is provided by extension methods</span></span><br><span class="line"><span class="comment">// with an implicit parameter for the type class</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">implicit</span> <span class="class"><span class="keyword">class</span> <span class="title">SemiGroupSyntax</span>[<span class="type">A</span>](<span class="params">val lhs: <span class="type">A</span></span>) <span class="keyword">extends</span> <span class="title">AnyVal</span> </span>&#123;</span><br><span class="line">  <span class="keyword">final</span> <span class="function"><span class="keyword">def</span> <span class="title">|+|</span> </span>(rhs: <span class="type">A</span>)(<span class="keyword">implicit</span> sga: <span class="type">SemiGroup</span>[<span class="type">A</span>]): <span class="type">A</span> =</span><br><span class="line">    lhs.combine(rhs)</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">combine</span></span>(rhs: <span class="type">A</span>)(<span class="keyword">implicit</span> sga: <span class="type">SemiGroup</span>[<span class="type">A</span>]): <span class="type">A</span> =</span><br><span class="line">    sga.combine(lhs, rhs)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// The type class instance is an implicit value</span></span><br><span class="line"><span class="comment">// implementing the type class</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">implicit</span> <span class="keyword">val</span> intSemiGroup: <span class="type">SemiGroup</span>[<span class="type">Int</span>] =</span><br><span class="line">  _ + _</span><br></pre></td></tr></table></figure><p>In Scala 3 the approach is simplified: all we need are extension methods which we covered in the <a href="/2019/12/04/2019-12-04-dotty-2">previous blog post</a> and the new <em>given instances</em>.</p><h2 id="Type-Classes"><a href="#Type-Classes" class="headerlink" title="Type Classes"></a>Type Classes</h2><p>As in Scala 2 type classes are polymorphic traits with one or more abstract methods. Yet instead of “normal” methods we already provide the syntax via extension methods. Then every type, which has a given instance (see below) of the type class in scope, acquires the extension methods.</p><p>To define a semigroup type class we use the new experimental brace-less syntax, which might or might not make it into Scala 3:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> lmss</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">trait</span> <span class="title">SemiGroup</span>[<span class="type">A</span>]</span>:</span><br><span class="line"></span><br><span class="line">  <span class="keyword">extension</span> (lhs: <span class="type">A</span>)</span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">combine</span></span>(rhs: <span class="type">A</span>): <span class="type">A</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="function"><span class="keyword">def</span> <span class="title">|+|</span></span>(rhs: <span class="type">A</span>): <span class="type">A</span> =</span><br><span class="line">      lhs.combine(rhs)</span><br></pre></td></tr></table></figure><h2 id="Given-Instances"><a href="#Given-Instances" class="headerlink" title="Given Instances"></a>Given Instances</h2><p>To provide type class instances we use given instances, which can be anonymous, implementing the abstract type class methods:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> lmss</span><br><span class="line"><span class="keyword">package</span> instances</span><br><span class="line"><span class="keyword">package</span> semigroup</span><br><span class="line"></span><br><span class="line"><span class="keyword">given</span> <span class="type">SemiGroup</span>[<span class="type">Int</span>]:</span><br><span class="line">  <span class="keyword">extension</span> (lhs: <span class="type">Int</span>)</span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">def</span> <span class="title">combine</span></span>(rhs: <span class="type">Int</span>): <span class="type">Int</span> =</span><br><span class="line">      lhs + rhs</span><br><span class="line"></span><br><span class="line"><span class="keyword">given</span> [<span class="type">A</span>] as <span class="type">SemiGroup</span>[<span class="type">Seq</span>[<span class="type">A</span>]]:</span><br><span class="line">  <span class="keyword">extension</span>  (lhs: <span class="type">Seq</span>[<span class="type">A</span>])</span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">def</span> <span class="title">combine</span></span>(rhs: <span class="type">Seq</span>[<span class="type">A</span>]): <span class="type">Seq</span>[<span class="type">A</span>] =</span><br><span class="line">      lhs ++ rhs</span><br></pre></td></tr></table></figure><h2 id="Given-Imports"><a href="#Given-Imports" class="headerlink" title="Given Imports"></a>Given Imports</h2><p>Unless the respective given instances are already in scope, e.g. locally or via inheritance, they have to be imported. Other than in Scala 2 we cannot use a standard import clause, but instead we have to use the new <em>given imports</em>:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">scala&gt; <span class="keyword">import</span> lmss.instances.semigroup.&#123; <span class="keyword">given</span> _ &#125;</span><br><span class="line"></span><br><span class="line">scala&gt; <span class="number">42</span> |+| <span class="number">7</span></span><br><span class="line"><span class="keyword">val</span> res0: <span class="type">Int</span> = <span class="number">49</span></span><br></pre></td></tr></table></figure><p>The above imports all given instances from the <code>semigroup</code> package. We can also select specific types to be imported:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scala&gt; <span class="keyword">import</span> lmss.instances.semigroup.&#123; <span class="keyword">given</span> lmss.<span class="type">SemiGroup</span>[?] &#125;</span><br></pre></td></tr></table></figure><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Compared to Scala 2 type classes are easier and more straightforward to implement in Scala 3: no more implicit classes for the syntax needed, just the type class as a polymorphic trait with extension methods and type class instances as given instances.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2019-12-10-dotty-3/</id>
    <link href="https://heikoseeberger.de/2019-12-10-dotty-3/"/>
    <published>2019-12-09T22:00:00.000Z</published>
    <summary>
      <![CDATA[<p><strong>Update</strong>: Thanks to feedback from <a href="https://twitter.com/kai_nyasha">@kai_nyasha</a> and <a href="https://twitter.co]]>
    </summary>
    <title>Learn myself some Scala 3, episode 3: type classes</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="scala" scheme="https://heikoseeberger.de/tags/scala/"/>
    <content>
      <![CDATA[<p>Extension methods have always – or at least as long as I have known Scala – been around. Before Scala 2.10 they had to be provided by a rather verbose pattern:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">object</span> <span class="title">IntSyntax</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">IntOps</span>(<span class="params">n: <span class="type">Int</span></span>) </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">reverse</span></span>: <span class="type">Int</span> =</span><br><span class="line">      n.toString.reverse.toInt</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">implicit</span> <span class="function"><span class="keyword">def</span> <span class="title">toIntOps</span></span>(n: <span class="type">Int</span>): <span class="type">IntOps</span> =</span><br><span class="line">    <span class="keyword">new</span> <span class="type">IntOps</span>(n)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> If these two definitions – a wrapper class providing the actual extension method and an implicit conversion from the type to be extended to the wrapper – are in scope, we can call the extension method:</p> <figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">scala&gt; <span class="number">123.</span>reverse</span><br><span class="line">           ^</span><br><span class="line">       error: value reverse is not a member of <span class="type">Int</span></span><br><span class="line"></span><br><span class="line">scala&gt; <span class="keyword">import</span> <span class="type">IntSyntax</span>._</span><br><span class="line"><span class="keyword">import</span> <span class="type">IntSyntax</span>._</span><br><span class="line"></span><br><span class="line">scala&gt; <span class="number">123.</span>reverse</span><br><span class="line">res1: <span class="type">Int</span> = <span class="number">321</span></span><br></pre></td></tr></table></figure><p>As extension methods are a frequently used feature in Scala, the above pattern was simplified in Scala 2.10 by the introduction of implicit classes:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">object</span> <span class="title">IntSyntax</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">final</span> <span class="keyword">implicit</span> <span class="class"><span class="keyword">class</span> <span class="title">IntOps</span>(<span class="params">val n: <span class="type">Int</span></span>) <span class="keyword">extends</span> <span class="title">AnyVal</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">reverse</span></span>: <span class="type">Int</span> =</span><br><span class="line">      n.toString.reverse.toInt</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>So by adding <code>implicit</code> to the definition of the wrapper class we can omit the definition of the implicit conversion which will be added by the compiler for us. Notice that we extend <code>AnyVal</code> just for performance reasons, i.e. in order to avoid allocating the wrapper object. But we still have to write too much boilerplate code and we also conflate or overload the concept of “implicits”.</p><p>Scala 3 completely replaces the <code>implicit</code> keyword and its multiple overloaded applications with a couple of contextual abstractions. One of these are extension methods which are supported via new syntax instead of a pattern:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extension</span> (n: <span class="type">Int</span>)</span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">reverse</span></span>: <span class="type">Int</span> =</span><br><span class="line">    n.toString.reverse.toInt</span><br></pre></td></tr></table></figure><p>Extension methods are defined using the new soft keyword <code>extension</code> and an additional parameter list taking the receiver to be extended. If they are in scope as a simple identifier, e.g. via importing, they can be appied at call site using the dot notation:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">scala&gt; <span class="number">123.</span>reverse</span><br><span class="line"><span class="keyword">val</span> res0: <span class="type">Int</span> = <span class="number">321</span></span><br></pre></td></tr></table></figure><p>Of course extension methods can also be collective – i.e. more than one for a single receiver – and polymorphic:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extension</span> (lhs: <span class="type">A</span>)</span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">combine</span></span>(rhs: <span class="type">A</span>): <span class="type">A</span> =</span><br><span class="line">    ???</span><br><span class="line"></span><br><span class="line">  <span class="keyword">final</span> <span class="function"><span class="keyword">def</span> <span class="title">|+|</span></span>(rhs: <span class="type">A</span>): <span class="type">A</span> =</span><br><span class="line">    lhs.combine(rhs)</span><br></pre></td></tr></table></figure><p>The astute reader probably guesses, that in order to implement these extension methods we need type classes – semi group in this case  – which we will cover in the next episode.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2019-12-04-dotty-2/</id>
    <link href="https://heikoseeberger.de/2019-12-04-dotty-2/"/>
    <published>2019-12-03T22:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Extension methods have always – or at least as long as I have known Scala – been around. Before Scala 2.10 they had to be provided by a r]]>
    </summary>
    <title>Learn myself some Scala 3, episode 2: extension methods</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="scala" scheme="https://heikoseeberger.de/tags/scala/"/>
    <content>
      <![CDATA[<p>Martin Odersky’s keynote talk at Scala Days 2019 made me realize, that I should pay a lot more attention to Scala 3 (a.k.a. Dotty) than I did so far. For me it now seems like Scala 3 will be a huge improvement over Scala 2, in particular because it significantly increases clearness and feature orthogonality. Just take a look at how the somewhat magic – and therefore hard to understand – realm of implicits has transitioned to <code>given</code> clauses which state the dependency on some “contextual” value and <code>delegate</code>s which allow us to define these values – just beautiful!</p><p>I have been travelling the Scala universe for long enough to take the timeline Martin presented – Scala 3 being released in 2020 – with a grain of salt, but nevertheless I think it is time to start learning myself some Scala 3 right away. And what could be better for learning something than writing about it? Hence I decided to write this blog post and hopefully some follow-up ones.</p><p>For this blog post I picked metaprogramming, which is a fairly advanced feature. Yet I will keep it super simple which is possible thanks to a new feature in Scala 3.</p><p>Some years ago, when I was learning Scala 2 macros, I created <a href="https://github.com/lightbend/scala-logging">Scala Logging</a>, a convenient and fast logging library wrapping SLF4J, which has been improved and maintained by others for the past years. It is simple yet useful, because it allows developers to just call the logging methods like <code>debug</code>, which get expanded to the check-enabled-idiom. So let us see, how this can be done in Scala 3.</p><p>To make it crystal clear, the following is what we want to achieve. We want to be able to just call the <code>debug</code> method, without having to first check <code>isDebugEnabled</code>:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">logger.debug(hello())</span><br></pre></td></tr></table></figure><p>This should be compiled to the check-enabled-idiom, i.e. we want the <code>hello</code> method to be evaluated only if <code>isDebugEnabled</code> is <code>true</code>:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (logger.isDebugEnabled) logger.debug(hello())</span><br></pre></td></tr></table></figure><p>Scala 3 there comes with the new <code>inline</code> keyword which is sort of an enabler for various metaprogramming features like macros. But even on its own it is quite powerful as we will see.</p><p>An <code>inline</code>  method is guaranteed to be inlined by the Scala 3 compiler, which means that the method body will be expanded into any call site, i.e. any code which calls the respecive method. Hence we can define a <code>Logger</code> class which wraps an underlying <code>Logger</code> from <a href="https://logging.apache.org/log4j/2.x">Log4j 2</a> and has an inlined <code>debug</code> method which takes the log message as a by-name parameter and applies the check-enabled-idiom:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.apache.logging.log4j.&#123; <span class="type">Logger</span> =&gt; <span class="type">Underlying</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Logger</span>(<span class="params">underlying: <span class="type">Underlying</span></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">inline</span> <span class="function"><span class="keyword">def</span> <span class="title">debug</span></span>(message: =&gt; <span class="type">String</span>): <span class="type">Unit</span> =</span><br><span class="line">    <span class="keyword">if</span> (underlying.isDebugEnabled) underlying.debug(message)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now let us add some code using the above:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.apache.logging.log4j.<span class="type">LogManager</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main</span></span>(args: <span class="type">Array</span>[<span class="type">String</span>]): <span class="type">Unit</span> = &#123;</span><br><span class="line">  <span class="keyword">val</span> logger = <span class="keyword">new</span> <span class="type">Logger</span>(<span class="type">LogManager</span>.getLogger(getClass))</span><br><span class="line">  logger.debug(hello())</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">def</span> <span class="title">hello</span></span>() = &#123;</span><br><span class="line">  println(<span class="string">&quot;********** hello() was called! **********&quot;</span>)</span><br><span class="line">  <span class="string">&quot;Hello!&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>When we run this code, we see that the <code>hello</code> method only gets called, when the log level is set to <code>DEBUG</code>. This becomes clear by inspecting the code which gets generated by the Scala 3 compiler for the <code>main</code> method – we can use <code>javap</code> for this purpose:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">  1: invokeinterface #42,  1  // InterfaceMethod rocks/heikoseeberger/log4dotty/Logging.logger:()Lrocks/heikoseeberger/log4dotty/Logger;</span><br><span class="line">  6: astore_2</span><br><span class="line">  7: aload_2</span><br><span class="line">  8: invokevirtual #48        // Method rocks/heikoseeberger/log4dotty/Logger.inline$underlying:()Lorg/apache/logging/log4j/Logger;</span><br><span class="line">11: invokeinterface #54,  1   // InterfaceMethod org/apache/logging/log4j/Logger.isDebugEnabled:()Z</span><br><span class="line">16: ifeq          38</span><br><span class="line">19: aload_2</span><br><span class="line">20: invokevirtual #48         // Method rocks/heikoseeberger/log4dotty/Logger.inline$underlying:()Lorg/apache/logging/log4j/Logger;</span><br><span class="line">23: aload_0</span><br><span class="line">24: invokespecial #58         // Method hello:()Ljava/lang/String;</span><br><span class="line">27: invokeinterface #62,  2   // InterfaceMethod org/apache/logging/log4j/Logger.debug:(Ljava/lang/String;)V</span><br><span class="line">32: getstatic     #68         // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;</span><br><span class="line">35: goto          41</span><br><span class="line">38: getstatic     #68         // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>As we can see, <code>hello</code> only gets called (pos. 24), if the check whether the <code>DEBUG</code> log level is enabled (pos. 11) has been <code>true</code>; else “nothing” happens, i.e. <code>Unit</code> is returned.</p><p>To sum it up, all we need to implement a convenient check-enabled logging library like Scala Logging in Scala 3, is the new <code>inline</code> feature – no further metaprogramming facilities like macros, which are also available in Scala 3, are necessary.</p><p>The full source code – in a little more polished fashion – can be found at <a href="https://github.com/hseeberger/log4dotty">github.com&#x2F;hseeberger&#x2F;log4dotty</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2019-06-19-dotty-1/</id>
    <link href="https://heikoseeberger.de/2019-06-19-dotty-1/"/>
    <published>2019-06-18T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Martin Odersky’s keynote talk at Scala Days 2019 made me realize, that I should pay a lot more attention to Scala 3 (a.k.a. Dotty) than I]]>
    </summary>
    <title>Learn myself some Scala 3, episode 1: metaprogramming – inline</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="akka" scheme="https://heikoseeberger.de/tags/akka/"/>
    <content>
      <![CDATA[<p>Scala Days 2019 and its sister conference Typelevel Summit were incredibly inspiring and I am traveling home with a lot of motivation in my backpack and elsewhere. I gave a talk about “Scala Streams to the Extreme” and you can find the source code for the demo at <a href="https://github.com/hseeberger/xtream">github.com&#x2F;hseeberger&#x2F;xtream</a> and the slides at <a href="https://github.com/hseeberger/deck-xtream">github.com&#x2F;hseeberger&#x2F;deck-xtream</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2019-06-14-scala-days-xtream/</id>
    <link href="https://heikoseeberger.de/2019-06-14-scala-days-xtream/"/>
    <published>2019-06-13T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Scala Days 2019 and its sister conference Typelevel Summit were incredibly inspiring and I am traveling home with a lot of motivation in]]>
    </summary>
    <title>&quot;Scala Streams to the Extreme&quot; at #scaladays 2019</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="akka" scheme="https://heikoseeberger.de/tags/akka/"/>
    <content>
      <![CDATA[<p>Like every year Scala eXchange London 2018 has been a blast again. I met friends, attended a lot of really interesting and inspiring talks and missed quite a few. Luckily the videos of the talks are already avaliable – here is the link to my talk about “Akka Cluster up and running”: <a href="https://skillsmatter.com/skillscasts/12671-akka-cluster-up-and-running">https://skillsmatter.com/skillscasts/12671-akka-cluster-up-and-running</a>. The source code of the demo application can be found at <a href="https://github.com/hseeberger/akkluster">https://github.com/hseeberger/akkluster</a>.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2018-12-16-scalax-akka-cluster-up-and-running/</id>
    <link href="https://heikoseeberger.de/2018-12-16-scalax-akka-cluster-up-and-running/"/>
    <published>2018-12-15T22:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Like every year Scala eXchange London 2018 has been a blast again. I met friends, attended a lot of really interesting and inspiring talk]]>
    </summary>
    <title>&quot;Akka Cluster up and running&quot; at #scalax 2018</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="akka" scheme="https://heikoseeberger.de/tags/akka/"/>
    <category term="actors" scheme="https://heikoseeberger.de/tags/actors/"/>
    <content>
      <![CDATA[<p>The <a href="https://en.wikipedia.org/wiki/Actor_model">actor model</a> was invented in the early seventies by <a href="https://en.wikipedia.org/wiki/Carl_Hewitt">Carl Hewitt</a> et al. and very successfully implemented in the <a href="https://www.erlang.org/">Erlang</a> programming language. Of course it is also the foundation for <a href="https://akka.io/">Akka</a> and in this post we are taking a look at how exactly Akka implements the actor model, both in good old Akka and in bleeding edge Akka Typed. As usual we are using Scala as our programming language of choice.</p><h2 id="The-Actor-Model"><a href="#The-Actor-Model" class="headerlink" title="The Actor Model"></a>The Actor Model</h2><p>Carl Hewitt <a href="https://www.youtube.com/watch?v=7erJ1DV_Tlo">explains</a> the actor as the “fundamental unit of computation embodying processing (do things), storage (state) and communications” where “everything is an actor” and “one actor is no actor, they come in systems”. Collaboration between actors happens via asynchronous communication, allowing for decoupling of sender and receiver: each actor has an address and can send messages to actors for which it knows their address without being blocked in its processing.</p><p>When an actor processes a message it has received – which is performed by its so called behavior – it can do the following, concurrently and in any order:</p><ol><li>create new actors</li><li>send messages to known actors</li><li>designate the behavior for the next message</li></ol><p>Given this brief description of the actor model, let’s now take a look at how Akka and Akka Typed implement it thereby ignoring creating new actors, because this happens more or less the same way in both Akka and Akka Typed.</p><h2 id="The-Actor-Model-in-Akka"><a href="#The-Actor-Model-in-Akka" class="headerlink" title="The Actor Model in Akka"></a>The Actor Model in Akka</h2><p>In Akka an actor is a class extending <code>Actor</code> and its so called initial behavior is defined by the <code>receive</code> method:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">trait</span> <span class="title">Actor</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">receive</span></span>: <span class="type">PartialFunction</span>[<span class="type">Any</span>, <span class="type">Unit</span>] <span class="comment">// actually type alias `Receive`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The initial behavior is used to handle messages until we change it, if ever. As we can see, the behavior is a partial function from <code>Any</code> to <code>Unit</code> which means that – although Scala and Java are type safe languages – Akka actors are untyped: we can send any message to an actor even though it most probably only handles specific ones – the compiler is not able to help us.</p><p>This is also reflected in the <code>ActorRef</code> trait which represents the address of an actor in Akka:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">trait</span> <span class="title">ActorRef</span> </span>&#123; <span class="comment">// actually `ScalaActorRef`</span></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">!</span></span>(message: <span class="type">Any</span>)(<span class="keyword">implicit</span> sender: <span class="type">ActorRef</span> = <span class="type">Actor</span>.noSender): <span class="type">Unit</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The <code>!</code> operator allows us to send a message of type <code>Any</code> to the actor identified by the respective address. Obviously there is no way to restrict the message to some specific type. Further the address of the sender is conveyed as an implicit parameter if possible, which is the case when sending happens from within an actor.</p><p>Let’s look at another important aspect of the behavior in Akka. It returns <code>Unit</code> which means that designating a different behavior for the next message must happen as a side effect. And indeed each actor has access to its <code>ActorContext</code> which offers the <code>become</code> method for that exact purpose:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">trait</span> <span class="title">ActorContext</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">become</span></span>(behavior: <span class="type">PartialFunction</span>[<span class="type">Any</span>, <span class="type">Unit</span>]): <span class="type">Unit</span> <span class="comment">// actually type alias `Receive`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-Same-Behavior-with-Mutable-State"><a href="#1-Same-Behavior-with-Mutable-State" class="headerlink" title="1. Same Behavior with Mutable State"></a>1. Same Behavior with Mutable State</h3><p>Equipped with this knowledge, let’s implement a simple example: an actor which produces increasing sequence numbers. Let’s start with an actor which stores the current sequence number value in a mutable field – Akka makes sure this cannot be shared and all access happens in a thread safe manner:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> akka.actor.<span class="type">Actor</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">object</span> <span class="title">MutableSeqNoGenerator</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">final</span> <span class="keyword">case</span> <span class="class"><span class="keyword">object</span> <span class="title">GetNext</span></span></span><br><span class="line">  <span class="keyword">final</span> <span class="keyword">case</span> <span class="class"><span class="keyword">class</span> <span class="title">SeqNo</span>(<span class="params">seqNo: <span class="type">Long</span></span>)</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">MutableSeqNoGenerator</span>(<span class="params">seqNo: <span class="type">Long</span> = 0</span>) <span class="keyword">extends</span> <span class="title">Actor</span> </span>&#123;</span><br><span class="line">  <span class="keyword">import</span> <span class="type">MutableSeqNoGenerator</span>._</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">var</span> _seqNo = seqNo</span><br><span class="line"></span><br><span class="line">  <span class="keyword">override</span> <span class="function"><span class="keyword">def</span> <span class="title">receive</span> </span>= &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="type">GetNext</span> =&gt;</span><br><span class="line">      sender() ! <span class="type">SeqNo</span>(_seqNo)</span><br><span class="line">      _seqNo += <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>As the behavior closes over the mutable field we need not ever change it (the behavior).</p><h3 id="2-Changing-Behavior"><a href="#2-Changing-Behavior" class="headerlink" title="2. Changing Behavior"></a>2. Changing Behavior</h3><p>While the above implementation represents an idiomatic pattern for Akka, we can also handle the mutability of the sequence number value by changing the behavior instead of using a mutable field:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> akka.actor.<span class="type">Actor</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">object</span> <span class="title">SeqNoGenerator</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">final</span> <span class="keyword">case</span> <span class="class"><span class="keyword">object</span> <span class="title">GetNext</span></span></span><br><span class="line">  <span class="keyword">final</span> <span class="keyword">case</span> <span class="class"><span class="keyword">class</span> <span class="title">SeqNo</span>(<span class="params">seqNo: <span class="type">Long</span></span>)</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">SeqNoGenerator</span>(<span class="params">seqNo: <span class="type">Long</span> = 0</span>) <span class="keyword">extends</span> <span class="title">Actor</span> </span>&#123;</span><br><span class="line">  <span class="keyword">import</span> <span class="type">SeqNoGenerator</span>._</span><br><span class="line"></span><br><span class="line">  <span class="keyword">override</span> <span class="function"><span class="keyword">def</span> <span class="title">receive</span> </span>= seqNoGenerator(seqNo)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="function"><span class="keyword">def</span> <span class="title">seqNoGenerator</span></span>(seqNo: <span class="type">Long</span>): <span class="type">Receive</span> = &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="type">GetNext</span> =&gt;</span><br><span class="line">      sender() ! <span class="type">SeqNo</span>(seqNo)</span><br><span class="line">      context.become(seqNoGenerator(seqNo + <span class="number">1</span>))</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In this case we change the behavior every time the actor processes a <code>GetNext</code> message and thus can get rid of the mutable field. This pattern is particularly useful to implement actors representing state machines.</p><h2 id="The-Actor-Model-in-Akka-Typed"><a href="#The-Actor-Model-in-Akka-Typed" class="headerlink" title="The Actor Model in Akka Typed"></a>The Actor Model in Akka Typed</h2><p>As clearly indicated by naming, Akka Typed brings type safety back to Akka. But Akka Typed also resembles the actual definition of the actor model much closer than Akka by dropping the <code>Actor</code> trait and conceptually treating behavior as a function from a typed message to the next behavior:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">ExtensibleBehavior</span>[<span class="type">T</span>] <span class="keyword">extends</span> <span class="title">Behavior</span>[<span class="type">T</span>] </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">receiveMessage</span></span>(ctx: <span class="type">ActorContext</span>[<span class="type">T</span>], msg: <span class="type">T</span>): <span class="type">Behavior</span>[<span class="type">T</span>]</span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">receiveSignal</span></span>(ctx: <span class="type">ActorContext</span>[<span class="type">T</span>], msg: <span class="type">Signal</span>): <span class="type">Behavior</span>[<span class="type">T</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>As we can see, the behavior has a type parameter for the type of messages the actor handles. Therefore <code>receiveMessage</code> is a total function instead of a partial one. Since there is no enclosing <code>Actor</code> class, the <code>ActorContext</code> is also passed as a parameter along with the respective message. And finally <code>receiveSignal</code> takes care of a few special messages like <code>PreRestart</code> or <code>Terminated</code> which of course are not covered by the message type <code>T</code>.</p><p>Having a parameterized behavior we obviously also need a parameterized address:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">implicit</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">ActorRefOps</span>[-<span class="type">T</span>](<span class="params">val ref: <span class="type">ActorRef</span>[<span class="type">T</span>]</span>) <span class="keyword">extends</span> <span class="title">AnyVal</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">!</span></span>(msg: <span class="type">T</span>): <span class="type">Unit</span> = ref.tell(msg)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now we can only send messages of type <code>T</code> to the respective actor: good-bye <code>Any</code>!</p><p>One important difference is worth mentioning: in Akka Typed there is no implicit sender any more. While this inevitable change – what exact type should <code>sender()</code> return? – might look like a step backwards, we can even regard it as an advantage: we have to make our message protocols more explicit and therefore easier to understand by including addresses for replies where needed.</p><p>Like in Akka we have two ways to actually create a behavior: a mutable – closing over mutable state – and an immutable one. This time the second one is exclusively considered the idiomatic one, hence we only show the following example:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> akka.typed.&#123; <span class="type">ActorRef</span>, <span class="type">Behavior</span> &#125;</span><br><span class="line"><span class="keyword">import</span> akka.typed.scaladsl.<span class="type">Actor</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">object</span> <span class="title">SeqNoGenerator</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">final</span> <span class="keyword">case</span> <span class="class"><span class="keyword">class</span> <span class="title">GetNext</span>(<span class="params">replyTo: <span class="type">ActorRef</span>[<span class="type">SeqNo</span>]</span>)</span></span><br><span class="line">  <span class="keyword">final</span> <span class="keyword">case</span> <span class="class"><span class="keyword">class</span> <span class="title">SeqNo</span>(<span class="params">seqNo: <span class="type">Long</span></span>)</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">apply</span></span>(seqNo: <span class="type">Long</span> = <span class="number">0</span>): <span class="type">Behavior</span>[<span class="type">GetNext</span>] =</span><br><span class="line">    <span class="type">Actor</span>.immutable &#123;</span><br><span class="line">      <span class="keyword">case</span> (_, <span class="type">GetNext</span>(replyTo)) =&gt;</span><br><span class="line">        replyTo ! <span class="type">SeqNo</span>(seqNo)</span><br><span class="line">        <span class="type">SeqNoGenerator</span>(seqNo + <span class="number">1</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Notice that we have to include the <code>replyTo</code> address in the <code>GetNext</code> message as a typed <code>ActorRef[SeqNo]</code>, representing an address which only accepts messages of type <code>SeqNo</code>.</p><p>Using <code>Actor.immutable</code> we define a so called immutable behavior, i.e. one which does not close over mutable state. It is typed to only handle messages of type <code>GetNext</code>. We are using pattern matching, but only to conveniently destruct the message – the message handler function given as argument to <code>Actor.immutable</code> is still a total one. The result of the message handler is the next behavior, similar like using <code>ActorContext.become</code> above, but not as a side-effect which makes testing much easier.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>We have looked at the ways Akka and Akka Typed implement the actor model. Both fully support the required behavior rules, i.e. creating new actors, sending messages to known actors and designating the next behavior. But Akka Typed not only makes the last aspect more tangible by conceptually treating behavior as a function from message to the next behavior, but also brings back type safety.</p><p>The full source code for the examples can be found at <a href="https://github.com/hseeberger/blog-actor-model">github.com&#x2F;hseeberger&#x2F;blog-actor-model</a> in the <code>de.heikoseeberger.blog.actormodel</code> package.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2017-10-02-actor-model/</id>
    <link href="https://heikoseeberger.de/2017-10-02-actor-model/"/>
    <published>2017-10-01T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>The <a href="https://en.wikipedia.org/wiki/Actor_model">actor model</a> was invented in the early seventies by <a href="https://en.wikipe]]>
    </summary>
    <title>The Actor Model in Akka and Akka Typed</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <category term="akka" scheme="https://heikoseeberger.de/tags/akka/"/>
    <content>
      <![CDATA[<p>Testing actors is different from “traditional” testing of objects or functions. First, the only way to interact with actors is via asynchronous messaging. This means that we can’t simply call a method or function and compare the actual result with the expected. Second – thanks to the baked in concurrency – actors behave in a nondeterministic fashion.</p><p><a href="https://akka.io/">Akka</a> comes with a neat module for facilitating testing of actors named “akka-testkit”. It has been created quite a while ago and we – including the authors – have learned some lessons since then. I’m going to introduce the tools provided by “akka-testkit” alongside with some recommendations on which to use and how.</p><h2 id="TestActorRef"><a href="#TestActorRef" class="headerlink" title="TestActorRef"></a>TestActorRef</h2><p>Akka – the core “akka-actor” module, to be more precise – prevents us from getting access to an instance of an actor. Yet “akka-testkit” introduces the <code>TestActorRef</code> which gives access to the <code>underlyingActor</code> instance.</p><p>While this might sound like a great idea, it turns out the opposite, because the <code>TestActorRef</code> brings its special dispatcher – the <code>CurrentThreadDispatcher</code> – which turns all interactions with the actor into synchronous ones. Clearly testing within this friendly sandboxed environment is far from real conditions, namely asynchrony and nondeterminism.</p><p>Usually not getting access to an instance of an actor is not an issue, because it should always be possible to separate our business logic from actor logic, e.g. by defining the business logic as methods in the companion object of the actor. That way we can still test the business logic the “traditional” way. And for testing the actor logic we can use <code>TestProbe</code>s – another tool provided by “akka-testkit” and introduced below.</p><blockquote><p>Avoid using <code>TestActorRef</code>!</p></blockquote><h2 id="TestKit"><a href="#TestKit" class="headerlink" title="TestKit"></a>TestKit</h2><p>As mentioned above, we can only interact with actors via asynchronous messaging. Therefore the message protocol of an actor – the messages it receives and sends – is its de facto API. Hence in order to test the actor logic we have to send specific messages and expect messages sent in response.</p><p>“akka-testkit” introduces the <code>TestKit</code> to extend our test suites from. This gives access to a <code>testActor</code> which can be used as (implicit) sender or mocked <code>ActorRef</code> in messages sent. In addition <code>TestKit</code> introduces a lot of so called built-in assertions which allow us to expect – or not expect – messages sent from the actor to be tested to the <code>testActor</code>.</p><p>Here comes a simple – and admittedly a little contrived – example which not only shows how to use <code>TestKit</code>, but also demonstrates its core issue:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">EchoSpec</span></span></span><br><span class="line">    <span class="keyword">extends</span> <span class="type">TestKit</span>(<span class="type">ActorSystem</span>(<span class="string">&quot;EchoSpec&quot;</span>))</span><br><span class="line">    <span class="keyword">with</span> <span class="type">WordSpecLike</span></span><br><span class="line">    <span class="keyword">with</span> <span class="type">BeforeAndAfterAll</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="string">&quot;Echo&quot;</span> should &#123;</span><br><span class="line">    <span class="string">&quot;respond with &#x27;Hello&#x27; upon receiving &#x27;Hello&#x27;&quot;</span> in &#123;</span><br><span class="line">      <span class="keyword">implicit</span> <span class="keyword">val</span> sender = testActor</span><br><span class="line">      <span class="keyword">val</span> echo            = system.actorOf(<span class="type">Echo</span>())</span><br><span class="line">      echo ! <span class="string">&quot;Hello&quot;</span></span><br><span class="line">      expectMsg(<span class="string">&quot;Hello&quot;</span>)</span><br><span class="line">      echo ! <span class="string">&quot;By mistake&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="string">&quot;respond with &#x27;World&#x27; upon receiving &#x27;World&#x27;&quot;</span> in &#123;</span><br><span class="line">      <span class="keyword">implicit</span> <span class="keyword">val</span> sender = testActor</span><br><span class="line">      <span class="keyword">val</span> echo            = system.actorOf(<span class="type">Echo</span>())</span><br><span class="line">      echo ! <span class="string">&quot;World&quot;</span></span><br><span class="line">      expectMsg(<span class="string">&quot;World&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">protected</span> <span class="function"><span class="keyword">def</span> <span class="title">afterAll</span></span>() = &#123; ... &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Can you spot the issue? Right, the second test fails, because the <code>testActor</code> is shared across all the tests in the suite. Even though tests usually run sequentially, messages sent but not consumed by one test – “By mistake” in our example – still remain in the inbox of the <code>testActor</code> and get consumed by subsequent tests instead of the intended ones.</p><p>Additionally for advanced protocols we often need more <code>ActorRef</code>s which we can use like the <code>testActor</code>. These are provided via the last tool introduced here, the <code>TestProbe</code>. So the recommendation is to always only use test-local <code>TestProbe</code>s which avoids the aforementioned issue of sharing a single <code>testActor</code> across tests.</p><blockquote><p>Don’t use <code>TestKit</code>, instead use test-local <code>TestProbe</code>s!</p></blockquote><h2 id="TestProbe"><a href="#TestProbe" class="headerlink" title="TestProbe"></a>TestProbe</h2><p><code>TestProbe</code>s are more or less like <code>TestKit</code> – they actually extend it – without extending the suite from. Instead they are created as needed for each test.</p><p>Here comes the above simple example rewritten to use test-local <code>TestProbe</code>s:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">EchoSpec</span> <span class="keyword">extends</span> <span class="title">WordSpec</span> <span class="keyword">with</span> <span class="title">BeforeAndAfterAll</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">implicit</span> <span class="keyword">val</span> system = <span class="type">ActorSystem</span>(<span class="string">&quot;EchoSpec&quot;</span>)</span><br><span class="line"></span><br><span class="line">  <span class="string">&quot;Echo&quot;</span> should &#123;</span><br><span class="line">    <span class="string">&quot;respond with &#x27;Hello&#x27; upon receiving &#x27;Hello&#x27;&quot;</span> in &#123;</span><br><span class="line">      <span class="keyword">val</span> sender             = <span class="type">TestProbe</span>()</span><br><span class="line">      <span class="keyword">implicit</span> <span class="keyword">val</span> senderRef = sender.ref</span><br><span class="line">      <span class="keyword">val</span> echo               = system.actorOf(<span class="type">Echo</span>())</span><br><span class="line">      echo ! <span class="string">&quot;Hello&quot;</span></span><br><span class="line">      sender.expectMsg(<span class="string">&quot;Hello&quot;</span>)</span><br><span class="line">      echo ! <span class="string">&quot;This message is sent unintentionally&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="string">&quot;respond with &#x27;World&#x27; upon receiving &#x27;World&#x27;&quot;</span> in &#123;</span><br><span class="line">      <span class="keyword">val</span> sender             = <span class="type">TestProbe</span>()</span><br><span class="line">      <span class="keyword">implicit</span> <span class="keyword">val</span> senderRef = sender.ref</span><br><span class="line">      <span class="keyword">val</span> echo               = system.actorOf(<span class="type">Echo</span>())</span><br><span class="line">      echo ! <span class="string">&quot;World&quot;</span></span><br><span class="line">      sender.expectMsg(<span class="string">&quot;World&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">protected</span> <span class="function"><span class="keyword">def</span> <span class="title">afterAll</span></span>() = &#123; ... &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>As each test has its own <code>TestProbe</code>, sending more message than consuming these in the first test doesn’t influence the second. Of course we have to write one more line for each test and also call <code>expectMsg</code> or other built-in assertions on the <code>TestProbe</code>, but this minimal additional boilerplate usually doesn’t hurt. And it will pay off, because errors like the above are sometimes almost impossible to spot.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>After discussing the three main tools “akka-testkit” provides, we have to conclude that only <code>TestProbe</code> should be used. No <code>TestActorRef</code> and no <code>TestKit</code>.</p><p>Happy hakking!</p>]]>
    </content>
    <id>https://heikoseeberger.de/2017-09-13-how-to-use-akka-testkit/</id>
    <link href="https://heikoseeberger.de/2017-09-13-how-to-use-akka-testkit/"/>
    <published>2017-09-12T21:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Testing actors is different from “traditional” testing of objects or functions. First, the only way to interact with actors is via asynch]]>
    </summary>
    <title>How to (not) use akka-testkit</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>Heiko Seeberger</name>
    </author>
    <content>
      <![CDATA[<p>I’m starting a new blog. The old can be found at <a href="https://hseeberger.wordpress.com/">hseeberger.wordpress.com</a>. </p><p>Stay tuned.</p>]]>
    </content>
    <id>https://heikoseeberger.de/2017-01-23-hello-new-blog/</id>
    <link href="https://heikoseeberger.de/2017-01-23-hello-new-blog/"/>
    <published>2017-01-22T22:00:00.000Z</published>
    <summary>
      <![CDATA[<p>I’m starting a new blog. The old can be found at <a href="https://hseeberger.wordpress.com/">hseeberger.wordpress.com</a>. </p>
<p>Stay t]]>
    </summary>
    <title>Hello, new Blog!</title>
    <updated>2026-10-19T07:55:34.592Z</updated>
  </entry>
</feed>
