<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexander Przemysław Kamiński</title>
    <description>The latest articles on DEV Community by Alexander Przemysław Kamiński (@exlee).</description>
    <link>https://dev.to/exlee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F350060%2F53648360-7846-49d3-816f-519a4a4f43e1.jpeg</url>
      <title>DEV Community: Alexander Przemysław Kamiński</title>
      <link>https://dev.to/exlee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/exlee"/>
    <language>en</language>
    <item>
      <title>Elegant Rust with proc macros</title>
      <dc:creator>Alexander Przemysław Kamiński</dc:creator>
      <pubDate>Thu, 02 Apr 2026 13:04:44 +0000</pubDate>
      <link>https://dev.to/exlee/elegant-rust-with-proc-macros-khc</link>
      <guid>https://dev.to/exlee/elegant-rust-with-proc-macros-khc</guid>
      <description>&lt;p&gt;When writing immediate mode (egui) applications it comes to me quickly that nigh all logic computations should be done off the UI thread. There are many ways to approach it, however as a fan of event-based systems sooner or later I implement some kind of event handling. The pattern is almost the same with minor differences and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ProcessorConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;TaskResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;active_tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ProcessorStart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ProcessorConfig&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;ProcessorStop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;LongRunningTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;LongRunningTaskComplete&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;task_result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TaskResult&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RwLock&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;ProcessorStart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.process_processor_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;ProcessorStop&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.process_processor_stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;LongRunningTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.process_long_running_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;LongRunningTaskComplete&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.process_long_running_task_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_processor_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_event_queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ProcessorStart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting processor with config: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="py"&gt;.id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_processor_stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_event_queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ProcessorStop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Stopping processor..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_long_running_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_event_queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;LongRunningTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting task {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.active_tasks&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_long_running_task_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_event_queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;LongRunningTaskComplete&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;task_result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Task completed. Success: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_result&lt;/span&gt;&lt;span class="py"&gt;.success&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.active_tasks&lt;/span&gt;&lt;span class="nf"&gt;.clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7212q0bf4u7tj4qkkh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7212q0bf4u7tj4qkkh5.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Design rationale
&lt;/h2&gt;

&lt;p&gt;Code above has some design decisions that were developed over the years and it has specific needs "baked in" as a result. E.g.:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Serialization/deserialization and clone for &lt;code&gt;Event&lt;/code&gt; is very important. I want to save stream to be able to: recreate the state, re-run event, create an event log etc. Starting with serialization and clone requirement makes sure that all events have adequate shape.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Arc&amp;lt;RwLock&amp;lt;State&amp;gt;&amp;gt;&lt;/code&gt; allows to defer processing to background threads and make a callback with state modification. It also keeps &lt;code&gt;Sender&amp;lt;Event&amp;gt;&lt;/code&gt; for interacting with event system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;amp;mut VecDeque&amp;lt;Event&amp;gt;&lt;/code&gt; is an event pushback, it works with following loop pattern:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;   &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="nf"&gt;.recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="nf"&gt;.push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="nf"&gt;.try_recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
           &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="nf"&gt;.push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
       &lt;span class="p"&gt;}&lt;/span&gt;

       &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="nf"&gt;.pop_front&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing &lt;code&gt;self.tx.clone().send(...)&lt;/code&gt; in the same thread could end up deadlocking receiver and the whole application. To avoid deadlock same-thread processor can send event directly to queue instead. Another feature coming from the pattern is also an in-place event transformations. For example it allows for upgrading events without changing processing order (by &lt;code&gt;event_queue.push_front&lt;/code&gt; during processing logic).&lt;/p&gt;

&lt;p&gt;I'll note that in example I didn't use &lt;code&gt;Envelope&lt;/code&gt; pattern - even though in final code I do. In a nutshell:  &lt;code&gt;Envelope&lt;/code&gt; replaces &lt;code&gt;Event&lt;/code&gt; with a wrapper-like structure, i.e. &lt;code&gt;Envelope { event: Event, ...}&lt;/code&gt;. Extra data can be included in envelope, a data that doesn't impact on processing but provides useful information. Such data can be, for example, a &lt;code&gt;tracing::Span&lt;/code&gt; (for telemetry it is almost required) or (in distributed systems) origin system, timestamps, correlation etc. Envelopes should be processed in a same way by processors and they add to boilerplate. &lt;code&gt;EventEnvelope&lt;/code&gt; processor could like like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tracing::instrument(skip_all,&lt;/span&gt; &lt;span class="nd"&gt;parent:&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;envelope&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;span)]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_some_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;envelope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventEnvelope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EventEnvelope&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nn"&gt;CustomCounter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;count_origin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;envelope&lt;/span&gt;&lt;span class="py"&gt;.origin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SomeTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;envelope&lt;/span&gt;&lt;span class="py"&gt;.event&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Perfect World
&lt;/h2&gt;

&lt;p&gt;After implementing &lt;em&gt;same thing thrice&lt;/em&gt; I was ready to make a macro for it. I love macros, even though I understand that they shadow the code and add to complexity. This is the same code as the initial example, but in proc-macro version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ProcessorConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;TaskResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;active_tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RwLock&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[event_macros::event_processor]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[handler(ProcessorStart)]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_processor_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ProcessorConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting processor with config: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="py"&gt;.id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;#[handler(ProcessorStop)]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_processor_stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Stopping processor..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;#[handler(LongRunningTask)]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_long_running_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting task {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.active_tasks&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;#[handler(LongRunningTaskComplete)]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_long_running_task_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TaskResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Task completed. Success: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_result&lt;/span&gt;&lt;span class="py"&gt;.success&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.active_tasks&lt;/span&gt;&lt;span class="nf"&gt;.clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I really, really like it. Transformed code  from that example is the same as the initial code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What transformations are doing?
&lt;/h2&gt;

&lt;p&gt;On the top there's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[event_macros::event_processor]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's an actual entry point in the macro, it gathers all the information needed and from that it outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full &lt;code&gt;pub enum Event { ... }&lt;/code&gt; definition&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run(&amp;amp;self, event: Event, event_queue: &amp;amp;mut VecDeque&amp;lt;Event&amp;gt;)&lt;/code&gt; implementation (listen, add to queue, handle from queue)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;impl Debug for Event&lt;/code&gt; (for debugging purpose it's helpful to know the name of current event)&lt;/li&gt;
&lt;li&gt;transformed &lt;code&gt;fn process_ ...&lt;/code&gt; functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Visualized, this is how transformation happen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01ld43cc7wdi1de7b6gb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01ld43cc7wdi1de7b6gb.png" alt=" " width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why proc-macro at all
&lt;/h2&gt;

&lt;p&gt;If I'd try hard enough I could (probably) achieve similar result with &lt;code&gt;macro_rules!&lt;/code&gt; macro. And I tried!&lt;/p&gt;

&lt;p&gt;There is however one thing that I really hate when working with such macros: &lt;code&gt;rust-analyzer&lt;/code&gt; and &lt;code&gt;rust-fmt&lt;/code&gt; giving up on everything. I don't get inspection, I don't get completion, I don't get formatting. For simple macros that's fine - I can live with a couple non-completed arguments. Yet, with event-driven architecture, the core logic actually lives in handlers, so I didn't want to be deprived of these helpers.&lt;/p&gt;

&lt;p&gt;When switching to proc-macros an interesting part is that processor functions are perfectly valid ones!  &lt;code&gt;rust-analyzer&lt;/code&gt; sees input and output types. I also do some magic (that I don't like but hey...): when &lt;code&gt;EventEnvelope&lt;/code&gt; or &lt;code&gt;VecDeque&amp;lt;EventEnvelope&amp;gt;&lt;/code&gt; is in function inputs I forward the variable name to body processing, i.e.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[handler(WithEnvelope)]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_with_envelope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_envelope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventEnvelope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_with_envelope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventEnvelope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EventEnvelope&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;my_envelope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... rest of the code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Production code
&lt;/h2&gt;

&lt;p&gt;You can see implementation of (macroized) code at &lt;a href="https://github.com/exlee/hn-jobs-evaluator/blob/e920def3bc505d2d3b3e59678f39beb9413b41bb/jobs/src/events.rs#L112-L527" rel="noopener noreferrer"&gt;one of my open source projects&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are however, some notable differences.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As I mentioned before: I'm using &lt;code&gt;EventEnvelope&lt;/code&gt; and &lt;code&gt;tracing&lt;/code&gt; for telemetry, so the actual code is slightly more complex:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;process_my_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventEnvelope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EventEnvelope&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MyEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="py"&gt;.event&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;unreachable!&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello from MyEvent"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;handle&lt;/code&gt; and &lt;code&gt;run&lt;/code&gt; functions are asynchronous (because my spawned processor is asynchronous one)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ...implementation?
&lt;/h2&gt;

&lt;p&gt;It's &lt;a href="https://github.com/exlee/hn-jobs-evaluator/blob/e920def3bc505d2d3b3e59678f39beb9413b41bb/event_macros/src/event.rs" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I won't go into implementation itself, but I need to say that &lt;em&gt;TODAY&lt;/em&gt; it's easier than it was some time ago.&lt;/p&gt;

&lt;p&gt;LLMs are quite capable in generating proc-macro code given simple instructions, and once I grasped the main concept it was actually quite easy (especially using &lt;code&gt;proc-macro&lt;/code&gt;, &lt;code&gt;proc-macro2&lt;/code&gt; and &lt;code&gt;syn&lt;/code&gt; crates). In case you wonder, the most important pieces are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;proc-macro&lt;/code&gt; is &lt;strong&gt;the only&lt;/strong&gt; crate that can produce proc-macros and it &lt;strong&gt;must&lt;/strong&gt; produce &lt;code&gt;proc_macro::TokenStream&lt;/code&gt; as output&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;proc-macro2&lt;/code&gt; provides many utilities, but outputs &lt;code&gt;proc_macro2::TokenStream&lt;/code&gt; - incompatible, but easily convertible to and from &lt;code&gt;proc_macro::TokenStream&lt;/code&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;syn&lt;/code&gt; is creating structs of actual code - things like &lt;code&gt;FnItem&lt;/code&gt; or &lt;code&gt;Variant&lt;/code&gt; for enums, you can construct them by hand&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;syn&lt;/code&gt; is using &lt;code&gt;proc-macro2&lt;/code&gt; for utilities &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;code pieces can be generated using &lt;code&gt;quote!&lt;/code&gt; (or &lt;code&gt;quote_spanned!&lt;/code&gt; if you want to hint user of what is actual source of errors)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;parse_quote!&lt;/code&gt; can be used to fill in &lt;code&gt;syn&lt;/code&gt; structs, e.g.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;  &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="py"&gt;.sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;parse_quote!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;some_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I internalized, used some help from LLMs implementation was quite smooth.&lt;/p&gt;

&lt;h2&gt;
  
  
  At the end of the day
&lt;/h2&gt;

&lt;p&gt;...I'm really happy with the result. Adding new Events was a chore, usually required at least for places to put the code in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;call site&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Event&lt;/code&gt; enum&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handle&lt;/code&gt; function&lt;/li&gt;
&lt;li&gt;boilerplate heavy &lt;code&gt;processor_&lt;/code&gt; function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the change it became only 2, much simplified and deprived of boiler plate.&lt;/p&gt;

&lt;p&gt;I considered packing this into a crate (because I KNOW this pattern is good and reusable) but I have some doubts about it.&lt;/p&gt;

&lt;p&gt;I don't know if there's any audience: would I use such crate? No idea. Maybe they exist. It's also opinionated - using &lt;code&gt;tracing&lt;/code&gt; and &lt;code&gt;serde&lt;/code&gt; for serialization. Also it raw crates &lt;code&gt;Event&lt;/code&gt; and &lt;code&gt;EventEnvelope&lt;/code&gt; in the current namespace, which might be conflicting. Then I require async processing run to dispatch events to handle.&lt;/p&gt;

&lt;p&gt;So maybe someday, but if anyone stumbles upon it, the proc-macro implementation is only 300 lines of code (excluding tests) and is pretty straightforward to modify. Maybe it's enough? &lt;/p&gt;

</description>
      <category>rust</category>
      <category>eventdriven</category>
      <category>software</category>
    </item>
    <item>
      <title>I hate Github Actions with Passion</title>
      <dc:creator>Alexander Przemysław Kamiński</dc:creator>
      <pubDate>Wed, 14 Jan 2026 23:00:00 +0000</pubDate>
      <link>https://dev.to/exlee/i-hate-github-actions-with-passion-1ppg</link>
      <guid>https://dev.to/exlee/i-hate-github-actions-with-passion-1ppg</guid>
      <description>&lt;p&gt;I can't overstate how much I hate GitHub Actions. I don't even remember hating any other piece of technology I used. Sure, I still make fun of PHP that I remember from times of PHP4&lt;sup id="fnref1"&gt;1&lt;/sup&gt;, but even then I didn't &lt;em&gt;hate&lt;/em&gt; it. Merely I found it subpar technology to other emerging at the time (like Ruby on Rails or Django). And yet I hate GitHub Actions.&lt;/p&gt;

&lt;p&gt;With Passion&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyihar4z2qxin1kuy2ilj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyihar4z2qxin1kuy2ilj.jpg" alt="Developer's spirit broken after yet another GitHub Actions Failure" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Road to Hell
&lt;/h2&gt;

&lt;p&gt;Day before writing these words I was implementing &lt;code&gt;build.rs&lt;/code&gt; for my &lt;a href="https://github.com/exlee/tmplr" rel="noopener noreferrer"&gt;tmplr&lt;/a&gt; project. To save you a click - it is a file/project scaffold tool with human readable (and craftable) template files. I (personally) use it very often, given how easy it is to craft new templates, by hand or with aid of the tool, so check it out if you need a similar tool.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;build.rs&lt;/code&gt; used &lt;code&gt;CUE&lt;/code&gt; to generate &lt;code&gt;README.md&lt;/code&gt;, &lt;code&gt;CHANGELOG.md&lt;/code&gt; and also a version/help file to guarantee consistency. It was fun thing to do, it took approx. 1.5h and I even &lt;a href="https://dev.to/exlee/buildrs-ing-documentation-with-cuelang-3h2a"&gt;wrote an article&lt;/a&gt; about it. For myself and future generations.&lt;/p&gt;

&lt;p&gt;I was happy with the results and didn't check CI output which, quite unsurprisingly, failed. I was using &lt;code&gt;cue&lt;/code&gt; binary inside &lt;code&gt;build.rs&lt;/code&gt; and without it build simply couldn't progress. When I woke up next day and saw e-mail from CI notifying me about failed build I immediatelly knew my day isn't going to start with puppies and rainbows.&lt;/p&gt;

&lt;p&gt;It took couple attempts to search and push GitHub Action that would install &lt;code&gt;CUE&lt;/code&gt; and then I got the worst of the worst results: One system in matrix failing to build.&lt;/p&gt;

&lt;p&gt;A word of explanation. I'm building &lt;code&gt;tmplr&lt;/code&gt; for 4 platforms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux ARM&lt;/li&gt;
&lt;li&gt;macOS ARM&lt;/li&gt;
&lt;li&gt;Linux x86_64&lt;/li&gt;
&lt;li&gt;macOS x86_64&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Makes sense, right? Even though my user base can be counted on a fingers of one-arm-less and second-arm-hook-equipped pirate, it's still a thing "One Should Do".&lt;/p&gt;

&lt;p&gt;And with all that - Linux ARM failed with "command can't be found". &lt;code&gt;CUE&lt;/code&gt; installed and ran nicely for all other 3 targets, but for some reason it failed for Linux ARM.&lt;/p&gt;

&lt;p&gt;In case you don't care about &lt;em&gt;why&lt;/em&gt; I hate GitHub but your mind started to wonder to "what went wrong" let me tell you; because I know.&lt;/p&gt;

&lt;p&gt;So supposedly cross build that happens in matrix is heavily isolated. When I install &lt;code&gt;CUE&lt;/code&gt; I install it only on x86_64 Linux host and macOS ARM host. macOS has zero issues running x86_64 binary and no issues are raised when Linux x86_64 tries to run x86_64 binary. But GitHub Actions is nice enough to &lt;strong&gt;hide&lt;/strong&gt; x86_64 binary from arm64 runner, so that it won't break.&lt;/p&gt;

&lt;p&gt;Thank you GitHub Actions. What would've I done without you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Broken Loop
&lt;/h2&gt;

&lt;p&gt;And so my least favorite feedback loop started and went like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search for possible fix&lt;/li&gt;
&lt;li&gt;Change &lt;code&gt;ci.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jj squash --ignore-immutable &amp;amp;&amp;amp; jj git push&lt;/code&gt; &lt;sup id="fnref3"&gt;3&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;Open "Actions" tab&lt;/li&gt;
&lt;li&gt;Open latest run&lt;/li&gt;
&lt;li&gt;Open Linux ARM run&lt;/li&gt;
&lt;li&gt;Wait couple of seconds&lt;/li&gt;
&lt;li&gt;Hate Life&lt;/li&gt;
&lt;li&gt;Offer the Universe choice words it won't soon forget&lt;/li&gt;
&lt;li&gt;Rinse &amp;amp; repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I got quite efficient when it comes to points 8 and 9 but otherwise the whole loop still took around 2-3 minutes to execute.&lt;/p&gt;

&lt;p&gt;FOR. A. SINGLE. CHANGE.&lt;/p&gt;

&lt;p&gt;Yes. For a single change. Like having an editor with 2 minute save lag, pushing commit using program running on cassette tapes&lt;sup id="fnref4"&gt;4&lt;/sup&gt; or playing chess over snail-mail. It's 2026 for Pete's sake, and we&lt;sup id="fnref5"&gt;5&lt;/sup&gt; won't tolerate this behavior!&lt;/p&gt;

&lt;p&gt;Now of course, in some Perfect World, GitHub could have a local runner with all the bells and whistles. Or maybe something that would allow me to quickly check for progress upon the push&lt;sup id="fnref6"&gt;6&lt;/sup&gt; or even something like a "scratch commit", i.e. a way that I could testbed different runs without polluting history of both Git and Action runs.&lt;/p&gt;

&lt;p&gt;But no such perfect world exists and one is at the whim of heartless YAML-based system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking off
&lt;/h2&gt;

&lt;p&gt;I suffered only 30 minutes of such loops. Could've done it for longer but I was out of colorful language to use and felt without it the process just isn't the same.&lt;/p&gt;

&lt;p&gt;There is a wise saying in the internet that goes like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the love of all that is holy, don't let GitHub Actions manage your logic. Keep your scripts under your own damn control and just make the Actions call them!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is what everyone should do. This is what I did.&lt;/p&gt;

&lt;p&gt;I deleted &lt;code&gt;build.rs&lt;/code&gt; (with a sliver of sadness because it was really nice - but sacrifices had to be made). I moved all the generation from &lt;code&gt;build.rs&lt;/code&gt; to GNU Makefile, committed the darn files into repository, reverted changes to CI and called it a day. Problem solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exit Code: 0
&lt;/h2&gt;

&lt;p&gt;GitHub Actions, Friends &amp;amp; Gentlefolk, is the reason why we can't have (some) nice things. I can't count how many hours I've lost debugging the runners or trying to optimize the build process. It's a sorry process every single time, a time that would be better spent elsewhere.&lt;/p&gt;

&lt;p&gt;And yet there are some benefits, like macOS builds that would be quite hard to get otherwise. I don't know any other system that would be easier to setup than GitHub Actions (if you know one, let me know) but it seems there's no escape.&lt;/p&gt;

&lt;p&gt;We are all doomed to GitHub Actions.&lt;/p&gt;

&lt;p&gt;...but at least I dodged the bullet early.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;And "PHP: Training Wheels Without a Bike" is still in Top 10 of my favorite memes. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;That's a capitalized Passion which is one degree above regular passion. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;I'm using Jujutsu, this command effectively merges changes in the latest commit (&lt;code&gt;master&lt;/code&gt; in my case) and then pushes it out. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;Long time ago, in Atari times, loading programs (and games) was done from cassette tapes. It took couple minutes and process was so temperamental that breathing or any movement in the room were strictly verboten. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;By "we" I mean "I". But what would be a drama without little dramatism? ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;It is possible to script &lt;code&gt;gh&lt;/code&gt; to do exactly that, but that means another piece of code I need to write ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>githubactions</category>
    </item>
    <item>
      <title>Build.rs-ing Documentation with Cuelang</title>
      <dc:creator>Alexander Przemysław Kamiński</dc:creator>
      <pubDate>Wed, 14 Jan 2026 09:06:45 +0000</pubDate>
      <link>https://dev.to/exlee/buildrs-ing-documentation-with-cuelang-3h2a</link>
      <guid>https://dev.to/exlee/buildrs-ing-documentation-with-cuelang-3h2a</guid>
      <description>&lt;p&gt;I have a little utility called &lt;a href="https://github.com/exlee/tmplr" rel="noopener noreferrer"&gt;tmplr&lt;/a&gt; which purpose is to generate files/file sets from a human-readable templates (check it out, it's neat!).&lt;/p&gt;

&lt;p&gt;It is at amazing version 0.0.9, and one thing that annoyed me every single time was the need of using version information in few places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HELP showed on &lt;code&gt;-h&lt;/code&gt; or &lt;code&gt;--help&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;README.md&lt;/code&gt; (that included copy of help text)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cargo.toml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Local git tag (git tag v0.0.9)&lt;/li&gt;
&lt;li&gt;Pushed tags it to the GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not &lt;em&gt;that bad&lt;/em&gt; but it's an overhead I wanted to eliminate. Since I'm a huge fan of &lt;a href="https://cuelang.org" rel="noopener noreferrer"&gt;CUE&lt;/a&gt; and I generate most of the configs/data I need with it I thought - heck, why not?&lt;/p&gt;

&lt;p&gt;Thankfully Rust provides ability to have a pre-build "scripts" (it's actually full Rust program) which allowed me to do not only version injection but also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate full &lt;code&gt;README.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Generate &lt;code&gt;CHANGELOG.md&lt;/code&gt; based on smarter entries&lt;/li&gt;
&lt;li&gt;Inject Rust code into &lt;code&gt;src/main.rs&lt;/code&gt; (that was really neat)&lt;/li&gt;
&lt;li&gt;Reuse part of the documentation in other places&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Long story short, let me show you my build.rs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;process&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Injects "HELP" into src/main.rs&lt;/span&gt;
    &lt;span class="nf"&gt;prepare_documentation_code&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;generate_doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"README.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"readme.full"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;generate_doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CHANGELOG.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"changelog.text"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cargo:rerun-if-changed=cue"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;generate_doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_in_manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;manifest_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CARGO_MANIFEST_DIR"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;readme_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;manifest_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_in_manifest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cue"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.args&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s"&gt;"export"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"-e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;eval_command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"--out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"./cue:documentation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nf"&gt;.output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to execute cue command"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.status&lt;/span&gt;&lt;span class="nf"&gt;.success&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;panic!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Cue {} generation failed:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;file_in_manifest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_utf8_lossy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;readme_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to write generated file"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;prepare_documentation_code&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;out_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var_os&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OUT_DIR"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;dest_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;out_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"generated_docs.rs"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cue"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.args&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s"&gt;"export"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"-e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"help.code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"--out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"./cue:documentation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nf"&gt;.output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to execute cue command"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.status&lt;/span&gt;&lt;span class="nf"&gt;.success&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;panic!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Cue generation failed:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_utf8_lossy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dest_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to write generated file"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Documentation &lt;em&gt;code&lt;/em&gt; is bit different because it's a source code that's going to be injected into main.rs using following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;include!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;concat!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;env!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OUT_DIR"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"/generated_docs.rs"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generated_docs is actual build artifact I don't want to submit to VCS thus I don't plainly generate it. Changelog and Readme is another story, this should be checked in, so I generate them direclty in MANIFEST_DIR (i.e. Project's root).&lt;/p&gt;

&lt;p&gt;I won't show my CUE files this time, as they're mostly filled with data and split across multiple files. Here's how my &lt;code&gt;cue/&lt;/code&gt; directory looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cue
├── changelog.cue
├── documentation.cue
├── help.cue
├── LICENSE.cue
└── readme.cue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;documentation.cue&lt;/code&gt; is a root package file - help, changelog and readme are files containing data (with some smart transformations)&lt;/p&gt;

&lt;p&gt;One piece you might be interested in is the final README.md outline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ...rest of file, sections etc.
full: """
  # tmplr
  \(sections.tmplr)
  ## Why tmplr?
  \(sections.why_tmplr)
  # Quick Start
  \(sections.quick_start)
  # CLI Help
  ‎`‎``
  \(help.text)
  `‎``
  # Installation
  \(sections.installation)
  # Usage (extended)
  ## .tmplr files
  \(sections.tmplr_files)
  ### Section Types
  \(sections.section_types)
  ### Magic Variables
  \(sections.magic_variables)
  ## CLI
  \(sections.cli)
  ## Templates directory
  \(sections.templates_directory)
  # TODO
  \(sections.todo)
  """
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you can clearly see that "CLI Help" section is reading directly from the &lt;code&gt;help.text&lt;/code&gt;. Neat, right? No more updating &lt;code&gt;README.md&lt;/code&gt; by hand, I just need to make sure I build before I push :)&lt;/p&gt;

&lt;p&gt;btw. final result is already pushed to the &lt;a href="https://github.com/exlee/tmplr" rel="noopener noreferrer"&gt;root repo&lt;/a&gt; where you can see the end result and full &lt;code&gt;.cue&lt;/code&gt; files for inspection. Project extension (see commit for full scope) took around 1.5h to develop, most of the time was spent porting the data from &lt;code&gt;.md&lt;/code&gt; to &lt;code&gt;.cue&lt;/code&gt; files&lt;/p&gt;

</description>
      <category>rust</category>
      <category>automation</category>
      <category>documentation</category>
      <category>cue</category>
    </item>
    <item>
      <title>CUE Does It All, But Can It Literate?</title>
      <dc:creator>Alexander Przemysław Kamiński</dc:creator>
      <pubDate>Tue, 13 Jan 2026 09:07:22 +0000</pubDate>
      <link>https://dev.to/exlee/cue-does-it-all-but-can-it-literate-5gmj</link>
      <guid>https://dev.to/exlee/cue-does-it-all-but-can-it-literate-5gmj</guid>
      <description>&lt;p&gt;&lt;code&gt;CUE&lt;/code&gt; is the Swiss Army knife of file generation. It is the tool you grab when you need to generate complex JSON, validate YAML, or generally stop configuration files from ruining your life. It slices, it dices, it ensures your integers are actually integers. But guess what else it can be? It turns out, it is also a surprisingly effective *&lt;strong&gt;&lt;em&gt;Literate Programming&lt;/em&gt;&lt;/strong&gt;* tool.&lt;/p&gt;

&lt;p&gt;This is important because, let’s be honest, the current king of this hill is &lt;code&gt;org-mode&lt;/code&gt;. And while &lt;code&gt;org-mode&lt;/code&gt; is powerful, it is also a bit of a golden cage. It works perfectly as long as you never leave the Emacs ecosystem. But the moment you try to export a workflow to a colleague who uses VS Code, you realize you have accidentally signed up for vendor lock-in. You want your documentation - your "literate code" - to be portable, not a magic spell that only works inside one specific editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Frankenstein" Problem
&lt;/h2&gt;

&lt;p&gt;When you write about code - whether it is a blog post, a tutorial, or internal docs - you rarely show one giant, boring file. That puts people to sleep. Instead, you break it down. You show a snippet of the logic. Then a snippet of the config. Then maybe a piece of the CSS. You stitch these disparate body parts together with prose.&lt;/p&gt;

&lt;p&gt;The problem is what I call the "Copy-Paste Tribute".&lt;/p&gt;

&lt;p&gt;Let's say you write a brilliant tutorial. You copy your working code into the Markdown file. Then, three days later, you realize you named a variable &lt;code&gt;t&lt;/code&gt; instead of &lt;code&gt;timeout&lt;/code&gt;. You fix it in the source code. But did you fix it in the Markdown file? Probably not. Now your readers are copying broken code, your tutorial is lying to them, and you look like you don't know what you are doing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CUE&lt;/code&gt; solves this by letting us define "parts" and then stitching them together programmatically. It does not just hold the text; it validates that the pieces actually fit. It ensures that the code in your explanation is the &lt;em&gt;exact same&lt;/em&gt; code in your final build. It is like having a Lego set where the bricks refuse to click if you are building something structurally unsound &lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basic Engine
&lt;/h2&gt;

&lt;p&gt;So, how do we stop paying this tribute? We treat our document like a build target.&lt;/p&gt;

&lt;p&gt;We can use &lt;code&gt;CUE&lt;/code&gt;'s &lt;code&gt;tool/file&lt;/code&gt; to create the document and &lt;code&gt;tool/exec&lt;/code&gt; to run commands. The beauty here is that we are not just templating strings; we are defining a dependency graph. If the dependencies (the code snippets) aren't valid, the document doesn't exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package example

import (
  "tool/file"
  "tool/exec"
)

// This is our "build" command. It tells CUE:
// "For every file defined in the 'files' struct, please create it"
command: example: {
  for k, v in files {
    "(k)": file.Create &amp;amp; {
      filename: k
      contents: v
    }
  }
}

// Imagine this is calling a script to get a version number.
// We aren't guessing the version; we are asking the shell for it.
data: exec.Run &amp;amp; {
  cmd: ["sh", "-c", "echo '1.2.3'"]
  stdout: string
}

// Our final document, stitched together.
// The '(data.stdout)' isn't just a placeholder; it's a promise
// that 'data' will run before this string is finalized.
files: {
  "output.md": """
My Awesome Project

Current version: (data.stdout)
"""
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a normal editor, that version number &lt;code&gt;1.2.3&lt;/code&gt; would just be static text I typed in while caffeinated. If the underlying logic changed to &lt;code&gt;1.2.4&lt;/code&gt;, the document would lie to the reader until I manually updated it. With &lt;code&gt;CUE&lt;/code&gt;, if &lt;code&gt;data.stdout&lt;/code&gt; fails to populate - say, if the shell command crashes - the file simply doesn't generate. The build breaks, protecting the user from bad info.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ignition Key: &lt;code&gt;cue cmd&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;There is a catch, though. If you just stare at the code above or run a standard &lt;code&gt;cue eval&lt;/code&gt;, nothing happens. &lt;code&gt;CUE&lt;/code&gt; is declarative by nature; it describes the &lt;em&gt;state&lt;/em&gt; of the world, it doesn't inherently &lt;em&gt;change&lt;/em&gt; it. Your &lt;code&gt;file.Create&lt;/code&gt; and &lt;code&gt;exec.Run&lt;/code&gt; blocks are just data sitting there, looking pretty.&lt;/p&gt;

&lt;p&gt;To actually turn that potential energy into kinetic energy, you need &lt;code&gt;cue cmd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cue cmd&lt;/code&gt; is the built-in task engine. It is the part of &lt;code&gt;CUE&lt;/code&gt; that is allowed to have side effects. It looks for those &lt;code&gt;command:&lt;/code&gt; blocks we defined earlier and executes the tasks inside them sequentially. It is the crucial bridge between "I want a file" (the definition) and "Here is your file" (the reality). Without invoking &lt;code&gt;cue cmd&lt;/code&gt;, your literate programming setup is just a very fancy, inert config file.&lt;/p&gt;

&lt;p&gt;Crucial Detail: There is a trap for the uninitiated here. For &lt;code&gt;cue cmd&lt;/code&gt; to actually pick up your file, the filename must end with &lt;code&gt;_tool.cue&lt;/code&gt; (e.g., &lt;code&gt;build_tool.cue&lt;/code&gt;). If you name it just &lt;code&gt;example.cue&lt;/code&gt;, &lt;code&gt;CUE&lt;/code&gt; will ignore your commands completely, and you will be left wondering why your genius code is doing absolutely nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Polyglot Pipeline
&lt;/h2&gt;

&lt;p&gt;Once you have the engine running, you might realize that simple shell commands aren't enough. &lt;code&gt;echo '1.2.3'&lt;/code&gt; is fine for toddlers, but what if you need to compile a Haskell module or render a diagram?&lt;/p&gt;

&lt;p&gt;The real magic happens when you realize &lt;code&gt;CUE&lt;/code&gt; is incredibly easy to extend. It isn't limited to its own syntax. You can define your own "renderers" using &lt;code&gt;tool/exec&lt;/code&gt;, essentially building a tiny, custom CI runner inside your config file.&lt;/p&gt;

&lt;p&gt;Here is how you can teach &lt;code&gt;CUE&lt;/code&gt; to speak multiple languages by wrapping standard CLI tools. Note the trick with the Haskell renderer: we aren't just passing raw code; we are wrapping it in a module structure so it compiles standalone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package example

import (
  "tool/exec"
)

// Define our "renderers" -- wrappers around CLI tools.
// Each renderer takes a 'code' string and pipes it into a command.

renderers: pikchr: exec.Run &amp;amp; {
  cmd: ["pikchr", "--svg-only", "-"]
  code:   string
  stdin:  code
  stdout: string
}

renderers: haskell: exec.Run &amp;amp; {
  code:   string
  stdout: string
  cmd: ["stack", "runghc"]
  // Here we wrap the snippet in a module so it compiles standalone
  stdin: """
module Program where
(code)
"""
}

renderers: bash: exec.Run &amp;amp; {
  code:   string
  stdout: string
  cmd: ["bash", "-e"]
  stdin: code
}

// Just for good measure, ZSH too.
// Why? Because sometimes Bash isn't hipster enough.
renderers: zsh: exec.Run &amp;amp; {
  code:   string
  stdout: string
  cmd: ["zsh", "-e"]
  stdin: code
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when I want to include a diagram or a code output, I just pass my snippet through the appropriate renderer. &lt;code&gt;CUE&lt;/code&gt; handles the heavy lifting of calling the shell, piping the input via &lt;code&gt;stdin&lt;/code&gt;, and capturing the &lt;code&gt;stdout&lt;/code&gt;. It turns your documentation into a living program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Narrative
&lt;/h2&gt;

&lt;p&gt;The best part? You can "test" your article. By running &lt;code&gt;cue cmd example&lt;/code&gt;, &lt;code&gt;CUE&lt;/code&gt; actually executes the shell commands, compiles the Haskell, renders the Pikchr diagram, and generates the final files.&lt;/p&gt;

&lt;p&gt;If your code snippet has a syntax error, the generation fails. If your Haskell code doesn't compile, the generation fails. If you try to create a Pikchr diagram with invalid coordinates, the generation fails. It turns "writing a blog post" into "passing a CI/CD pipeline".&lt;/p&gt;

&lt;p&gt;It is a bit like having a proofreader who is also a very strict compiler &lt;sup id="fnref2"&gt;2&lt;/sup&gt;. It ensures that every diagram in my post was actually rendered from the code right next to it. No stale images, no manual exports, and no "magic" steps I forgot to write down. It is independent of any specific editor ecosystem, effectively solving the "works on my machine" problem for documentation. You aren't just writing; you are engineering the text.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Or if you are trying to put a Megablok on a Lego. &lt;code&gt;CUE&lt;/code&gt; has high standards and will not hesitate to judge your poor structural choices. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;And unlike a human proofreader, &lt;code&gt;CUE&lt;/code&gt; doesn't ask for a salary,&lt;br&gt;
drink all your coffee, or complain about your excessive use of puns. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>cue</category>
      <category>literateprogramming</category>
      <category>programming</category>
    </item>
    <item>
      <title>RwLock HashMap Arc Mutex File</title>
      <dc:creator>Alexander Przemysław Kamiński</dc:creator>
      <pubDate>Sun, 04 Jan 2026 09:38:35 +0000</pubDate>
      <link>https://dev.to/exlee/rwlock-hashmap-string-36a1</link>
      <guid>https://dev.to/exlee/rwlock-hashmap-string-36a1</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PathBuf&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;fifos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;RwLock&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="c1"&gt;//                                ^                      ^   ^&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="c1"&gt;//          many fifos, but only  |    async ref counter |   | but only one will&lt;/span&gt;
 &lt;span class="c1"&gt;//           one active at a time |    because many file |   | be active at a time&lt;/span&gt;
 &lt;span class="c1"&gt;//                                |    refs could exist      &lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rust</category>
    </item>
    <item>
      <title>The Four-Language Waltz: A Tale of Allocators and Regret</title>
      <dc:creator>Alexander Przemysław Kamiński</dc:creator>
      <pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/exlee/the-four-language-waltz-a-tale-of-allocators-and-regret-7pp</link>
      <guid>https://dev.to/exlee/the-four-language-waltz-a-tale-of-allocators-and-regret-7pp</guid>
      <description>&lt;h2&gt;
  
  
  The Definition of Insanity
&lt;/h2&gt;

&lt;p&gt;They say the definition of insanity is doing the same thing over and over again and expecting different results. In software engineering, we call this "benchmarking." Or, in my specific case, "rewriting a template utility four times because I am spiritually incapable of settling."&lt;/p&gt;

&lt;p&gt;The goal was simple. Deceptively so. I wanted a tool called &lt;code&gt;tmplr&lt;/code&gt;. Its job? To take a template, sprinkle in some variables, and spit out a file. It is the sort of task that a shell script could do if you didn't value your sanity, or that Python could do if you didn't value your startup time.&lt;/p&gt;

&lt;p&gt;I wanted it to be like Haskell's Stack Templates—human-readable, elegant—but without the requirement that the output be a valid Cabal project. I wanted the freedom to template &lt;em&gt;anything&lt;/em&gt;. A grocery list. A love letter. A ransom note. The possibilities were endless.&lt;/p&gt;

&lt;p&gt;So, naturally, I decided to write it in every language I could get my hands on until one of them didn't make me cry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 1: Haskell (The Mathematical Elephant)
&lt;/h2&gt;

&lt;p&gt;I started with Haskell. It was a smooth ride. Haskell is like a very expensive, very comfortable armchair that you can never actually move out of the room.&lt;/p&gt;

&lt;p&gt;The code flowed. The types aligned like the stars in a favorable horoscope. But then came the compilation.&lt;/p&gt;

&lt;p&gt;I wanted this tool to be shareable. Portable. I wanted binaries. Haskell looked at me, adjusted its monocle, and handed me a 30MiB executable. Thirty. Megabytes. For a text replacement tool. That is not a utility; that is a piece of furniture.&lt;/p&gt;

&lt;p&gt;And cross-compilation? Forget it. I had a binary for Linux Debian. If you were on Windows or MacOS, you were essentially dead to me. Haskell is a language of pure thought, and apparently, pure thought is very heavy to carry around on a USB drive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 2: Go (The Beige Cardigan)
&lt;/h2&gt;

&lt;p&gt;I fled to Go. Go is the language you use when you want to get work done and you don't care if you feel anything while doing it.&lt;/p&gt;

&lt;p&gt;I had just written a stream sorter (&lt;code&gt;ssort&lt;/code&gt;) with the help of an LLM, and the experience was... fine. It was "boring productive."&lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;The binaries were slimmer. The cross-compilation actually worked.&lt;/p&gt;

&lt;p&gt;But I missed the types. Oh, how I missed them. Going from Haskell to Go is like going from playing 4D chess to playing Checkers with rocks. It works, but you spend a lot of time checking &lt;code&gt;if rock != nil&lt;/code&gt;. I yearned for structure. I yearned for the compiler to judge me more harshly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 3: Zig (The Icarus Maneuver)
&lt;/h2&gt;

&lt;p&gt;And so, I asked the oracle (the LLM) for a middle ground. It suggested Rust.&lt;/p&gt;

&lt;p&gt;Naturally, I ignored it and picked Zig.&lt;/p&gt;

&lt;p&gt;Zig is seductive. It promises you the control of C without the decades of psychological trauma. It promises binaries so small they could fit in a text message. For a while, it was paradise.&lt;/p&gt;

&lt;p&gt;Then I hit &lt;strong&gt;The Segmentation Function&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This was the core logic: taking the template, splitting it, managing the variables, and dealing with branching logic. It was a complex, 200-line beast. I wrestled it into submission. It worked!&lt;/p&gt;

&lt;p&gt;But then, the Debug Allocator cleared its throat.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Excuse me,”&lt;/em&gt; it whispered, &lt;em&gt;“but I believe you have leaked memory.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I had forgotten to release intermediate objects. I stared at the leak report. The leak report stared back. My head began to throb in that specific way that suggests your brain is trying to physically reject the concept of manual memory management.&lt;/p&gt;

&lt;p&gt;I looked at the code. I looked at the headache medicine. I chose life.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 4: Rust (The Strict Librarian)
&lt;/h2&gt;

&lt;p&gt;I crawled back to Rust.&lt;/p&gt;

&lt;p&gt;I don't love Rust. I don't wear the socks. I don't attend the crab-themed parties. But at that moment, I needed a babysitter. I needed a compiler that would grab me by the wrist and say, &lt;em&gt;“No, Alexander. You cannot drop that reference there. Pick it up.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I rewrote the Segmentation Function. It was still difficult. I still had logic errors. But here, the types and the robust testing framework acted as guard rails. When I made a mistake, the compiler didn't just segfault and laugh; it gave me an essay on why I was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;Is it the Holy Grail?&lt;/p&gt;

&lt;p&gt;No. The binary is 350KiB. I wanted under 100KiB. But compared to the 30MiB Haskell Leviathan, it is a feather.&lt;/p&gt;

&lt;p&gt;It is fast. It is correct. It runs on Linux. It does &lt;em&gt;not&lt;/em&gt; run on Windows, because including pathing libraries would add bloat, and I have principles&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;I have come to an understanding with Rust. I don't have to be a fanboy to appreciate that sometimes, it’s nice to have a tool that stops you from shooting your own foot off, even if it forces you to ask permission before loading the gun.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/exlee/tmplr" rel="noopener noreferrer"&gt;It works&lt;/a&gt;. And I am never rewriting it again. Probably.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;"Boring Productive" is the highest compliment you can pay a language in a corporate environment, and the worst insult you can pay it on a weekend. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Also, Windows paths use backslashes, which are clearly an abomination against god and nature. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>rust</category>
      <category>haskell</category>
      <category>go</category>
    </item>
  </channel>
</rss>
