<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Schneems - Programming Practices, Performance, and Pedantry</title>
  <subtitle>Ruby Hero. Runs CodeTriage.com. Works for Heroku. Posts on programming and open source.
</subtitle>
  <id>https://www.schneems.com/feed.xml</id>
  <link href="https://www.schneems.com/feed.xml" rel="self" type="application/atom+xml" />
  <updated>2026-03-03T13:28:27+00:00</updated>

  
    <author>
        <name>Richard Schneeman</name>
      
    </author>
  

  
    <entry>
      <id>https://www.schneems.com/2026/03/01/how-to-sustain-heroku</id>
      <title>How to &quot;Sustain Heroku&quot;</title>
      <content type="html" xml:base="https://www.schneems.com/2026/03/01/how-to-sustain-heroku/">
        <![CDATA[<p>This is a personal essay (I speak for me and my views, not for my employer) about what exactly a <a href="https://www.heroku.com/blog/an-update-on-heroku/">“Sustaining Engineering Model”</a> is, in the context of the recent Heroku announcement, and the book “The Innovator’s Dilemma,” as seen by someone who has worked at the company for the past 14 years.</p>

<p>I was listening to “The Nvidia Way” recently, as recommended by “Oxide and Friends” <a href="https://oxide-and-friends.transistor.fm/episodes/books-in-the-box-v">Books in the Box</a> episode. It mentions “The Innovator’s Dilemma” a LOT. So I picked up that audiobook too. I was surprised to find out that the forward (of this edition) of The Innovator’s Dilemma was written by Marc Benioff, CEO of Salesforce (which has owned and operated Heroku since 2011).</p>

<p>I’m still working through the book, but some of the terminology we’re now being thrust into seems to come from the book. A “sustaining” business. <a href="https://online.hbs.edu/blog/post/sustaining-vs-disruptive-innovation">https://online.hbs.edu/blog/post/sustaining-vs-disruptive-innovation</a>. In that context, “sustaining” isn’t a bad thing; it’s basically short for “predictable.” If you’re in the business of selling hammers, there are incremental improvements to materials or processes. You can find efficiency through forecasting and prior market knowledge. But it’s not a market where new upstarts are coming in with radically different things and taking over.</p>

<p>When I worked for GE as an intern in their Appliance Park (made refrigerators, etc.) there was an organizational strategy for delivering new products to market (such as French door refrigerators, or meeting new energy-star guidelines). It was called NPI (New Product Introduction), where those engineers got good at optimizing for speed of delivery to the market. And another org called PCTO (Product Cost Take Out), where they would take products already delivered and find ways to increase the margins on them (like using a cheaper compressor in exchange for more expensive insulation if that allowed you to still meet regulation and energy-star targets).</p>

<p>Even in such an old field as home appliances, there’s still work to be done. There are still competitive edges to be found and optimized. To me, that’s an example of a sustaining business.</p>

<p>No GE intern wanted to work in PCTO. It wasn’t “cool” or “glamorous.” But it’s the bulk of the work. It’s how the company stays competitive. It would have been “better” to “design it right the first time,” but that would lead to longer product introduction cycles, which not only means your competitors deliver before you, but it also means they’re learning what works and what doesn’t before you. The PCTO org brought value not just by having a cost-competitive product. It is also what enabled NPI to exist at all.</p>

<p>When I hear Heroku say it is moving to a <a href="https://www.heroku.com/blog/an-update-on-heroku/">“sustaining” engineering model</a>, it doesn’t mean features stop. Heck, the first commercial fridge was introduced in 1913, and we’re still finding ways to add bells and whistles, like the water pitcher in the door and quad-door design of my most recent fridge. But those innovations aren’t disruptive; they’re iterative and relatively predictable. Those innovations are only possible because <a href="https://en.wikipedia.org/wiki/Worse_is_better">worse is better</a>. i.e., GE figured out what mattered (shipping fast is more important than perfect), but it did it in a way that it didn’t stop there, once it’s shipped, it’s shipped again over and over until the kinks are worked out and the margins are competitive.</p>

<h2 id="sustaining-is-focus-and-predictable-growth">Sustaining is Focus and Predictable Growth</h2>

<p>In “The Innovator’s Dilemma,” a “sustaining” innovation would be increasing the density of iron on a disk platter to achieve incrementally more storage density or going from one spindle to two. A “disruptive” technology would be the digital camera. It originally produced worse images than film and was very expensive. Kodak didn’t invest in it because it didn’t give their current customers what they needed. By the time digital cameras disrupted the film camera industry, Kodak was too late to make a difference.</p>

<p>In the context of Heroku, a transition to a “sustaining engineering model” means (to me) we’ve got to examine our <a href="https://en.wikipedia.org/wiki/Sacred_cow_\(idiom\)">sacred cows</a> and focus on the most important pieces of the company. Some of this is a continuation of what we were already doing. There’s already been a push for toil reduction and increased automation. In my personal role, I want to move building Ruby binaries to be a completely automated process (right now it is semi-automated), with a balance between speed and automation, and security via checksum validation. This is an engineering investment in engineering. Spending hours now to save minutes for a thing that is predictable and recurring will not only free me up to work on other automations, but it will also reduce interruptions and toil. Previously, this was on the roadmap, but it’s been a lower priority. Other teams have other examples. For example, <a href="https://www.heroku.com/blog/introducing-the-next-generation-of-heroku-postgres/">Next Generation Postgres</a> is already in pilot and still moving ahead.</p>

<p>To me, “sustaining” means “focus.” Focus inward on what we’re doing today and what our customers need today. It might mean missing out on disruptive changes, but focusing on what the “next big thing” could be can cause you to miss out on the incremental progress improvements right in front of your nose.</p>

<p>More doesn’t always mean better. More processes, more ways to track work, and more inboxes to check all slow us down. Less doesn’t always mean worse. Sustaining isn’t just keeping lights on. It’s keeping your product and your customers nourished, sustained, not frozen in time or starved. When Heroku first came out, it was a mind-blowing, disruptive change from how people spun up servers before it. Sustaining means aligning on what not to do, beyond just what we would like to do. We can’t afford to be fat and lazy. We can’t afford to aim for 100% in 100% of everything we do. Frankly, that model wasn’t serving our customers very well.</p>]]>
      </content>
      
      <summary type="html">
        <![CDATA[This is a personal essay (I speak for me and my views, not for my employer) about what exactly a “Sustaining Engineering Model” is, in the context of the recent Heroku announcement, and the book “The Innovator’s Dilemma,” as seen by someone who has worked at the company for the past 14 years.]]>
      </summary>
      <updated>2026-03-01T00:00:00+00:00</updated>
      <link href="https://www.schneems.com/2026/03/01/how-to-sustain-heroku/" rel="alternate" type="text/html" title="How to "Sustain Heroku"" />
    </entry>
  
    <entry>
      <id>https://www.schneems.com/2025/12/19/non-violent-comments-calling-out-or-calling-in</id>
      <title>Non-Violent Comments: Calling out or Calling in?</title>
      <content type="html" xml:base="https://www.schneems.com/2025/12/19/non-violent-comments-calling-out-or-calling-in/">
        <![CDATA[<p>Now that programmers are at war with the robots (Gen AI) for our jobs, we need to lean into the things that they cannot do. Today, I’m going to be talking about how to be a human and communicate with other humans in the most hostile of scenarios, “in conflict (drama).”</p>

<p>TLDR: Be clear in your communications what you support and “whose side are you on.” There’s a bullet point list of suggestions at the bottom.</p>

<h2 id="knot-quite-right-help">Knot-quite-right help</h2>

<p>Before connecting my thoughts back to tech and to “drama”, I’m going to start with a made-up example: Imagine you’re at school and a classmate comes to you and says:</p>

<blockquote>
  <p>“Hey idiot, tie your shoes.”</p>
</blockquote>

<p>What do you do next? Do you say, “Hey, thanks! I appreciate you helping me not trip over myself,” or do you get defensive?</p>

<p>The schoolmate clearly led with an attack: “Hey, idiot.” So the rest of it: “tie your shoes” is likely to also be interpreted as an attack too. Maybe you guess they’ll say “made you look” and get the rest of your classmates to laugh too when you glance down at your shoes. Who knows. It’s not logical, it’s emotional.</p>

<p>Let’s break it down. In any “cause” or “drama,” there might be two sides, there’s at least three actors (bodies): Those involved, the speaker, and the listener.</p>

<p>In this case, the actors are:</p>

<ul>
  <li><strong>Those involved</strong>: You, with Schrodinger’s shoelaces</li>
  <li><strong>The speaker</strong>: Classmate</li>
  <li><strong>The listener</strong>: You || The rest of class</li>
</ul>

<blockquote>
  <p>Note: Here <code class="language-plaintext highlighter-rouge">||</code> is a logical “or”. So read this as “You or the rest of the class”.</p>
</blockquote>

<p>Every conflict has two sides. Here sides for/against are:</p>

<ul>
  <li><strong>For taking action</strong>: This person is looking out for me, and we have a shared goal</li>
  <li><strong>Against taking action</strong>: This person wants to make me look bad for some reason</li>
</ul>

<p>Now, instead, imagine they said, “Hey, I saw your shoes are untied. I don’t want you to trip over them, get hurt, and delay getting to lunch for everyone.” Do you think you would be more or less likely to check your shoes and take action?</p>

<p>The difference between the two approaches is: Calling out, versus calling in. In the second example, the classmate didn’t just raise awareness of the issue, but also worked to make it <strong>clear</strong> they were on the side of “not delaying lunch” (which falls on the side of “please tie your shoes”). They didn’t just point out a problem; they worked to ensure the listener could hear their message and take the desired action.</p>

<h2 id="comment-confusion">Comment confusion</h2>

<p>Recently, someone posted on Reddit, asking, “Why does tool X suck?” The actors involved are:</p>

<ul>
  <li><strong>Those involved</strong>: Authors of the tool || Users of the tool</li>
  <li><strong>The speaker</strong>: Person asking the question on Reddit</li>
  <li><strong>The listener</strong>: Users of the tool || Community at large || Authors of the tool</li>
</ul>

<p>It was a genuine question, and kicked off a great discussion. But, it was made without awareness that the authors are active in the sub-reddit and would 100% for sure see the post. Whether the poster meant it this is the conflict inherent in the question:</p>

<ul>
  <li><strong>For</strong>: The author should feel good about making something people use even if it has rough edges.</li>
  <li><strong>Against</strong>: The author is bad and should feel bad.</li>
</ul>

<p>If the intent of “Why does tool X suck?” was to say “I am mad at this tool and want to attack the authors,” then those would be good words to choose. If the intent is to understand the software, identify strengths and weaknesses, and possibly help the authors understand and make it better, the first step is not alienating them.</p>

<h2 id="three-body-problem">Three body problem</h2>

<p>Now that you understand the for/against and involved/speaker/listener frame, it’s going to get complicated by getting recursive. As everyone involved can switch perspectives based on the flow of the conversation. Like the real “three body problem,” this switching of places of involved/speaker/listener makes real-world conflicts chaotic and unpredictable.</p>

<p>Take the prior case of “why does tool X suck?” After the post is made, someone sees it they comment something like “hey, that’s a really crappy thing to say…” as the original person “calling out” the tool, becomes called out. My suggested fix would be to recursively apply “calling in,” including “calling in” about “calling out.”</p>

<p>It also gets tricky, as a post on Mastodon (or other social media platforms) is read by individuals as if it were written “to” them, but “them” might be a random bystander or the leader of an organization. A statement, “I hate this tool,” might be <strong>true</strong>, but it might not be <strong>helpful</strong> if it’s missing the context and qualifiers needed to let people know what exactly you <strong>meant</strong> by making that statement.</p>

<p>Humans are really good at pattern matching and applying meaning, even when none exists. Others will read your communications and implicitly try to assign an “us” or a “them” side, whether it was meant that way or not.</p>

<blockquote>
  <p>For more info on this topic I like <a href="https://hsc.unm.edu/medicine/education/leo/_media/quarterly-reports/leo-toolkit_calling-in-vs-calling-out.pdf">this PDF on “Calling In”</a>, starting on page 2 for more examples.</p>
</blockquote>

<h2 id="non-violent-comments">Non Violent Comments</h2>

<p>All of that is well and good. Here’s some other rapid-fire suggestions for being able to both hear and be heard:</p>

<ul>
  <li><strong>No dog piles, please</strong>: Admitting a weakness is being vulnerable, not an invitation to attack and pile on. Agree with it and help strengthen it. If someone states, “here’s my bias on this issue,” it doesn’t invalidate their words, but helps frame them with context.</li>
  <li><strong>Don’t accept framing at face value, don’t dismiss it either</strong>: When people speak, they do so with bias and agenda. There’s always at least 2 sides to any story and normally many more. It’s okay to explore and question alternative viewpoints and interpretations. But no one likes to exclusively talk to “devil’s advocates.” Also, make sure you’re not dismissing lived experiences. Another way to express this is “you can acknowledge you heard someone without agreeing with their words.”</li>
  <li><strong>Stay hydrated, but don’t be thirsty</strong>: Being “thirsty” is being desperate for attention. Sometimes a comment thread isn’t about you, or the other person just doesn’t want to talk about what you want to talk about. Try to make conversations give-and-take and not take-and-take-and-take.</li>
  <li><strong>Some people just want to chat</strong>: Not everyone has an agenda, or a fully resolved set of viewpoints. Many people are working through how they feel on things and they need a place to talk through it. If this is you, I suggest looking for smaller communities like a friend’s five-person discord instead of the open internet. Even if you’re just “asking questions,” it will look like you’re trying to frame things, and you’ll be pushed into a position. If you are genuinely asking questions, try to be sensitive of the optics and be willing to have people respond defensively.</li>
  <li><strong>Disagree productively</strong>: From the <a href="https://www.reddit.com/r/ruby/">r/ruby rules sidebar</a> here are some high level tips:
    <ul>
      <li>YES: Read comments fully before responding</li>
      <li>YES: Practice active listening. Let the other person know what you heard.</li>
      <li>YES: Distinguish acknowledgment from agreement.</li>
      <li>NO: Willful misrepresentation of someone’s stated position.</li>
      <li>NO: Sexualized language or imagery</li>
      <li>NO: Trolling, insulting or derogatory comments, and personal or political attacks.</li>
      <li>NO: Conduct which could reasonably be considered inappropriate in a professional setting.</li>
    </ul>
  </li>
  <li><strong>When in doubt, use Non-Violent Communication (NVC)</strong>: If you’re struggling to say what you have to say without others getting defensive check your communication has the following parts:
    <ul>
      <li><strong>Observation</strong>: What did you experience that led to the comment? This is where you build shared context</li>
      <li><strong>Emotion</strong>: How did that make you feel are you supportive or against that observation? Note that “I feel that you…” or “You are making me feel…” is not an emotion. It’s okay to be mad but also sad or frustrated or curious or a billion other characters that will be in inside out 3</li>
      <li><strong>Need (general)</strong>: Is it clear what you want from the world? What are your values? Do you care about justice or safety? Give the reader an anchor to know where you’re coming from</li>
      <li><strong>Request (to the reader)</strong>: Is it clear what you want from the person you’re speaking to? It could be “to hear me.” If it’s not clear, people often default to thinking you want them to change their position or opinion, which, if you didn’t know, is … not common on the internet, therefore people fight back. Also note that a request is not a demand.</li>
    </ul>
  </li>
</ul>

<p>The above NVC is reworded as a bit of a checklist of questions, because if you formulaically apply it like an SAT essay, then it will sound robotic and condescending. Also, a big part of the real NVC practice is working to actively defuse perceived slights and perceived attacks from the speaker. NVC is not a magic bullet, and people can use it to harm. But I like thinking of it as structural de-composition. Every communication you send has all four parts in it. If something you say is missing a part, you’re asking the reader to do that work for you and fill in the blank. Not only is this a bit rude, but there’s also no guarantee they’ll do it correctly.</p>

<p>This section got longer than I was anticipating, but please don’t call me out on it. Instead, call me in.</p>]]>
      </content>
      
      <summary type="html">
        <![CDATA[Now that programmers are at war with the robots (Gen AI) for our jobs, we need to lean into the things that they cannot do. Today, I’m going to be talking about how to be a human and communicate with other humans in the most hostile of scenarios, “in conflict (drama).”]]>
      </summary>
      <updated>2025-12-19T00:00:00+00:00</updated>
      <link href="https://www.schneems.com/2025/12/19/non-violent-comments-calling-out-or-calling-in/" rel="alternate" type="text/html" title="Non-Violent Comments: Calling out or Calling in?" />
    </entry>
  
    <entry>
      <id>https://www.schneems.com/2025/11/19/find-accidental-code-usage-with-a-custom-clippytoml</id>
      <title>Disallow code usage with a custom `clippy.toml`</title>
      <content type="html" xml:base="https://www.schneems.com/2025/11/19/find-accidental-code-usage-with-a-custom-clippytoml/">
        <![CDATA[<p>I recently discovered that adding a <code class="language-plaintext highlighter-rouge">clippy.toml</code> file to the root of a Rust project gives the ability to disallow a method or a type when running <code class="language-plaintext highlighter-rouge">cargo clippy</code>. This has been really useful. I want to share two quick ways that I’ve used it: Enhancing <code class="language-plaintext highlighter-rouge">std::fs</code> calls via <code class="language-plaintext highlighter-rouge">fs_err</code> and protecting CWD threadsafety in tests.</p>

<blockquote>
  <p>Update: you can also use this technique to <a href="https://blog.cloudflare.com/18-november-2025-outage/">disallow unwrap()</a>! There’s also <a href="https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used"><code class="language-plaintext highlighter-rouge">unwrap_used</code></a> which you use by adding <code class="language-plaintext highlighter-rouge">#![deny(clippy::unwrap_used)]</code> to your <code class="language-plaintext highlighter-rouge">main.rs</code>.</p>
</blockquote>

<h2 id="std-lib-enhancer">std lib enhancer</h2>

<p>I use the <a href="https://github.com/andrewhickman/fs-err">fs_err</a> crate in my projects, which provides the same filesystem API as <code class="language-plaintext highlighter-rouge">std::fs</code> but with one crucial difference: error messages it produces have the <strong>name</strong> of the file you’re trying to modify. Recently, while I was skimming the issues, someone mentioned <a href="https://github.com/andrewhickman/fs-err/issues/71">using clippy.toml to deny <code class="language-plaintext highlighter-rouge">std::fs</code> usage</a>. I thought the idea was neat, so I tried it in my projects, and it worked like a charm. With this in the <code class="language-plaintext highlighter-rouge">clippy.toml</code> file:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">disallowed-methods</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span>
    <span class="c"># Use fs_err functions, so the filename is available in the error message</span>
    <span class="p">{</span><span class="w"> </span><span class="n">path</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"std::fs::canonicalize"</span><span class="p">,</span><span class="w"> </span><span class="n">replacement</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"fs_err::canonicalize"</span><span class="w"> </span><span class="p">},</span>
    <span class="p">{</span><span class="w"> </span><span class="n">path</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"std::fs::copy"</span><span class="p">,</span><span class="w"> </span><span class="n">replacement</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"fs_err::copy"</span><span class="w"> </span><span class="p">},</span>
    <span class="p">{</span><span class="w"> </span><span class="n">path</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"std::fs::create_dir"</span><span class="p">,</span><span class="w"> </span><span class="n">replacement</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"fs_err::create_dir"</span><span class="w"> </span><span class="p">},</span>
    <span class="c"># ...</span>
<span class="p">]</span>
</code></pre></div></div>

<p>Someone running <code class="language-plaintext highlighter-rouge">cargo clippy</code> will get an error:</p>

<pre><code class="language-term">$ cargo clippy
    Checking jruby_executable v0.0.0 (/Users/rschneeman/Documents/projects/work/docker-heroku-ruby-builder/jruby_executable)
    Checking shared v0.0.0 (/Users/rschneeman/Documents/projects/work/docker-heroku-ruby-builder/shared)
warning: use of a disallowed method `std::fs::canonicalize`
   --&gt; ruby_executable/src/bin/ruby_build.rs:169:9
    |
169 |         std::fs::canonicalize(Path::new("."))?;
    |         ^^^^^^^^^^^^^^^^^^^^^ help: use: `fs_err::canonicalize`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#disallowed_methods
    = note: `#[warn(clippy::disallowed_methods)]` on by default
</code></pre>

<p>Running <code class="language-plaintext highlighter-rouge">cargo clippy --fix</code> will now automatically update the code. Neat!</p>

<h2 id="cwd-protector">CWD protector</h2>

<p>Why was I skimming issues in the first place? I <a href="https://github.com/andrewhickman/fs-err/issues/55">suggested adding a feature to allow enhancing errors with debugging information</a>, so instead of:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>failed to open file `file.txt`: The system cannot find the file specified. (os error 2)
</code></pre></div></div>

<p>The message could contain a lot more info:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>failed to open file `file.txt`: The system cannot find the file specified. (os error 2)

Path does not exist `file.txt`
- Absolute path `/path/to/dir/file.txt`
- Missing `file.txt` from parent directory:
  `/path/to/dir`
    └── `file.md`
    └── `different.txt`
</code></pre></div></div>

<p>To implement that functionality, I wrote <a href="https://github.com/schneems/path_facts">path_facts</a>, a library that provides facts about your filesystem (for debugging purposes). And since the core value of the library is around producing good-looking output, I wanted snapshot tests that covered all my main branches. This includes content from both relative and absolute paths. A naive implementation might look like this:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">temp</span> <span class="o">=</span> <span class="nn">tempfile</span><span class="p">::</span><span class="nf">tempdir</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="nn">std</span><span class="p">::</span><span class="nn">env</span><span class="p">::</span><span class="nf">set_current_dir</span><span class="p">(</span><span class="n">temp</span><span class="nf">.path</span><span class="p">())</span><span class="nf">.unwrap</span><span class="p">();</span> <span class="c1">// &lt;= Not thread safe</span>

<span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span><span class="nn">Path</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"exists.txt"</span><span class="p">),</span> <span class="s">""</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

<span class="nn">insta</span><span class="p">::</span><span class="nd">assert_snapshot!</span><span class="p">(</span>
    <span class="nn">PathFacts</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
        <span class="nf">.to_string</span><span class="p">()</span>
        <span class="nf">.replace</span><span class="p">(</span><span class="o">&amp;</span><span class="n">temp</span><span class="nf">.path</span><span class="p">()</span><span class="nf">.canonicalize</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">()</span><span class="nf">.display</span><span class="p">()</span><span class="nf">.to_string</span><span class="p">(),</span> <span class="s">"/path/to/directory"</span><span class="p">),</span>
    <span class="o">@</span><span class="s">r"
    exists `exists.txt`
     - Absolute: `/path/to/directory/exists.txt`
     - `/path/to/directory`
         └── `exists.txt` file [✅ read, ✅ write, ❌ execute]
    "</span><span class="p">)</span>
</code></pre></div></div>

<p>In the above code, the test changes the current working directory to a temp dir where it is then free to make modifications on disk. But, since Rust uses a multi-threaded test runner and <code class="language-plaintext highlighter-rouge">std::env::set_current_dir</code> affects the whole process, this approach is not safe ☠️.</p>

<p>There are a lot of different ways to approach the fix, like using <a href="https://nexte.st/">cargo-nextest</a>, which executes all tests in their own process (where changing the CWD is safe). Though this doesn’t prevent someone from running <code class="language-plaintext highlighter-rouge">cargo test</code> accidentally. There are other crates that use macros to force non-concurrent test execution, but they require you to <a href="https://crates.io/crates/serial_test">remember to tag the appropriate tests</a>. I wanted something lightweight that was hard to mess up, so I turned to <code class="language-plaintext highlighter-rouge">clippy.toml</code> to fail if anyone used <code class="language-plaintext highlighter-rouge">std::env::set_current_dir</code> for any reason:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">disallowed-methods</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span>
    <span class="p">{</span><span class="err">
</span><span class="w">        </span><span class="n">path</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"std::env::set_current_dir"</span><span class="p">,</span><span class="err">
</span><span class="w">        </span><span class="n">reason</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"Use `crate::test_help::SetCurrentDirTempSafe` to safely set the current directory for tests"</span><span class="err">
</span><span class="w">    </span><span class="p">},</span>
<span class="p">]</span>
</code></pre></div></div>

<p>Then I wrote a custom type that used a mutex to guarantee that only one test body was executing at a time:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span><span class="o">&lt;</span><span class="nv">'a</span><span class="o">&gt;</span> <span class="n">SetCurrentDirTempSafe</span><span class="o">&lt;</span><span class="nv">'a</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="c1">// let global_lock = ...</span>
        <span class="c1">// ...</span>

        <span class="nd">#[allow(clippy::disallowed_methods)]</span>
        <span class="nn">std</span><span class="p">::</span><span class="nn">env</span><span class="p">::</span><span class="nf">set_current_dir</span><span class="p">(</span><span class="n">tempdir</span><span class="nf">.path</span><span class="p">())</span><span class="nf">.unwrap</span><span class="p">();</span>
</code></pre></div></div>

<p>You might call my end solution hacky (this hedge statement brought to you by too many years of being ONLINE), but it prevents anyone (including future-me) from writing an accidentally thread-unsafe test:</p>

<pre><code class="language-term">$ cargo clippy --all-targets --all-features -- --deny warnings
    Checking path_facts v0.2.1 (/Users/rschneeman/Documents/projects/path_facts)
error: use of a disallowed method `std::env::set_current_dir`
   --&gt; src/path_facts.rs:395:9
    |
395 |         std::env::set_current_dir(temp.path()).unwrap();
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: Use `crate::test_help::SetCurrentDirTempSafe` to safely set the current directory for tests
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#disallowed_methods
    = note: `-D clippy::disallowed-methods` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
</code></pre>

<h2 id="clippytoml">clippy.toml</h2>

<p>Those are only two quick examples showing how to use <a href="https://doc.rust-lang.org/clippy/lint_configuration.html#lint-configuration-options">clippy.toml</a> to enhance a common API, and how to safeguard against incorrect usage. There’s plenty more you can do with that file, including:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">disallowed-macros</code></li>
  <li><code class="language-plaintext highlighter-rouge">disallowed-methods</code></li>
  <li><code class="language-plaintext highlighter-rouge">disallowed-names</code></li>
  <li><code class="language-plaintext highlighter-rouge">disallowed-types</code></li>
</ul>

<p>You wouldn’t want to use this technique of annotating your project with <code class="language-plaintext highlighter-rouge">clippy.toml</code> if the thing you’re trying to prevent would be actively malicious for the system if it executes, since <code class="language-plaintext highlighter-rouge">clippy.toml</code> rules won’t block your <code class="language-plaintext highlighter-rouge">cargo build</code>. You’ll also need to make sure to run <code class="language-plaintext highlighter-rouge">cargo clippy --all-targets</code> in your CI so some usage doesn’t accidentally slip through.</p>

<p>And that clippy lint work has paid off, <a href="https://github.com/andrewhickman/fs-err/pull/81">my latest PR to <code class="language-plaintext highlighter-rouge">fs_err</code></a> was merged and deployed in version <code class="language-plaintext highlighter-rouge">3.2.0</code>, and you can use it to speed up your development debugging by turning on the <code class="language-plaintext highlighter-rouge">debug</code> feature:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[</span><span class="n">dev-dependencies</span><span class="k">]</span>
<span class="n">fs-err</span> <span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="s">"debug"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span>
</code></pre></div></div>

<p>Clip cautiously, my friends.</p>]]>
      </content>
      
      <summary type="html">
        <![CDATA[I recently discovered that adding a clippy.toml file to the root of a Rust project gives the ability to disallow a method or a type when running cargo clippy. This has been really useful. I want to share two quick ways that I’ve used it: Enhancing std::fs calls via fs_err and protecting CWD threadsafety in tests.]]>
      </summary>
      <updated>2025-11-19T00:00:00+00:00</updated>
      <link href="https://www.schneems.com/2025/11/19/find-accidental-code-usage-with-a-custom-clippytoml/" rel="alternate" type="text/html" title="Disallow code usage with a custom `clippy.toml`" />
    </entry>
  
    <entry>
      <id>https://www.schneems.com/2025/11/05/upgrade-to-puma-7-and-unlock-the-power-of-fair-scheduled-keepalive</id>
      <title>Upgrade to Puma 7 and Unlock the Power of Fair Scheduled Keep-alive</title>
      <content type="html" xml:base="https://www.schneems.com/2025/11/05/upgrade-to-puma-7-and-unlock-the-power-of-fair-scheduled-keepalive/">
        <![CDATA[<p>Puma 7 is here, and that means your Ruby app is now keep-alive ready. This bug, which existed in Puma for years, caused one out of every 10 requests to take 10x longer by unfairly “cutting in line.”  In this post, I’ll cover how web servers work, what caused this bad behavior in Puma, and how it was fixed in Puma 7; specifically an architectural change recommended by MSP-Greg that was needed to address the issue.</p>

<p>Keep reading in the <a href="https://www.heroku.com/blog/upgrade-to-puma-7-and-unlock-the-power-of-fair-scheduled-keep-alive/">Heroku blog post</a>.</p>]]>
      </content>
      
      <summary type="html">
        <![CDATA[Puma 7 is here, and that means your Ruby app is now keep-alive ready. This bug, which existed in Puma for years, caused one out of every 10 requests to take 10x longer by unfairly “cutting in line.” In this post, I’ll cover how web servers work, what caused this bad behavior in Puma, and how it was fixed in Puma 7; specifically an architectural change recommended by MSP-Greg that was needed to address the issue.]]>
      </summary>
      <updated>2025-11-05T00:00:00+00:00</updated>
      <link href="https://www.schneems.com/2025/11/05/upgrade-to-puma-7-and-unlock-the-power-of-fair-scheduled-keepalive/" rel="alternate" type="text/html" title="Upgrade to Puma 7 and Unlock the Power of Fair Scheduled Keep-alive" />
    </entry>
  
    <entry>
      <id>https://www.schneems.com/2025/11/04/learn-how-to-lower-heroku-dyno-latency-through-persistent-connections-keepalive</id>
      <title>Learn How to Lower Heroku Dyno Latency through Persistent Connections (Keep-alive)</title>
      <content type="html" xml:base="https://www.schneems.com/2025/11/04/learn-how-to-lower-heroku-dyno-latency-through-persistent-connections-keepalive/">
        <![CDATA[<p>Before the latest improvements to the Heroku Router, every connection between the router and your application dyno risked incurring the latency penalty of a TCP slow start. To understand why this is a performance bottleneck for modern web applications, we must look at the fundamentals of the Transmission Control Protocol (TCP) and its history with HTTP.</p>

<p>Keep reading in the <a href="https://www.heroku.com/blog/learn-how-to-lower-heroku-dyno-latency-keep-alive/">heroku blog post</a>.</p>]]>
      </content>
      
      <summary type="html">
        <![CDATA[Before the latest improvements to the Heroku Router, every connection between the router and your application dyno risked incurring the latency penalty of a TCP slow start. To understand why this is a performance bottleneck for modern web applications, we must look at the fundamentals of the Transmission Control Protocol (TCP) and its history with HTTP.]]>
      </summary>
      <updated>2025-11-04T00:00:00+00:00</updated>
      <link href="https://www.schneems.com/2025/11/04/learn-how-to-lower-heroku-dyno-latency-through-persistent-connections-keepalive/" rel="alternate" type="text/html" title="Learn How to Lower Heroku Dyno Latency through Persistent Connections (Keep-alive)" />
    </entry>
  
    <entry>
      <id>https://www.schneems.com/2025/08/03/our-app-supports-markdown-and-other-lies-apps-tell</id>
      <title>&apos;Our app supports Markdown&apos; and other lies apps tell</title>
      <content type="html" xml:base="https://www.schneems.com/2025/08/03/our-app-supports-markdown-and-other-lies-apps-tell/">
        <![CDATA[<p>I’ll make this plain and simple: If your app does not let me read back, the character-for-character raw text I put into the editor, it does NOT support markdown.</p>

<p>Let’s set the scene: Our protagonist is burning the midnight oil. They’ve got a great idea for an open source RFC, but they want to do some due-diligence first with some co-workers so they open up their employee encouraged document sharing solution, Slack Canvas and write a beautiful bit of carefully annotated markdown. It looks … mostly right (why on earth do H3s look nearly identical to H2??) but it’s good enough. They got some great feedback, made some edits and now it’s time to go contribute to the great commons we call open source. They select all (CMD+a), they copy the text (CMD+c), they paste it into GitHub and hit enter. Slowly the blood drains from their face. It’s all gone. All the backticks, all the bolds, all the links. It kinda looks like the original document, but is quietly, subtly, horrifically mangled.</p>

<p>In desperation, they reach first for the <code class="language-plaintext highlighter-rouge">Edit</code> menu, then <code class="language-plaintext highlighter-rouge">File</code>. Flashes of the crappy <a href="https://quip.com/">https://quip.com/</a> markdown export feature flash through their memory. It had it’s own problems, but at least it was there. Silence covers the room. An almost imperceptible evil laugh of Google doc’s bastardized markdown support can be heard of the dawning realization: the app takes markdown synatax in. But it devours it. Destroys it. Vomiting rich text in in the place of where carefully currated backticks and astericks once shone brightly. The document is dead. Our hero weeps.</p>

<p>The whole point of markdown (as witnessed and confirmed in my headcannon as a developer since 2006) is that it provides a format that can be consumed as EITHER rendered text, OR as plain text. Markdown is not a format for styling a text document, it is a format that requires that the writer, can read the original text (not literally, you pedants, there’s not exactly a spec that says that, but perhaps there should be). Without this guarantee we get a sea of “almost markdown” formats. We are mark-drowning in them. They all behave slightly differently. For apps that do a decently good job of letting developers “read your write” there’s product-driven feature’s like Notion’s desire to embed rich objects such as spreadsheets and to add custom inter-notion document linking syntax that encourage users to subtly poison the portability of their original text.</p>

<p>I don’t begrudge them extending the syntax to meet their needs, but wish an export story was better thought out. Perhaps rich objects should be supported via iframes and exporting documents to markdown should support some kind of a “deep” export (i.e. it doesn’t matter if the markdown it exports is perfect, if it links to a bunch of gated and internal documents. Such thoughts probably get your cyber-security sense tingling (as they should) and likely bore investors and product managers to tears, therefore it’s a usecase that is sorely neglected given the millions/billions of dollors of “innovation” in the “markdown as a shared mangled RTF doc” apps that we’re collectively burried under.</p>

<p>So forget “hard mode” all I’m asking for is simple. If you take markdown in, you should allow me to take my original markdown out <strong>CHARACTER FOR CHARACTER</strong>. So far the best tools I’ve found for this are: Vim, GitHub, and <a href="https://obsidian.md/">Obsidian</a>. (Yes, your favorite IDE too). These are all hacker tools, but why can’t we live in a world where ALL tools respect our input enough to let us read it back? Perhaps you’ve been toiling like our hero. These document inflicted wounds aren’t large, but they’re never-ending. They create toil, and burn trust in tools. Perhaps like me, you’ve not had the words to enumerate what exactly is missing. Hopefully, now you do. If a company asks for some feedback, please let them know that to “read my markdown write” is a fundamental and inalienable right.</p>

<h2 id="special-bonus-rant-please-add-a-newline-after-your-markdown-headers">Special bonus rant: Please add a newline after your markdown headers</h2>

<p>Oh and while I’m ranting. I beg of you, please (please, please, please, please) add a newline after markdown headers. I.e. Don’t do this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## I really don't like when
People do this. Hard to read.
</code></pre></div></div>

<p>Instead, please do this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## Everyone who does this is amazing

They are the coolest people I know.
</code></pre></div></div>

<p>Why? Remember when I said markdown was supposed to optimize for rendering and plain text? When someone is reading your plain text, the first example without the vertical whitespace provides no visual pause. It’s notexactlythesamethingasreadingasentencewithnospaces. But you get how important white space can be when it comes to reading and comprehension speed. Yes, your favorite markdown (and markdown-ish) tools will render both of them the same, but to someone not viewing those docs in that same tool, they will appreciate you and star your github repos more and other vauge promises of things developers presumably want.</p>

<p>For some reason, I don’t entirely comprehend, the first style is much more popular on GitHub. To the point that the vast majority of LLM produced markdown does not have vertical whitespace after headers. We’re in a world now where “do what I do” (as opposed to do as I say) is fed back to us one token at a time. We might be past the point of no-return when it comes self-reinforcing behavior (as AI is now trained on the output that developers are committing as their own), but when skynet takes over and you’re looking for a shibboleth to prove you’re not a robot with an Austrian accent, you might remember this post and send a PR with some glorious, human, hand-crafted markdown.</p>]]>
      </content>
      
      <summary type="html">
        <![CDATA[I’ll make this plain and simple: If your app does not let me read back, the character-for-character raw text I put into the editor, it does NOT support markdown.]]>
      </summary>
      <updated>2025-08-03T00:00:00+00:00</updated>
      <link href="https://www.schneems.com/2025/08/03/our-app-supports-markdown-and-other-lies-apps-tell/" rel="alternate" type="text/html" title="'Our app supports Markdown' and other lies apps tell" />
    </entry>
  
    <entry>
      <id>https://www.schneems.com/2025/06/03/dont-mcblock-me</id>
      <title>Don&apos;t McBlock me</title>
      <content type="html" xml:base="https://www.schneems.com/2025/06/03/dont-mcblock-me/">
        <![CDATA[<p>“That cannot be done.” Is rarely true, but it’s a phrase I’ve heard more and more from technical people without offering any rationale or further explanation. This tendency to use absolute language when making blocking statements reminded me of a useful “McDonald’s rule” that I was introduced to many years ago when deciding where to eat with friends. It goes something like this:</p>

<p>If I say to a friend, “I’m hungry, let’s go to McDonald’s” (or wherever), they’re not allowed to block me without making a counter-suggestion. They can’t just say “No,” they have to say something like “How about Arby’s” instead. This simple rule changes the dynamic of the suggester/blocker to one of the proposer/counter-proposer. If someone is simply refusing to be involved, they McBlocked me.</p>

<p>In practice, though, it’s hard to always have a suggestion you’re willing to run with, so a relaxed version of the rule is that the other person has to AT LEAST specify why not. Instead of “no” it must be “no, because”. For example, it could be “I had a burger for lunch” or “I’m banned for life after jumping on a table and demanding Szechuan dipping sauce.” This helps show that you’re not just blocking things, you understand the goal and want to move the conversation forward. It gives the other person something to work with. Easy for eats, but what about tech?</p>

<p>I work for Heroku, and recently, there was a <a href="https://devcenter.heroku.com/changelog-items/3231">stack EOL</a> where customers were asked to migrate off of Ubuntu 20.04 (heroku-20). In this (many-month-long) deprecation process, I saw a lot of people make a lot of absolute statements. One of them was:</p>

<blockquote>
  <p>“You cannot run Rails 4 on heroku-22.”</p>
</blockquote>

<p>Which, as you’ll guess, is only half the story. What they meant was:</p>

<blockquote>
  <p>“Rails 4.2 saw its <a href="https://rubygems.org/gems/rails/versions/4.2.11.3">last release in 2020</a> and is quite thoroughly EOL. That version cannot run on any Ruby version 3.1. x- 3.4. x, which are present on heroku-22 or above, due to library errors. Therefore, to run Rails 4 on heroku-22, you would have to fork it and patch the security vulnerabilities yourself and update it to run on a modern Ruby version.”</p>
</blockquote>

<p>Which, to be fair, sounds a lot like “cannot be done,” but with more words. But, as you’ll also have likely guessed, once you know about the possible path forwards, however impractical, it might give you other ideas.</p>

<p>You might start asking questions like “if we have to fork and maintain it, anyone else would have to also, I wonder if someone else already did.” This could send you down a quick search where you might discover that <a href="https://railslts.com/en">Rails LTS</a> is a thing and basically provides a managed fork of Rails 4.2 for a fee that runs with the latest Ruby versions.</p>

<p>I wrote about the existence of this service previously:</p>

<ul>
  <li><a href="https://www.heroku.com/blog/migrating-ruby-apps-latest-stack/">Heroku blog: Migrating Your Ruby Apps to the Latest Stack</a></li>
  <li><a href="https://www.reddit.com/r/Heroku/comments/1ij7b89/upgrading_ruby_versions_to_run_on_heroku24/">Reddit thread: On using an old Ruby version on a newer stack</a></li>
</ul>

<p>Now, that new thing could still be a bad idea, and you might still not end up doing it, but the key here is that you’re not saying “no,” you’re saying “here are the barriers I know about.” A good way to test if you’re just using more words to say “no” or not is if your statement is falsifiable or satisfiable in some way.</p>

<p>A “no, because” statement instead of a plain “no” moves the problem from a blocker into an opportunity. You can see this in a really good open source conversation. Instead of “this can’t be done,” someone can send a PR. Instead of “I won’t merge your PR” they can comment: “I agree/disagree with the problem/opportunity you’ve raised, I’m uncomfortable merging this because of <code class="language-plaintext highlighter-rouge">&lt;specific reason&gt;</code>.”</p>

<p>A quick story, and you can go. Before writing this post, I pitched the word-smithing of “McBlocker” to my wife at the dinner table (where you can tell we are very cool and fun people). My kids, age 7 and 9, were there. My 9 y/o asked me to take him to the library after dinner (did I mention how cool we are?), where I was talking to him about types of non-fiction that he might like. I was talking about biographies when he blurted out, “I don’t like biographies.” To which I responded, “Hey, don’t McBlock me,” and when I got a laugh of recognition in return, I figured the phrase was worth a blog post.</p>

<hr />
<p>If you enjoyed this, you might enjoy my <a href="https://www.codetriage.com/">service for helping people contribute to open source</a> (free) or my book <a href="https://howtoopensource.dev/">How to Open Source</a> (paid). Now, go McRepost this to your favorite federated social network!</p>]]>
      </content>
      
      <summary type="html">
        <![CDATA[“That cannot be done.” Is rarely true, but it’s a phrase I’ve heard more and more from technical people without offering any rationale or further explanation. This tendency to use absolute language when making blocking statements reminded me of a useful “McDonald’s rule” that I was introduced to many years ago when deciding where to eat with friends. It goes something like this:]]>
      </summary>
      <updated>2025-06-03T00:00:00+00:00</updated>
      <link href="https://www.schneems.com/2025/06/03/dont-mcblock-me/" rel="alternate" type="text/html" title="Don't McBlock me" />
    </entry>
  
    <entry>
      <id>https://www.schneems.com/2025/05/07/bad-type-patterns-the-duplicate-duck</id>
      <title>Bad Type Patterns - The Duplicate duck</title>
      <content type="html" xml:base="https://www.schneems.com/2025/05/07/bad-type-patterns-the-duplicate-duck/">
        <![CDATA[<p>Why aren’t people <a href="https://lobste.rs/s/qmmfje/don_t_be_afraid_types">writing more types</a>? Perhaps it’s because the intermediate and expert developers deleted the patterns that didn’t work and left no trace for beginners to learn from. This post details some code I recently deleted that has a pattern I call the “duplicate duck.” You can learn the process I used to develop the type, and why I deleted it. Further, I advocate for Rust developers to document and share their mistakes in the hope that we can all learn from them.</p>

<h2 id="tldr-whats-a-duplicate-duck">TLDR: What’s a duplicate duck?</h2>

<p>A “duplicate duck” is a type that implements a subset of traits of a popular type with the same results. In my case I wrote a type, <code class="language-plaintext highlighter-rouge">MultiError</code>, that I later realized was identically <a href="https://en.wikipedia.org/wiki/Duck_typing">duck typed</a>  to <a href="https://docs.rs/syn/latest/syn/struct.Error.html"><code class="language-plaintext highlighter-rouge">syn::Error</code></a> and that my struct added nothing. I deleted my type with no loss in functionality and the world was better for it.</p>

<p>I saved my code before throwing it away. The following is the story of my design process and eventual epiphany.</p>

<blockquote>
  <p>Quick <code class="language-plaintext highlighter-rouge">whoami</code>: I write Rust for Heroku where I <a href="https://github.com/heroku/buildpacks/blob/main/docs/ruby/README.md">maintain the Ruby Cloud Native Buildpack</a>. I also maintain a free service <a href="https://www.codetriage.com">CodeTriage</a> and wrote a book, <a href="https://howtoopensource.dev">How to Open Source</a>, for turning coders into contributors.</p>
</blockquote>

<h2 id="story-version---context">Story version - Context</h2>

<p>I’ve been hacking on proc macros recently, you can read about a recent investigation <a href="https://www.schneems.com/2025/03/26/a-daft-procmacro-trick-how-to-emit-partialcode-errors/">“A Daft proc-macro trick: How to Emit Partial-Code + Errors”</a>. I want proc macro authors to emit as many accumulated errors as possible (versus stopping on the first one), I’m also a fan of unit testing. I wanted to add a return type from my functions that said, “I return many accumulated errors,” and I wanted that return type to be unit-testable.</p>

<p>In my code, I’ve been accumulating errors with <code class="language-plaintext highlighter-rouge">VecDeque&lt;syn::Error&gt;</code>. This makes it easy to combine them into a single <code class="language-plaintext highlighter-rouge">syn::Error</code> :</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="k">mut</span> <span class="n">error</span><span class="p">)</span> <span class="o">=</span> <span class="n">errors</span><span class="nf">.pop_front</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">e</span> <span class="k">in</span> <span class="n">errors</span> <span class="p">{</span>
        <span class="n">error</span><span class="nf">.combine</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="nf">Some</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="nb">None</span>
<span class="p">}</span>
</code></pre></div></div>

<p>However, I don’t want to return a result of <code class="language-plaintext highlighter-rouge">Result&lt;T, VecDeque&lt;syn::Error&gt;&gt;</code> from my functions as the error state isn’t guaranteed to be non-empty. A good type should make invalid state impossible to represent.</p>
<h2 id="start-with-the-data">Start with the data</h2>

<p>To guarantee my type always had at least one error, I separated out the first error from the rest of the collection. Even if this container is empty, the type definition guarantees we can always turn this into a <code class="language-plaintext highlighter-rouge">syn::Error</code></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="cd">/// Guaranteed to hold at least one [`syn::Error`]</span>
<span class="cd">///</span>
<span class="cd">/// The [`syn::Error`] can hold multiple errors</span>
<span class="cd">/// through [`syn::Error::combine()`], however it</span>
<span class="cd">/// does not allow the receiver to distinguish</span>
<span class="cd">/// between the two cases, which makes testing</span>
<span class="cd">/// less precise. Using this type is a stronger</span>
<span class="cd">/// hint that the function accumulates errors.</span>
<span class="cd">///</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Clone)]</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">MultiError</span> <span class="p">{</span>
    <span class="n">first</span><span class="p">:</span> <span class="nn">syn</span><span class="p">::</span><span class="n">Error</span><span class="p">,</span>
    <span class="n">rest</span><span class="p">:</span> <span class="n">VecDeque</span><span class="o">&lt;</span><span class="nn">syn</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">MultiError</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">from</span><span class="p">(</span><span class="k">mut</span> <span class="n">errors</span><span class="p">:</span> <span class="n">VecDeque</span><span class="o">&lt;</span><span class="nn">syn</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">first</span><span class="p">)</span> <span class="o">=</span> <span class="n">errors</span><span class="nf">.pop_front</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">rest</span> <span class="o">=</span> <span class="n">errors</span><span class="p">;</span>
            <span class="nf">Some</span><span class="p">(</span><span class="k">Self</span> <span class="p">{</span> <span class="n">first</span><span class="p">,</span> <span class="n">rest</span> <span class="p">})</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nb">None</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>Warning: Just because the docs state something, doesn’t mean it’s true.</p>
</blockquote>

<blockquote>
  <p>Note the visibility, by default I use <code class="language-plaintext highlighter-rouge">pub(crate)</code> for the struct and associated functions but not for the fields (<code class="language-plaintext highlighter-rouge">first</code> and <code class="language-plaintext highlighter-rouge">rest</code>). When I’m unsure of my design, it’s easier to change them later if all access goes through functions.</p>
</blockquote>

<p>This type allowed me to introduce helper functions like this:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="n">parse_attrs</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span>
        <span class="n">attrs</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nn">syn</span><span class="p">::</span><span class="n">Attribute</span><span class="p">]</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">MultiError</span><span class="o">&gt;</span>
<span class="k">where</span>
    <span class="n">T</span><span class="p">:</span> <span class="nn">syn</span><span class="p">::</span><span class="nn">parse</span><span class="p">::</span><span class="n">Parse</span><span class="p">,</span>
<span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">errors</span> <span class="o">=</span> <span class="nn">VecDeque</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="c1">// ...</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="o">=</span> <span class="nn">MultiError</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="n">errors</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// &lt;== HERE</span>
        <span class="nf">Err</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nf">Ok</span><span class="p">(</span>
        <span class="c1">// ...</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This code says “I take in any slice of <code class="language-plaintext highlighter-rouge">syn::Attribute</code> and then parse that attribute into a vector of <code class="language-plaintext highlighter-rouge">T</code> or return one or more syn errors”. So far, so good.</p>

<p>But my macro needs a <code class="language-plaintext highlighter-rouge">syn::Error</code> to generate error tokens and my function returns a <code class="language-plaintext highlighter-rouge">MultiError</code>. So I needed a way to convert my type into a <code class="language-plaintext highlighter-rouge">syn::Error</code>.</p>

<h2 id="add-into-behavior">Add into behavior</h2>

<p>Based on the properties of the type, we know we can always convert into a <code class="language-plaintext highlighter-rouge">syn::Error</code> infallibly, so I can expose that via implementing <code class="language-plaintext highlighter-rouge">Into&lt;syn::Error&gt;</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="nb">From</span><span class="o">&lt;</span><span class="n">MultiError</span><span class="o">&gt;</span> <span class="k">for</span> <span class="nn">syn</span><span class="p">::</span><span class="n">Error</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">from</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="n">MultiError</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">MultiError</span> <span class="p">{</span> <span class="k">mut</span> <span class="n">first</span><span class="p">,</span> <span class="n">rest</span> <span class="p">}</span> <span class="o">=</span> <span class="n">value</span><span class="p">;</span>
        <span class="k">for</span> <span class="n">e</span> <span class="k">in</span> <span class="n">rest</span> <span class="p">{</span>
            <span class="n">first</span><span class="nf">.combine</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">first</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As a bonus, the try operator (<code class="language-plaintext highlighter-rouge">?</code>) will implicitly call <code class="language-plaintext highlighter-rouge">into()</code> which allows us to do things like this:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">check_logic</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nn">syn</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="c1">// ...</span>
  <span class="k">let</span> <span class="n">result</span><span class="p">:</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="n">MultiError</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nf">logic</span><span class="p">();</span>
  <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="n">result</span><span class="o">?</span><span class="p">;</span> <span class="c1">// &lt;=== Convert MultiError to syn::Error implicitly</span>
  <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With that added, I needed a way to test my logic to ensure I was capturing multiple errors.</p>

<h2 id="add-display">Add Display</h2>

<p>To render the error on failure it needs to implement <code class="language-plaintext highlighter-rouge">std::fmt::Display</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fmt</span><span class="p">::</span><span class="n">Display</span> <span class="k">for</span> <span class="n">MultiError</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">fmt</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">f</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fmt</span><span class="p">::</span><span class="n">Formatter</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fmt</span><span class="p">::</span><span class="nb">Result</span> <span class="p">{</span>
        <span class="nn">Into</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">syn</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">into</span><span class="p">(</span><span class="k">self</span><span class="nf">.clone</span><span class="p">())</span><span class="nf">.fmt</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It’s not pretty, but it worked and was easy. This code path is only ever called under test.</p>
<h2 id="add-iteration">Add iteration</h2>

<p>To expose multiple errors for testing, I chose to implement the <code class="language-plaintext highlighter-rouge">IntoIterator</code> trait:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="nb">IntoIterator</span> <span class="k">for</span> <span class="n">MultiError</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">Item</span> <span class="o">=</span> <span class="nn">syn</span><span class="p">::</span><span class="n">Error</span><span class="p">;</span>
    <span class="k">type</span> <span class="n">IntoIter</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">VecDeque</span><span class="o">&lt;</span><span class="nn">syn</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="k">as</span> <span class="nb">IntoIterator</span><span class="o">&gt;</span><span class="p">::</span><span class="n">IntoIter</span><span class="p">;</span>

    <span class="k">fn</span> <span class="nf">into_iter</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span><span class="p">::</span><span class="n">IntoIter</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">MultiError</span> <span class="p">{</span> <span class="n">first</span><span class="p">,</span> <span class="k">mut</span> <span class="n">rest</span> <span class="p">}</span> <span class="o">=</span> <span class="k">self</span><span class="p">;</span>
        <span class="n">rest</span><span class="nf">.push_front</span><span class="p">(</span><span class="n">first</span><span class="p">);</span>
        <span class="n">rest</span><span class="nf">.into_iter</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This code says that we can now convert our struct into something that produces a series of <code class="language-plaintext highlighter-rouge">syn::Error</code>-s. Since we’ve already got a <code class="language-plaintext highlighter-rouge">VecDeque</code> lying around, and I knew that it implemented the same trait, I piggybacked my logic on top. This allowed me to do things like this test:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">test_captures_many_field_errors</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">field</span><span class="p">:</span> <span class="nn">syn</span><span class="p">::</span><span class="n">Field</span> <span class="o">=</span> <span class="nn">syn</span><span class="p">::</span><span class="nd">parse_quote!</span> <span class="p">{</span>
            <span class="nd">#[cache_diff(unknown)]</span>
            <span class="nd">#[cache_diff(unknown)]</span>
            <span class="n">version</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
        <span class="p">};</span>
        <span class="k">let</span> <span class="n">result</span><span class="p">:</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">ParseAttribute</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">MultiError</span><span class="o">&gt;</span> <span class="o">=</span>
            <span class="k">crate</span><span class="p">::</span><span class="nn">shared</span><span class="p">::</span><span class="nn">parse_attrs</span><span class="p">::</span><span class="o">&lt;</span><span class="n">ParseAttribute</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">field</span><span class="py">.attrs</span><span class="p">);</span>

        <span class="nd">assert!</span><span class="p">(</span>
            <span class="n">result</span><span class="nf">.is_err</span><span class="p">(),</span>
            <span class="s">"Expected {result:?} to be err but it is not"</span>
        <span class="p">);</span>
        <span class="k">let</span> <span class="n">error</span> <span class="o">=</span> <span class="n">result</span><span class="nf">.err</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="n">error</span><span class="nf">.into_iter</span><span class="p">()</span><span class="nf">.count</span><span class="p">());</span> <span class="c1">// &lt;== into_iter() HERE</span>
    <span class="p">}</span>

    <span class="k">enum</span> <span class="n">ParseAttribute</span> <span class="p">{</span>
        <span class="c1">//...</span>
    <span class="p">}</span>
    <span class="k">impl</span> <span class="nn">syn</span><span class="p">::</span><span class="nn">parse</span><span class="p">::</span><span class="n">Parse</span> <span class="k">for</span> <span class="n">ParseAttribute</span> <span class="p">{</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>This code parses a single field with multiple <code class="language-plaintext highlighter-rouge">syn::Attribute</code>-s on it. In this case, <code class="language-plaintext highlighter-rouge">cache_diff(unknown)</code> is an invalid attribute, and I want to assert that it does not stop after the first one it sees. The code converts my result into an iterator and then asserts that there are two elements. Great!</p>

<h2 id="iter-oops">Iter Oops</h2>

<p>While the above code example worked fine, I kept applying this pattern, bubbling up errors until I hit a failure in my code:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">test_captures_many_field_errors</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="nn">ParseContainer</span><span class="p">::</span><span class="nf">from_derive_input</span><span class="p">(</span><span class="o">&amp;</span><span class="nn">syn</span><span class="p">::</span><span class="nd">parse_quote!</span> <span class="p">{</span>
            <span class="k">struct</span> <span class="n">Metadata</span> <span class="p">{</span>
                <span class="nd">#[cache_diff(unknown)]</span>
                <span class="nd">#[cache_diff(unknown)]</span>
                <span class="n">version</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>

                <span class="nd">#[cache_diff(unknown)]</span>
                <span class="nd">#[cache_diff(unknown)]</span>
                <span class="n">name</span><span class="p">:</span> <span class="nb">String</span>
            <span class="p">}</span>
        <span class="p">});</span>

        <span class="nd">assert!</span><span class="p">(</span>
            <span class="n">result</span><span class="nf">.is_err</span><span class="p">(),</span>
            <span class="s">"Expected {result:?} to be err but it is not"</span>
        <span class="p">);</span>
        <span class="k">let</span> <span class="n">error</span> <span class="o">=</span> <span class="n">result</span><span class="nf">.err</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="n">error</span><span class="nf">.into_iter</span><span class="p">()</span><span class="nf">.count</span><span class="p">());</span> <span class="c1">// &lt;== FAILED here</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>The error said that I was returning only two errors instead of 4. Which was confusing. I moved the code into a <code class="language-plaintext highlighter-rouge">trybuild</code> integration test and saw 4 errors. At this point it dawned on me, that at some time I was storing multiple errors into a single <code class="language-plaintext highlighter-rouge">syn::Error</code> and then placing that combined error in my <code class="language-plaintext highlighter-rouge">MultiError</code>. Basically I had a multi <code class="language-plaintext highlighter-rouge">MultiError</code>.</p>

<p>If that was hard to follow, here’s some pseudo code:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="k">mut</span> <span class="n">errors</span><span class="p">:</span> <span class="n">VecDeque</span><span class="o">&lt;</span><span class="nn">syn</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nn">VecDeque</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

<span class="k">match</span> <span class="nf">call_fun</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// Returns a MultiError</span>
    <span class="nf">Ok</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="nd">todo!</span><span class="p">(),</span>
    <span class="c1">// Combines it into a single `syn::Error`</span>
    <span class="nf">Error</span><span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">errors</span><span class="nf">.push_back</span><span class="p">(</span><span class="n">error</span><span class="nf">.into</span><span class="p">())</span>
<span class="p">}</span>
<span class="c1">// ...</span>

<span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="o">=</span> <span class="nn">MultiError</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="n">errors</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">Err</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="nf">Ok</span><span class="p">(</span>
    <span class="c1">// ...</span>
    <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Essentially, my <code class="language-plaintext highlighter-rouge">MultiError</code> type allowed for what I <strong>thought</strong> was uninspectable-state. Each <code class="language-plaintext highlighter-rouge">syn::Error</code> could hold N errors.</p>

<h2 id="a-fowl-epiphany">A Fowl Epiphany</h2>

<p>As I went through the stages of grief for my beautiful type that had a fundamental flaw, I hit on the idea that perhaps I could upstream a change to expose the internal combined errors from <code class="language-plaintext highlighter-rouge">syn::Error</code>. I thought that the <code class="language-plaintext highlighter-rouge">IntoIterator</code> interface was a good candidate to add. But to my shock, when I opened the docs the <a href="https://docs.rs/syn/2.0.100/syn/struct.Error.html#impl-IntoIterator-for-Error"> <code class="language-plaintext highlighter-rouge">impl IntoIterator</code> for <code class="language-plaintext highlighter-rouge">syn::Error</code> was right there this whole time</a>. I just missed it.</p>

<p>When I realized that <code class="language-plaintext highlighter-rouge">syn::Error</code> already implemented every trait that I needed, I was able to change every <code class="language-plaintext highlighter-rouge">MultiError</code> into <code class="language-plaintext highlighter-rouge">syn::Error</code> and replace every <code class="language-plaintext highlighter-rouge">MultiError::from_error</code> with a function that returns <code class="language-plaintext highlighter-rouge">Option&lt;syn::Error&gt;</code>. Then, with zero other logic changes, my code compiled. That confirmed my suspicions that I had written a duck-typed duplicate of a commonly available struct.</p>

<p>The only value my <code class="language-plaintext highlighter-rouge">MultiError</code> type brought was that it hinted that the function was written with error accumulation in mind, but could not guarantee that the accumulation logic was correct. It didn’t seem like this minor social hint was enough to justify the extra code. I could achieve similar goals with a type alias.</p>
<h2 id="bad-duck">Bad duck</h2>

<p>If a type doesn’t introduce new capabilities or constraints and can be replaced by an existing, stable type, it should probably be deleted in favor of the more common type.</p>

<h2 id="good-duck">Good duck</h2>

<p>Just because a type starts to smell a little foul (or rather “fowl”) does that mean you need to get rid of it? Producing a new type guarantees that there are no mix-up between your type and the common type. New typing could also allow you to restrict operations to a subset of the common type. Both of these things are about adding constraints.</p>

<p>A third reason to keep a duck around would be the stability of the interface. If you’re going to expose your type via a library and you’re worried it might change, then it could be helpful to wrap the type so your downstream user don’t have to change their code even if the underlying logic or implementation changes.</p>

<h2 id="duck-documentation">Duck documentation</h2>

<p>When in doubt, consider documenting your duck and explaining what constraints the new type adds over the original. After writing them down, search for an already existing type that has the same behaviors. Perhaps go so far as to document why those types don’t meet your needs. If you cannot enumerate those differences well, then perhaps it’s a sign you should ditch your duck.</p>

<p>In my case I had explicitly called out <code class="language-plaintext highlighter-rouge">syn::Error</code> and even went as far as implementing <code class="language-plaintext highlighter-rouge">Into&lt;syn::Error&gt;</code>. Those are two strong signs that I should have investigated my claims and looked for features provided by trait implementations.</p>

<h2 id="practice-your-duck-calls">Practice your duck calls</h2>

<p>One of the reasons I missed that <code class="language-plaintext highlighter-rouge">syn::Error</code> already met my needs that I didn’t stop to consider why certain traits were implemented on the struct or think about how they might be used to expose the data that I needed. Over time I’ve been better at internalizing and mentally mapping trait names to the behaviors they provide. Still, I’ve got some more work to do. Hopefully after this experience, with strong hints that I’m re-implementing an existing type as a duck, I won’t forget to check trait implementations for what I need.</p>

<p>Beyond “trying harder” and “writing a blog post as penitence so I don’t do it again,” I thought that it would be nice if this behavior was also shown via an example, so I <a href="https://github.com/dtolnay/syn/pull/1855">sent a PR to syn to add some examples to syn::Error::combine</a>.  I don’t think we need to clutter all code with documenting every possible use case of every possible trait, but this very useful iteration functionality its in nicely in demonstrating how the combine behavior works. Hopefully, the addition of these docs will bre received well and not as an <a href="https://en.wikipedia.org/wiki/Albatross_(metaphor)">albatross</a></p>

<p>I would like to encourage everyone to pay attention to your types and the pain you’re feeling around them. If you find you’ve written a type that you later refactored away, consider pausing and capturing why it was written and why the world is better off without it. What other “bad type” patterns are out there and how can we make it easier for newcomers to spot and avoid them?</p>]]>
      </content>
      
      <summary type="html">
        <![CDATA[Why aren’t people writing more types? Perhaps it’s because the intermediate and expert developers deleted the patterns that didn’t work and left no trace for beginners to learn from. This post details some code I recently deleted that has a pattern I call the “duplicate duck.” You can learn the process I used to develop the type, and why I deleted it. Further, I advocate for Rust developers to document and share their mistakes in the hope that we can all learn from them.]]>
      </summary>
      <updated>2025-05-07T00:00:00+00:00</updated>
      <link href="https://www.schneems.com/2025/05/07/bad-type-patterns-the-duplicate-duck/" rel="alternate" type="text/html" title="Bad Type Patterns - The Duplicate duck" />
    </entry>
  
    <entry>
      <id>https://www.schneems.com/2025/03/26/a-daft-procmacro-trick-how-to-emit-partialcode-errors</id>
      <title>A Daft proc-macro trick: How to Emit Partial-Code + Errors</title>
      <content type="html" xml:base="https://www.schneems.com/2025/03/26/a-daft-procmacro-trick-how-to-emit-partialcode-errors/">
        <![CDATA[<blockquote>
  <p>Update (2025/04/02): The change I suggested below was <a href="https://github.com/oxidecomputer/daft/pull/64">merged in PR #64</a>. It’s pretty neat I went from knowing nothing about this project to contributing to it in the span of a single blog post.</p>
</blockquote>

<p>A recent Oxide and Friends podcast episode, “A crate is born,” detailed the creation of a proc macro for deriving “diffable” data structures with a trick I want to tell you about. To help rust-analyzer as much as possible, <a href="https://hachyderm.io/@rain">@rain</a> explained that the macro should always emit as much valid source code as possible, even when an error is emitted. They didn’t go into detail, so I looked into the internals that made this code + error emitting behavior possible and wanted to share.</p>

<blockquote>
  <p><a href="https://oxide-and-friends.transistor.fm/episodes/a-crate-is-born">Podcast link: A Crate is Born</a></p>
</blockquote>

<p>This post covers:</p>

<ul>
  <li>Why does macro output matter to <code class="language-plaintext highlighter-rouge">rust-analyzer </code>?</li>
  <li>What mechanics are used to emit code + errors?</li>
  <li>When does this macro emit code + errors versus when does it just emit code?</li>
  <li>How does this relate to best practices in future or existing Rust macros?</li>
  <li>What is error accumulation, and why should every proc macro use it?</li>
</ul>

<blockquote>
  <p>Who am I? I write Rust code for Heroku, mainly on the <a href="https://github.com/heroku/buildpacks-ruby">Ruby Cloud Native Buildpack</a> (CNB). CNBs are an alternative to Dockerfile for building OCI images. You can learn more by <a href="https://github.com/heroku/buildpacks/tree/main/docs#use">following a language-specific tutorial you can run locally</a>. I also <a href="https://howtoopensource.dev/">wrote a book on Open Source contribution</a> (paid) and I maintain <a href="https://www.codetriage.com/">an Open Source contribution app - CodeTriage.com</a> (free).</p>
</blockquote>

<h2 id="why-does-macro-output-matter-to-rust-analyzer">Why does macro output matter to <code class="language-plaintext highlighter-rouge">rust-analyzer</code>?</h2>

<blockquote>
  <p>Skip this if you already understand the problem statement</p>
</blockquote>

<p>The Rust compiler will stop when it hits code that cannot compile. However, <code class="language-plaintext highlighter-rouge">rust-analyzer</code> (the Language Server Protocol implementation that powers IDEs like vscode) tries to resume after an error because it can’t just stop rendering type hints.</p>

<p>Intuitively, it makes sense that if you have an invalid function in your code, it shouldn’t break syntax highlighting (or other features) in your valid code:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">invalid_wrong_return</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
  <span class="p">()</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">valid</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
  <span class="s">"I am valid"</span><span class="nf">.to_string</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Daft (v0.1.2), emits trait implementations and sometimes generates new data structures. From the snapshot tests, an input of something like this:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[derive(Debug,</span> <span class="nd">Eq,</span> <span class="nd">PartialEq,</span> <span class="nd">Diffable)]</span>
<span class="k">struct</span> <span class="n">Basic</span> <span class="p">{</span>
    <span class="n">a</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span>
    <span class="n">b</span><span class="p">:</span> <span class="n">BTreeMap</span><span class="o">&lt;</span><span class="n">Uuid</span><span class="p">,</span> <span class="n">BTreeSet</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Will generate code like this:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">BasicDiff</span><span class="o">&lt;</span><span class="nv">'__daft</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">a</span><span class="p">:</span> <span class="o">&lt;</span><span class="nb">i32</span> <span class="k">as</span> <span class="p">::</span><span class="nn">daft</span><span class="p">::</span><span class="n">Diffable</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Diff</span><span class="o">&lt;</span><span class="nv">'__daft</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">b</span><span class="p">:</span> <span class="o">&lt;</span><span class="n">BTreeMap</span><span class="o">&lt;</span><span class="n">Uuid</span><span class="p">,</span> <span class="n">BTreeSet</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;&gt;</span> <span class="k">as</span> <span class="p">::</span><span class="nn">daft</span><span class="p">::</span><span class="n">Diffable</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Diff</span><span class="o">&lt;</span><span class="nv">'__daft</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="k">impl</span> <span class="p">::</span><span class="nn">daft</span><span class="p">::</span><span class="n">Diffable</span> <span class="k">for</span> <span class="n">Basic</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">Diff</span><span class="o">&lt;</span><span class="nv">'__daft</span><span class="o">&gt;</span> <span class="o">=</span> <span class="n">BasicDiff</span><span class="o">&lt;</span><span class="nv">'__daft</span><span class="o">&gt;</span> <span class="k">where</span> <span class="k">Self</span><span class="p">:</span> <span class="nv">'__daft</span><span class="p">;</span>
    <span class="k">fn</span> <span class="n">diff</span><span class="o">&lt;</span><span class="nv">'__daft</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="nv">'__daft</span> <span class="k">self</span><span class="p">,</span> <span class="n">other</span><span class="p">:</span> <span class="o">&amp;</span><span class="nv">'__daft</span> <span class="k">Self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">BasicDiff</span><span class="o">&lt;</span><span class="nv">'__daft</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">Self</span><span class="p">::</span><span class="n">Diff</span> <span class="p">{</span>
            <span class="n">a</span><span class="p">:</span> <span class="p">::</span><span class="nn">daft</span><span class="p">::</span><span class="nn">Diffable</span><span class="p">::</span><span class="nf">diff</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="py">.a</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">other</span><span class="py">.a</span><span class="p">),</span>
            <span class="n">b</span><span class="p">:</span> <span class="p">::</span><span class="nn">daft</span><span class="p">::</span><span class="nn">Diffable</span><span class="p">::</span><span class="nf">diff</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="py">.b</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">other</span><span class="py">.b</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If the macro does not emit this information (possibly due to some hypothetical error not present in this example), then rust-analyzer wouldn’t know that the <code class="language-plaintext highlighter-rouge">BasicDiff</code> struct was expected to exist, what its fields were, or that <code class="language-plaintext highlighter-rouge">Basic::diff()</code> returned a <code class="language-plaintext highlighter-rouge">BasicDiff</code> struct. In short, the IDE would be generally less helpful.</p>

<p>Now that you understand the goal, how do we emit code when there’s an error?</p>

<h2 id="what-mechanics-are-used-to-emit-code--errors">What mechanics are used to emit code + errors?</h2>

<p>The short version is that macros don’t output code or errors; they emit tokens. The daft crate collects errors and continues when possible. If it can generate code, it will emit that generated code as tokens before turning the errors into tokens and then emitting both. An earlier version of the code looked like this:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">errors</span> <span class="o">=</span> <span class="n">error_store</span>
    <span class="nf">.into_inner</span><span class="p">()</span>
    <span class="nf">.into_iter</span><span class="p">()</span>
    <span class="nf">.map</span><span class="p">(|</span><span class="n">error</span><span class="p">|</span> <span class="n">error</span><span class="nf">.into_compile_error</span><span class="p">());</span>

<span class="nd">quote!</span> <span class="p">{</span>
    #<span class="n">out</span>
    #<span class="p">(</span>#<span class="n">errors</span><span class="p">)</span><span class="o">*</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Where <code class="language-plaintext highlighter-rouge">quote!</code> produces tokens from both the code (<code class="language-plaintext highlighter-rouge">#out</code>) and the errors <code class="language-plaintext highlighter-rouge">#(#errors)*</code>.</p>

<p>But don’t take my word for it, read the source: The entry point for the <code class="language-plaintext highlighter-rouge">Daft</code> derive macro is <a href="https://github.com/oxidecomputer/daft/blob/5343fbb0d907ece8990d9d9b60e42669d35b7ece/daft-derive/src/lib.rs#L20"><code class="language-plaintext highlighter-rouge">internal::derive_diffable</code></a>. This function returns a <a href="https://github.com/oxidecomputer/daft/blob/5343fbb0d907ece8990d9d9b60e42669d35b7ece/daft-derive/src/internals/imp.rs#L11-L14"><code class="language-plaintext highlighter-rouge">DeriveDiffableOutput</code></a>. The <code class="language-plaintext highlighter-rouge">DeriveDiffableOutput</code> holds <code class="language-plaintext highlighter-rouge">Option&lt;TokenStream&gt;</code> for valid code that was generated and <code class="language-plaintext highlighter-rouge">Vec&lt;syn::Error&gt;</code> for errors</p>

<p>The <code class="language-plaintext highlighter-rouge">DeriveDiffableOutput</code> implements <code class="language-plaintext highlighter-rouge">quote::ToTokens</code> that emits the valid code followed by the errors (if they exist). <a href="https://github.com/oxidecomputer/daft/blob/5343fbb0d907ece8990d9d9b60e42669d35b7ece/daft-derive/src/internals/imp.rs#L16-L21">code</a>.</p>

<p>Put it all together, and you have a crate that emits partially generated code, even with errors. Neat.</p>

<h2 id="when-to-emit-code--errors">When to emit code + errors?</h2>

<p>Now that I knew how Daft implemented this feature, I wanted to understand when they chose to apply this pattern. I <a href="https://gist.github.com/schneems/6e27cc2e7fe8dea212f95ed9e154bbc7">reviewed the snapshot tests</a> and developed my own classifications.</p>

<p>There are three classes of failures in the snapshot tests:</p>

<ul>
  <li>Emit code and errors (Warning error)</li>
  <li>Emit code only, no errors (Compile error)</li>
  <li>Emit errors only, no code (Error)</li>
</ul>

<p>First, proc-macros cannot emit warnings. If the coder entered slightly off information that wouldn’t affect compilation, the macro author must choose between letting it slide or raising an error. There’s no in-between.</p>

<p>When there’s a problem but daft can determine programmer intent, it will emit code and errors. I call this a “warning error.” The primary example in snapshot testing is when the <code class="language-plaintext highlighter-rouge">#[daft(leaf)]</code>  attribute is used on an enum that is already a leaf by default (this is an internal concept to the crate).</p>

<blockquote>
  <p>Note that the daft crate does not use “warning error” as terminology. I am making that distinction based on my <a href="https://gist.github.com/schneems/6e27cc2e7fe8dea212f95ed9e154bbc7">analysis of the snapshot test. Notes are here.</a>.</p>
</blockquote>

<p>Second, the program can fail to compile even when code is emitted without error, for example, if a trait bound is not satisfied. The macro author cannot detect the problem because the reflection tools don’t expose the necessary information. They must rely on the compiler errors to guide their user.</p>

<p>Finally, there are situations where the author cannot safely emit code because an input is ambiguous or wrong. With these “plain” errors, if the macro author tried to guess and got it incorrect, they’re feeding rust-analyzer incorrect information, which might confuse the end user more. For example, if an attribute that doesn’t exist, such as <code class="language-plaintext highlighter-rouge">#[daft(unknown)]</code>, is found, the macro author has no idea what was intended there and shouldn’t guess.</p>

<p>While working on this classification exercise I found two snapshot tests where code isn’t emitted but could be.</p>

<h2 id="should-all-proc-macros-emit-code--errors">Should all proc macros emit code + errors?</h2>

<p>Obligatory: “It depends.”</p>

<p>If you find your rust-analyzer horribly broken due to a proc-macro problem, then this is a great trick to suggest. However, it isn’t a critical feature that every macro should have. Instead, libraries should focus on improving error accumulation (talked about later).</p>

<p>This code + error functionality requires a lot of plumbing, and ultimately, there’s only one code path (or two if they like my suggestion) that generates code + errors. Looking at how this code path came to be, it seems more that it was added because the plumbing already existed and the opportunity presented itself. From that lens, it’s easy to see why Daft goes this extra mile. The cost to implement was (comparatively) low:</p>

<ul>
  <li><a href="https://github.com/oxidecomputer/daft/pull/41/files#diff-865f958fa8f3072cc4ef20a5f278d6165486225f009f28237cafd156c96ba946R23">Error store introduced in PR #41</a></li>
  <li><a href="https://github.com/oxidecomputer/daft/pull/42">Emit Code + Errors added in PR #42</a></li>
</ul>

<p>While the lede: emitting code + errors is likely too much of an ask for most crates, every proc macro should accumulate errors. Let’s look at that now.</p>

<h2 id="more--for-your--accumulate-proc-macro-errors">More <code class="language-plaintext highlighter-rouge">!</code> for your <code class="language-plaintext highlighter-rouge">$</code>: Accumulate proc macro errors</h2>

<p>What do I mean by accumulated errors? In a Python (or Ruby) program that raises an error, you have to fix that to find out if there’s another error lurking that also needs to be fixed. Rust programmers don’t like playing that game. They want as many errors upfront as possible:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error: #[daft(leaf)] specified multiple times
 --&gt; tests/fixtures/invalid/field-specified-multiple-times.rs:5:18
  |
5 |     #[daft(leaf, leaf, leaf)]
  |                  ^^^^

error: #[daft(leaf)] specified multiple times
 --&gt; tests/fixtures/invalid/field-specified-multiple-times.rs:5:24
  |
5 |     #[daft(leaf, leaf, leaf)]
  |                        ^^^^

error: #[daft(ignore)] specified multiple times
 --&gt; tests/fixtures/invalid/field-specified-multiple-times.rs:8:12
  |
8 |     #[daft(ignore)]
  |            ^^^^^^

error: #[daft(ignore)] specified multiple times
 --&gt; tests/fixtures/invalid/field-specified-multiple-times.rs:9:12
  |
9 |     #[daft(ignore)]
  |            ^^^^^^
</code></pre></div></div>

<p>This daft output says there are two errors on line 5. One error on lines 8 and 9. This code generated the following errors:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">daft</span><span class="p">::</span><span class="n">Diffable</span><span class="p">;</span>

<span class="nd">#[derive(Diffable)]</span>
<span class="k">struct</span> <span class="n">MyStruct</span> <span class="p">{</span>
    <span class="nd">#[daft(leaf,</span> <span class="nd">leaf,</span> <span class="nd">leaf)]</span> <span class="c1">// line 5</span>
    <span class="n">a</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span>
    <span class="nd">#[daft(ignore)]</span>
    <span class="nd">#[daft(ignore)]</span> <span class="c1">// line 8</span>
    <span class="nd">#[daft(ignore)]</span> <span class="c1">// line 9</span>
    <span class="n">b</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Fields and their attributes are parsed iteratively, so it’s common for a macro to stop iterating on the first problem (line 5) before returning. Instead, Daft stores the errors and continues parsing until it longer can.</p>

<p>I don’t think it’s the end of the world if a proc macro only emits a single error at a time, but it’s a requirement if you’re aiming for a “Michelin star proc-macro” experience.</p>

<h2 id="how-does-daft-implement-error-accumulation">How does Daft implement error accumulation?</h2>

<p>Instead of using a <code class="language-plaintext highlighter-rouge">Result&lt;T, syn::Error&gt;</code> return, daft passes an accumulator that holds a <code class="language-plaintext highlighter-rouge">Vec&lt;syn::Error&gt;</code> to every fallible function.  If there’s an error, it’s added to the accumulator.
This pattern also means that instead of having to choose between emitting <code class="language-plaintext highlighter-rouge">T</code> or <code class="language-plaintext highlighter-rouge">syn::Error</code> (via a <code class="language-plaintext highlighter-rouge">Result&lt;T, syn::Error&gt;</code>), the programmer can do both by returning a <code class="language-plaintext highlighter-rouge">T</code> while mutating the accumulator. That would indicate the problem is more of a “warning error” if the data structure can still be safely returned. Functions that return <code class="language-plaintext highlighter-rouge">Option&lt;T&gt;</code> indicate they’re likely holding one or more plain errors that would prevent code generation when a <code class="language-plaintext highlighter-rouge">None</code> is returned.</p>

<p>Beyond affording the ability to return code + errors, not using a Result means that the try operator (<code class="language-plaintext highlighter-rouge">?</code>) cannot be used accidentally for an early/eager return. This property encourages the macro author to capture as many errors as possible and emit them all instead of only emitting the first error. It’s a neat pattern, but it’s not the only way to accumulate errors.</p>

<h2 id="alternative-pattern-for-synerror-accumulation">Alternative pattern for <code class="language-plaintext highlighter-rouge">syn::Error</code> accumulation</h2>

<p>The <code class="language-plaintext highlighter-rouge">syn::Error</code> struct has the capability of combining multiple errors without an accumulator by using <a href="https://docs.rs/syn/2.0.100/syn/struct.Error.html#method.combine"><code class="language-plaintext highlighter-rouge">syn::Error::combine</code></a>. For example:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="k">mut</span> <span class="n">errors</span> <span class="o">=</span> <span class="nn">VecDeque</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">syn</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
<span class="c1">// ...</span>
<span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="k">mut</span> <span class="n">first</span><span class="p">)</span> <span class="o">=</span> <span class="n">errors</span><span class="nf">.pop_front</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">e</span> <span class="k">in</span> <span class="n">errors</span><span class="nf">.into_iter</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">first</span><span class="nf">.combine</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="nf">Err</span><span class="p">(</span><span class="n">first</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="nf">Ok</span><span class="p">(</span>
    <span class="c1">// ...</span>
    <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This pattern is useful when the function signature isn’t changeable. For example, the <a href="https://docs.rs/syn/2.0.100/syn/parse/trait.Parse.html"><code class="language-plaintext highlighter-rouge">syn::parse::Parse</code></a> trait is commonly used by proc macros as a building block, and it has a fixed signature:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">parse</span><span class="p">(</span><span class="n">input</span><span class="p">:</span> <span class="nn">syn</span><span class="p">::</span><span class="nn">parse</span><span class="p">::</span><span class="n">ParseStream</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="nn">syn</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>With this pattern, the error behavior of the function is encoded in its return type:</p>

<ul>
  <li>Errors that block code generation should return <code class="language-plaintext highlighter-rouge">Result&lt;T, syn::Error&gt;</code></li>
  <li>Errors that do not block code generation should return <code class="language-plaintext highlighter-rouge">(T, Option&lt;syn::Error&gt;)</code></li>
  <li>Errors that may or may not block code generation should return <code class="language-plaintext highlighter-rouge">Result&lt;(T, Option&lt;syn::Error&gt;), syn::Error&gt;</code>.
    <ul>
      <li><code class="language-plaintext highlighter-rouge">Ok((T, None))</code> indicates no errors</li>
      <li><code class="language-plaintext highlighter-rouge">Ok((T, Some()))</code> indicates an error that did not block code generation.</li>
      <li><code class="language-plaintext highlighter-rouge">Err()</code> indicates that code could not be generated due to error</li>
    </ul>
  </li>
</ul>

<p>That last one is verbose, but it prevents representing an invalid state when <code class="language-plaintext highlighter-rouge">None</code> code and <code class="language-plaintext highlighter-rouge">None</code> errors are returned simultaneously.</p>

<p>The downside of this technique is that nothing prevents an early return on error with try (<code class="language-plaintext highlighter-rouge">?</code>).</p>

<p>I  was curious how this pattern would look implemented in place of the daft one, so I experimented with a <a href="https://github.com/schneems/daft/pull/1">draft (not daft) PR</a>.</p>

<blockquote>
  <p>Note: The PR is to my own <code class="language-plaintext highlighter-rouge">main</code> branch, not theirs. I don’t think any maintainer loves waking up to a giant PR with the “refactoring” in it.</p>
</blockquote>

<h2 id="wrap-up">Wrap up</h2>

<p>We learned why <code class="language-plaintext highlighter-rouge">rust-analyzer</code> is sensitive to macro output. We explored the mechanics that Daft uses to emit code + errors, and  accumulate errors. I introduced an alternative error accumulation method and I made some strong statements. Namely that emitting code + errors is a nice-to-have while accumulating and emitting all errors is an achievable best practice.</p>

<p>Coming from Ruby, proc macros are wonderful things that allow Rust developers to write powerful and expressive DSLs, and I love them. With the power to meta-program, there’s also the possibility to meta-confuse your end user or toolchain (like rust-analyzer). I love that the Daft maintainers put as much work and care into the failure modes as the rest of their logic in addition to accumulating and presenting as many errors as possible.</p>

<p>I hoped you enjoyed learning about these patterns as much as I did.</p>

<blockquote>
  <p>FYI I’m working on a proc-macro tutorial and would love to hear from readers on <a href="https://ruby.social/@Schneems">Mastodon</a> or <a href="https://www.reddit.com/r/rust/comments/1jkisf1/a_daft_procmacro_trick_how_to_emit_partialcode/">Reddit</a> about what real-world patterns you’ve seen around improving the end-user experience, especially around errors.</p>
</blockquote>]]>
      </content>
      
      <summary type="html">
        <![CDATA[Update (2025/04/02): The change I suggested below was merged in PR #64. It’s pretty neat I went from knowing nothing about this project to contributing to it in the span of a single blog post.]]>
      </summary>
      <updated>2025-03-26T00:00:00+00:00</updated>
      <link href="https://www.schneems.com/2025/03/26/a-daft-procmacro-trick-how-to-emit-partialcode-errors/" rel="alternate" type="text/html" title="A Daft proc-macro trick: How to Emit Partial-Code + Errors" />
    </entry>
  
    <entry>
      <id>https://www.schneems.com/2025/03/17/installing-the-sassc-ruby-gem-on-a-mac-a-debugging-story</id>
      <title>Installing the sassc Ruby gem on a Mac. A debugging story</title>
      <content type="html" xml:base="https://www.schneems.com/2025/03/17/installing-the-sassc-ruby-gem-on-a-mac-a-debugging-story/">
        <![CDATA[<p>I’m not exactly sure about the timeline, but at some point, <code class="language-plaintext highlighter-rouge">gem install sassc</code> stopped working for me on my Mac (ARM). Initially, I thought this was because that gem was no longer maintained, and the last release was in 2020, but I was wrong. It’s 100% installable today. Read the rest to find out the real culprit and how to fix it.</p>

<blockquote>
  <p>FWIW some folks on <a href="https://lobste.rs/s/d69ogy/installing_sassc_ruby_gem_on_mac">lobste.rs</a> suggested switching to <a href="https://rubygems.org/gems/sass-embedded">sass-embedded</a> for sass needs. This post still, works but into the future it might not.</p>
</blockquote>

<p>In this post I’ll explain some things about native extensions libraries in Ruby and in the process tell you how to fix this error below if you’re getting it on your Mac:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/rschneeman/.gem/ruby/3.4.1/gems/sassc-2.4.0/ext
/Users/rschneeman/.rubies/ruby-3.4.1/bin/ruby extconf.rb
creating Makefile

current directory: /Users/rschneeman/.gem/ruby/3.4.1/gems/sassc-2.4.0/ext
make DESTDIR\= sitearchdir\=./.gem.20250314-33410-os7ibg sitelibdir\=./.gem.20250314-33410-os7ibg clean

current directory: /Users/rschneeman/.gem/ruby/3.4.1/gems/sassc-2.4.0/ext
make DESTDIR\= sitearchdir\=./.gem.20250314-33410-os7ibg sitelibdir\=./.gem.20250314-33410-os7ibg
compiling ./libsass/src/ast.cpp
compiling ./libsass/src/ast2c.cpp
make: *** [ast.o] Error 1
make: *** Waiting for unfinished jobs....
compiling ./libsass/src/ast_fwd_decl.cpp
make: *** [ast2c.o] Error 1
compiling ./libsass/src/ast_sel_super.cpp
make: *** [ast_fwd_decl.o] Error 1
compiling ./libsass/src/ast_sel_cmp.cpp
make: *** [ast_sel_super.o] Error 1
compiling ./libsass/src/ast_supports.cpp
make: *** [ast_sel_cmp.o] Error 1
compiling ./libsass/src/ast_sel_weave.cpp
make: *** [ast_supports.o] Error 1
compiling ./libsass/src/ast_values.cpp
compiling ./libsass/src/backtrace.cpp
make: *** [ast_sel_weave.o] Error 1
compiling ./libsass/src/ast_selectors.cpp
make: *** [ast_values.o] Error 1
compiling ./libsass/src/ast_sel_unify.cpp
make: *** [backtrace.o] Error 1
make: *** [ast_selectors.o] Error 1
make: *** [ast_sel_unify.o] Error 1

make failed, exit code 2
</code></pre></div></div>

<h2 id="last-things-first-how-to-fix-the-problem">Last things first: How to fix the problem</h2>

<p>You can install the <code class="language-plaintext highlighter-rouge">sassc</code> on your Mac by:</p>

<ul>
  <li>Uninstall(ing) your ruby version(s)</li>
  <li>Delete your gems (or at least <code class="language-plaintext highlighter-rouge">sassc</code>)</li>
  <li>Update to a recent Xcode release (<code class="language-plaintext highlighter-rouge">$ xcode-select --version</code> for me reports <code class="language-plaintext highlighter-rouge">xcode-select version 2409</code>)</li>
  <li>Re-compile Ruby version(s)</li>
  <li>Now <code class="language-plaintext highlighter-rouge">gem install sassc</code> should work</li>
</ul>

<p>If you want to know more about native compilation or my debugging process, read on!</p>

<blockquote>
  <p>There might be a simpler way to solve the problem (such as directly editing the rbconfig file), but I’m comfortable sharing the above steps because that’s what I’ve done. If you fixed this differently, post the solution on your own site or in the comments somewhere.</p>
</blockquote>

<h2 id="debugging-collecting-info">Debugging: Collecting info</h2>

<p>When I get an error, it makes sense to search for it and ask an LLM (if that’s your thing). I did both. GitHub copilot suggested that I make sure command-line tools are installed and that <code class="language-plaintext highlighter-rouge">cmake</code> is installed via homebrew. This was unhelpful, but it’s worth double-checking.</p>

<p>Searching <code class="language-plaintext highlighter-rouge">libsass make: *** [ast2c.o] Error 1</code> brought me to <a href="https://github.com/sass/sassc-ruby/issues/248">https://github.com/sass/sassc-ruby/issues/248</a>. This brought me to <a href="https://github.com/sass/sassc-ruby/issues/225#issuecomment-2391129846">https://github.com/sass/sassc-ruby/issues/225#issuecomment-2391129846</a>. Suggesting that the problem is related to RbConfig and native extensions. These have the fix in there, but don’t go into detail on the <strong>why</strong> the fix works. This post attempts to dig deeper using a debugging mindset.</p>

<blockquote>
  <p>Meta narrative: This article skips between explaining things I know to be true and debugging via doing. Skip any explanations you feel are tedious.</p>
</blockquote>

<h2 id="explaining-native-extensions">Explaining: Native extensions</h2>

<blockquote>
  <p>Skip if: You know what a native extension is</p>
</blockquote>

<p>Most Ruby libraries are plain Ruby code. For an example look at <a href="https://github.com/zombocom/mini_histogram">https://github.com/zombocom/mini_histogram</a>. When you <code class="language-plaintext highlighter-rouge">gem install mini_histogram</code>, it downloads the source code, and that’s all that’s needed to run it (well, that and a Ruby version installed on the machine). The term “native extensions” refers to libraries that use Ruby’s C API or FFI in some way. There are a few reasons why someone would want to do this:</p>

<ul>
  <li>Performance: A really expensive algorithm might run faster if it’s written in a different language and then invoked by Ruby.</li>
  <li>Interface: A library doesn’t want to reinvent the wheel, so it leans on already existing software installed at the system level. For example, if a program needs to handle SSL connections, it can use OpenSSL on the system instead of rewriting all of that logic in Ruby. For example, the <code class="language-plaintext highlighter-rouge">psych</code> gem uses <code class="language-plaintext highlighter-rouge">libyaml</code>, and the <code class="language-plaintext highlighter-rouge">nokogiri</code> gem uses <code class="language-plaintext highlighter-rouge">libxml</code>.</li>
</ul>

<p>For developers who haven’t used much C or C++, it’s useful to know that system-installed packages are how they (mostly) share code. There’s no rubygems.org for C packages. Things like <code class="language-plaintext highlighter-rouge">apt</code> for Ubuntu might be conflated as a “C package manager,” but it’s really like <code class="language-plaintext highlighter-rouge">brew</code> (for Mac), where it installs things globally. Then, when you compile a program in C, it can dynamically or statically link to other libraries to use them.</p>

<p>Back to native extensions: When a gem with a native extension is installed the source code is downloaded but then a secondary compilation process is invoked. Here’s <a href="https://dev.to/vinistock/creating-ruby-native-extensions-kg1">a tutorial on creating a native extension</a>. It utilizes a tool called <a href="https://github.com/rake-compiler/rake-compiler">rake-compiler</a>. But under the hood it effectively boils down to when you <code class="language-plaintext highlighter-rouge">gem install &lt;native-extension&gt;</code> it will run compilation code such as <code class="language-plaintext highlighter-rouge">$ make install</code> on the system. This process generates compiled binaries, these binaries are compiled against a specific CPU architecture that is native to the machine you’re on, hence why they’re called native extensions. You’re using native (binary) code to extend Ruby’s capabilities.</p>

<h2 id="explaining-vendoring-in-native-extensions">Explaining: Vendoring in native extensions</h2>

<blockquote>
  <p>Skip if: You understand why <code class="language-plaintext highlighter-rouge">libsass</code> CPP files would be found in the <code class="language-plaintext highlighter-rouge">sassc</code> gem</p>
</blockquote>

<p>Compiling code is hard. Or rather, dependency management is hard, and compiling code requires that the platform have certain dependencies installed; therefore, compiling code is hard. To make life easier, one common pattern that Ruby developers do is to vendor in dependencies into their native extension gem. Rather than assuming <code class="language-plaintext highlighter-rouge">libsass</code> is installed on the system in a location that is easy to find, it can instead bring that code along with it.</p>

<p>Here you can see that sassc from <code class="language-plaintext highlighter-rouge">gem install sassc</code> brings C++ source code from libsass:</p>

<pre><code class="language-term">$ ls /Users/rschneeman/.gem/ruby/3.4.2/gems/sassc-2.4.0/ext/libsass/src | head -n 3
MurmurHash2.hpp
ast.cpp
ast.hpp
</code></pre>

<p>In this case <code class="language-plaintext highlighter-rouge">libsass</code> may have dependencies that it hasn’t vendored and it expects to find on the system, but the key here is that when you <code class="language-plaintext highlighter-rouge">gem install sassc</code> it needs to <code class="language-plaintext highlighter-rouge">make install</code> not just its own bridge code (using Ruby’s C API), but it also needs to compile <code class="language-plaintext highlighter-rouge">libsass</code> as well. That is where the errors are coming from, it’s not able to compile these C++ files:</p>

<pre><code class="language-term">compiling ./libsass/src/ast2c.cpp
make: *** [ast.o] Error 1
make: *** Waiting for unfinished jobs....
</code></pre>

<p>For completeness: There’s another type of vendoring that native-extension gems can do. They can statically compile and vendor in a binary. This bypasses the need to <code class="language-plaintext highlighter-rouge">make install</code> and is much faster, but moves the burden to the gem maintainer. Here’s an example where <a href="https://rubygems.org/gems/nokogiri/versions/1.18.4-arm64-darwin">Nokogiri 1.18.4 is precompiled to run on my ARM Mac</a>. You don’t need to know this for debugging the <code class="language-plaintext highlighter-rouge">sassc</code> install problem, since that process isn’t being used here.</p>

<h2 id="debugging-remove-ruby-from-the-loop">Debugging: Remove ruby from the loop</h2>

<p>When debugging, I like to remove layers of abstraction when possible to boil the problem down to its core essence. You might think “I cannot run <code class="language-plaintext highlighter-rouge">gem install sass</code> “ is the problem, but really that’s the context; the <strong>real</strong> problem is that within that process, the <code class="language-plaintext highlighter-rouge">make</code> command fails. The output of the command isn’t terribly well structured, but there are hints that this is the core problem:</p>

<pre><code class="language-term">current directory: /Users/rschneeman/.gem/ruby/3.4.1/gems/sassc-2.4.0/ext
make DESTDIR\= sitearchdir\=./.gem.20250314-65761-9llhhv sitelibdir\=./.gem.20250314-65761-9llhhv
compiling ./libsass/src/ast.cpp
compiling ./libsass/src/ast2c.cpp
</code></pre>

<p>This is saying, “When I am in this directory” and “I run this command <code class="language-plaintext highlighter-rouge">make &lt;arguments&gt;</code>” then I get this output.</p>

<p>When someone is experiencing an exception on their Rails app, I encourage them to try copying that code into a <code class="language-plaintext highlighter-rouge">rails console</code> session to reproduce the problem without the overhead of the request/response cycle. This helps reduce the scope and removes a layer of abstraction.</p>

<p>Here removing abstraction will be manually go into that directory and run <code class="language-plaintext highlighter-rouge">make</code>. Doing this gave me the same error:</p>

<pre><code class="language-term">$ make clean &amp;&amp; make
compiling ./libsass/src/ast.cpp
compiling ./libsass/src/ast2c.cpp
compiling ./libsass/src/ast_fwd_decl.cpp
make: *** [ast.o] Error 1
make: *** Waiting for unfinished jobs....
</code></pre>

<p>I was curious about how to get more information out of <code class="language-plaintext highlighter-rouge">make</code> and found a SO post suggesting that <code class="language-plaintext highlighter-rouge">make -n</code> will list out the commands. From <code class="language-plaintext highlighter-rouge">man make</code> or <code class="language-plaintext highlighter-rouge">make --help</code> I see this description:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  -n, --just-print, --dry-run, --recon
                              Don't actually run any commands; just print them.
</code></pre></div></div>

<p>Running that gave me some output:</p>

<pre><code class="language-term">$ make -n
echo compiling ./libsass/src/ast.cpp
false -I. -I/Users/rschneeman/.rubies/ruby-3.4.1/include/ruby-3.4.0/arm64-darwin24 -I/Users/rschneeman/.rubies/ruby-3.4.1/include/ruby-3.4.0/ruby/backward -I/Users/rschneeman/.rubies/ruby-3.4.1/include/ruby-3.4.0 -I. -I./libsass/include -I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/libyaml/include -I/opt/homebrew/opt/gdbm/include -I/opt/X11/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT   -fno-common -fdeclspec -std=c++11 -DLIBSASS_VERSION='"3.6.4"' -arch arm64 -o ast.o -c ./libsass/src/ast.cpp
echo compiling ./libsass/src/ast2c.cpp
false -I. -I/Users/rschneeman/.rubies/ruby-3.4.1/include/ruby-3.4.0/arm64-darwin24 -I/Users/rschneeman/.rubies/ruby-3.4.1/include/ruby-3.4.0/ruby/backward -I/Users/rschneeman/.rubies/ruby-3.4.1/include/ruby-3.4.0 -I. -I./libsass/include -I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/libyaml/include -I/opt/homebrew/opt/gdbm/include -I/opt/X11/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT   -fno-common -fdeclspec -std=c++11 -DLIBSASS_VERSION='"3.6.4"' -arch arm64 -o ast2c.o -c ./libsass/src/ast2c.cpp
</code></pre>

<p>If you’re familiar with the output above you probably spotted the problem. If not, let’s detour and explain what this make tool even is.</p>

<h2 id="explaining-what-is-a-make">Explaining: What is a make?</h2>

<blockquote>
  <p>Skip this if you know what make is and how to write a <code class="language-plaintext highlighter-rouge">Makefile</code></p>
</blockquote>

<p>GNU make describes itself as:</p>

<blockquote>
  <p><em>GNU Make</em> is a tool which controls the generation of executables and other non-source files of a program from the program’s source files.</p>
</blockquote>

<p>The library Rake is a similar concept implemented in Ruby. The name “Rake” is short for “Ruby (M)ake.”</p>

<p>In Rake, you can define a task and its prerequisites. The Rake tool will resolve those to ensure they’re run in the correct order without having to run them multiple times. This is commonly used for database migrations and generating assets for a web app, such as CSS and JS.</p>

<p>Technically, that’s all Make does as well, it allows you to define tasks in a reusable way, and it handles some of the logic of execution. In practice, make has become the go-to composition tool for compiling C programs. In that world there are projects that don’t even tell you how to build the binaries because they expect you to <code class="language-plaintext highlighter-rouge">./configure &amp;&amp; make &amp;&amp; make install</code> in the same way some Ruby developers might forget instructions on adding a gem to the Gemfile in the README of their rubygem.</p>

<p>You can see a makefile in action following <a href="https://docs.ruby-lang.org/en/master/contributing/building_ruby_md.html#label-Quick+start+guide">Ruby’s instructions on compilation</a></p>

<pre><code class="language-term">$ git clone https://github.com/ruby/ruby
$ cd ruby
$ ./autogen.sh
$ mkdir build &amp;&amp; cd build
$ ../configure --prefix="${HOME}/.rubies/ruby-master"
$ cat Makefile | head -n 10
RUBY_RELEASE_YEAR = 2024
RUBY_RELEASE_MONTH = 06
RUBY_RELEASE_DAY = 06
# -*- mode: makefile-gmake; indent-tabs-mode: t -*-

SHELL = /bin/sh
NULLCMD = :
silence = no # yes/no
yes_silence = $(silence:no=)
no_silence = $(silence:yes=)
</code></pre>

<p>At the end of the day, <code class="language-plaintext highlighter-rouge">make</code> does very little. It’s almost more like its own language that happens to be useful for compiling code rather than a “compiling code” tool. The result is that the bulk of the logic comes from the contents of the Makefile and what the developer put in there rather than the Make tool itself. The output ends up being indistinguishable from a bunch of shell scripts in a trenchcoat.</p>

<h2 id="debugging-weird-make-output">Debugging: Weird make output</h2>

<p>Now we know that <code class="language-plaintext highlighter-rouge">make</code> does very little and we have its output we see two lines (I added a space for clarity):</p>

<pre><code class="language-term">$ make -n
echo compiling ./libsass/src/ast.cpp

false -I. -I/Users/rschneeman/.rubies/ruby-3.4.1/include/ruby-3.4.0/arm64-darwin24 -I/Users/rschneeman/.rubies/ruby-3.4.1/include/ruby-3.4.0/ruby/backward -I/Users/rschneeman/.rubies/ruby-3.4.1/include/ruby-3.4.0 -I. -I./libsass/include -I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/libyaml/include -I/opt/homebrew/opt/gdbm/include -I/opt/X11/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT   -fno-common -fdeclspec -std=c++11 -DLIBSASS_VERSION='"3.6.4"' -arch arm64 -o ast.o -c ./libsass/src/ast.cpp
</code></pre>

<p>We could remove <code class="language-plaintext highlighter-rouge">make</code> from the equation by running them directly:</p>

<pre><code class="language-term">$ echo compiling ./libsass/src/ast.cpp
compiling ./libsass/src/ast.cpp
</code></pre>

<p>That worked as expected, what about the next line? It starts with <code class="language-plaintext highlighter-rouge">false</code> which, from the manual page</p>

<pre><code class="language-term">$ man false
...
DESCRIPTION
     The false utility always returns with a non-zero exit code.
</code></pre>

<p>So no matter what comes after this command, it will simply exit non-zero. This command can never work. This seems odd, definetly not what the author of this makefile intended. If this is the bug, and I think it is, where is that <code class="language-plaintext highlighter-rouge">false</code> coming from? Is it dynamic from something in the environment (environment variables) or is it coming from shelling out to some other utility on disk or is it coming from some config file? Or is it static? Is it baked in already.</p>

<p>Re-running <code class="language-plaintext highlighter-rouge">make -n</code> with env vars (mentioned in the GitHub comments) such as <code class="language-plaintext highlighter-rouge">CC="clang" CXX="clang++"</code> has no effect. It’s the same output. This leads me to believe it’s something static.</p>

<p>Looking at the contents of the Makefile:</p>

<pre><code class="language-term">$ cat Makefile | grep false
CXX = false
</code></pre>

<p>Huh, that’s weird. Where is that used?</p>

<pre><code class="language-term">$ cat Makefile | grep CXX
CXX = false
CXXFLAGS = $(CCDLFLAGS) -fdeclspec -std=c++11 -DLIBSASS_VERSION='"3.6.4"' $(ARCH_FLAG)
LDSHAREDXX = $(CXX) -dynamic -bundle
	$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$&lt;
</code></pre>

<p>That last line comes from this code in the Makefile:</p>

<pre><code class="language-term">.cc.o:
	$(ECHO) compiling $(&lt;)
	$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$&lt;
</code></pre>

<h2 id="explain-what-is-in-a-makefile">Explain: What is in a Makefile</h2>

<blockquote>
  <p>Skip this if you know make syntax</p>
</blockquote>

<p>To understand what this is doing, we can write a tiny make program:</p>

<pre><code class="language-term">$ cat Makefile
lol:
	echo "hahaha"
</code></pre>

<blockquote>
  <p>The indentation under the <code class="language-plaintext highlighter-rouge">lol:</code> should be a tab, but your editor or my blogging process might have converted it into a space.</p>
</blockquote>

<p>Now when we run that:</p>

<pre><code class="language-term">$ make
echo "hahaha"
hahaha
</code></pre>

<p>It printed the command and then the output of that command. We’re not limited to static commands though. Modify the file:</p>

<pre><code class="language-term">$ cat Makefile
CMD=echo

lol:
	$(CMD) "hahaha"
</code></pre>

<p>Here, we’ve extracted the command <code class="language-plaintext highlighter-rouge">echo</code> into a variable and are using that to produce the same effective command.</p>

<h2 id="explaining-what-cxxfalse-means-in-the-makefile">Explaining: What CXX=false means in the Makefile</h2>

<p>What that means is <code class="language-plaintext highlighter-rouge">CXX=false</code> tells make to replace <code class="language-plaintext highlighter-rouge">$(CXX)</code> with <code class="language-plaintext highlighter-rouge">false</code> which is not what we want. But where did <code class="language-plaintext highlighter-rouge">CXX=false</code> come from? I’m glad you asked. If you search the source code for that line, you won’t find it. That’s because this Makefile is generated.</p>

<p>When we looked at native extensions before, notice that I talked about <code class="language-plaintext highlighter-rouge">rake-compiler</code> and not about hand-rolling a <code class="language-plaintext highlighter-rouge">Makefile</code>. Even when we looked at <code class="language-plaintext highlighter-rouge">ruby/ruby</code>-s Makefile, it wasn’t hardcoded; it came to be after calling <code class="language-plaintext highlighter-rouge">./autogen.sh</code> and <code class="language-plaintext highlighter-rouge">../configure</code>. This Makefile is generated at install time.</p>

<h2 id="debugging-where-did-the-false-come-from">Debugging: Where did the <code class="language-plaintext highlighter-rouge">false</code> come from?</h2>

<p>When you compile Ruby <code class="language-plaintext highlighter-rouge">./configure &amp;&amp; make &amp;&amp; make install</code> it needs to gather information about the system in order to know how to compile itself. Things like “what compiler are you using” (it could be gcc or clang, for example). Ruby isn’t the only program that needs to know this stuff; native extension code that compiles needs to know it, too.</p>

<p>When you compile Ruby it generates a <code class="language-plaintext highlighter-rouge">rbconfig.rb</code> file that contains information that Ruby users can access via <a href="https://docs.ruby-lang.org/en/3.4/RbConfig.html">RbConfig</a>.  From the docs:</p>

<blockquote>
  <p>The module storing Ruby interpreter configurations on building.</p>

  <p>This file was created by mkconfig.rb when ruby was built. It contains build information for ruby which is used e.g. by mkmf to build compatible native extensions. Any changes made to this file will be lost the next time ruby is built.</p>
</blockquote>

<p>So that info is what Ruby used at compile time. Where is it?</p>

<pre><code class="language-term">$ find /Users/rschneeman/.rubies -name rbconfig.rb
...
/Users/rschneeman/.rubies/ruby-3.4.1/lib/ruby/3.4.0/arm64-darwin24/rbconfig.rb
</code></pre>

<p>When I looked at that file I saw something alarming:</p>

<pre><code class="language-term">$ cat ../arm64-darwin24/rbconfig.rb | grep false
# frozen-string-literal: false
  CONFIG["CXX"] = "false"
	config[v] = false
</code></pre>

<p>When Ruby was compiled it came to the conclusion that it should use <code class="language-plaintext highlighter-rouge">clang</code> to compile C code:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="no">CONFIG</span><span class="p">[</span><span class="s2">"CC"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"clang"</span>
</code></pre></div></div>

<p>But it mistakenly concluded that it should use the <code class="language-plaintext highlighter-rouge">false</code> command to compile C++ code (the meaning of these environment variables). It SHOULD be <code class="language-plaintext highlighter-rouge">clang++</code> or something like <code class="language-plaintext highlighter-rouge">clang++—std=gnu++11</code>, but it’s not.</p>

<p>When the Makefile for the <code class="language-plaintext highlighter-rouge">sassc</code> gem is generated it hardcodes <code class="language-plaintext highlighter-rouge">CXX=false</code> into it by mistake because it is pulling that information from the <code class="language-plaintext highlighter-rouge">RbConfig</code> module generated by Ruby at compile time.</p>

<p>Why did it record <code class="language-plaintext highlighter-rouge">false</code>? Well, I don’t know. I assume it has something to do with the interplay between Ruby’s configuration script and Xcode developer tools. I didn’t debug down that pathway. Since we can fix the problem by re-installing the same version of Ruby with a newer version of the Xcode developer tools, it seems that the problem is in Xcode, but there might be a more complicated interaction involved (perhaps Ruby is doing something Xcode didn’t expect, for example).</p>

<h2 id="the-fix-uninstall-and-reinstall">The fix: Uninstall, and reinstall</h2>

<p>Thankfully others came before me and came to the conclusion about where the problem was coming from and how to fix it. They suggested what I did above:</p>

<ul>
  <li>Delete/uninstall Ruby</li>
  <li>Delete/uninstall gems (adding this to avoid any cached or stale generated Makefiles)</li>
  <li>Upgrade Xcode developer tools. (Version <code class="language-plaintext highlighter-rouge">2409</code> worked for me)</li>
  <li>Reinstall Ruby</li>
  <li>Install <code class="language-plaintext highlighter-rouge">sassc</code> to your heart’s content</li>
</ul>

<p>After doing this you can inspect the <code class="language-plaintext highlighter-rouge">RbConfig</code> file:</p>

<pre><code class="language-term">$ cat /Users/rschneeman/.rubies/ruby-3.4.1/lib/ruby/3.4.0/arm64-darwin24/rbconfig.rb | grep CXX
  CONFIG["LDSHAREDXX"] = "$(CXX) -dynamic -bundle"
  CONFIG["CXXFLAGS"] = "-fdeclspec"
  CONFIG["CXX"] = "clang++ -std=gnu++11"
</code></pre>

<p>Lookin good. It no longer reports <code class="language-plaintext highlighter-rouge">false</code>.</p>

<h2 id="wrapup">Wrapup</h2>

<p>I mentioned above that it might be possible to manually edit these files to fix the problem. That would save the time and energy for re-compiling your Rubies. But you definitely want to upgrade your Xcode developer tools and ensure that future ruby installs have the right information. Going through the motions of this full process for at least one Ruby version (assuming you’re using a version switcher like <a href="https://github.com/postmodern/chruby">chruby</a> or <a href="https://asdf-vm.com/">asdf</a>) is recommended. Personally, I uninstalled everything to decrease the chances that I have to re-learn about this problem and find this blog post X months/years in the future because I missed something in my process.</p>

<p>For those of you without this problem: Hopefully, this was educational. You might be wondering why I decided to blog about <strong>this</strong> specific topic (of all things). Well, I’ve got to do something while I’m recompiling all those rubies, and learning-via-teaching is a core pedagogy of mine.</p>

<p>If you enjoyed this post consider:</p>

<ul>
  <li>Reading more of my writing by:
    <ul>
      <li>Trying the <a href="https://github.com/heroku/buildpacks/blob/main/docs/ruby/README.md">evergreen Cloud Native Buildpack tutorial for Ruby</a> that covers building OCI images with CNBs instead of Dockerfiles.</li>
    </ul>
  </li>
  <li>Buying my book  <a href="https://howtoopensource.dev/">“How to Open Source”</a> with your corporate card.</li>
  <li>Following me on socials:
    <ul>
      <li><a href="https://ruby.social/@Schneems">Mastodon</a></li>
      <li><a href="https://ruby.social/@Schneems">Bsky</a></li>
    </ul>
  </li>
  <li>Taking some time this fine afternoon to write a blog post about whatever random debugging topic you’re currently battling.</li>
  <li>Finding a doggo and petting them</li>
</ul>]]>
      </content>
      
      <summary type="html">
        <![CDATA[I’m not exactly sure about the timeline, but at some point, gem install sassc stopped working for me on my Mac (ARM). Initially, I thought this was because that gem was no longer maintained, and the last release was in 2020, but I was wrong. It’s 100% installable today. Read the rest to find out the real culprit and how to fix it.]]>
      </summary>
      <updated>2025-03-17T00:00:00+00:00</updated>
      <link href="https://www.schneems.com/2025/03/17/installing-the-sassc-ruby-gem-on-a-mac-a-debugging-story/" rel="alternate" type="text/html" title="Installing the sassc Ruby gem on a Mac. A debugging story" />
    </entry>
  
</feed>
