<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="https://www.w3.org/2005/Atom">
  <channel>
    <title>Schneems - Programming Practices, Performance, and Pedantry</title>
    <description>Ruby Hero. Runs CodeTriage.com. Works for Heroku. Posts on programming and open source."
</description>
    <link>https://www.schneems.com/</link>
    <atom:link href="https://www.schneems.com/feed.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>It's dangerous to go alone, `pub` `mod` `use` this.rs</title>
        <description>&lt;p&gt;What exactly does &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt; do in Rust? And how exactly do I “require” that other file I just made? This article is what I wish I could have given a younger me.&lt;/p&gt;

&lt;p&gt;This post starts with a fast IDE-centric tutorial requiring little prior knowledge. Good if you have a Rust project and want to figure out how to split up files. Afterward, I’ll dig into details so you can understand how to reason about file loading and modules in Rust.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Tip: If you enjoy this article, check out my book &lt;a href=&quot;https://howtoopensource.dev/&quot;&gt;How to Open Source&lt;/a&gt; to help you transform from a coder to an open source contributor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;tutorial-naievely-requireload-ing-files-in-rust&quot;&gt;Tutorial: Naievely require/load-ing files in Rust&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Skip this if: You’re the type of person who likes to see all the ingredients before you see the cake.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this tutorial, I’ll be using VS Code and the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer&quot;&gt;rust analyzer extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With that software installed, create a new Rust project via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo new&lt;/code&gt; command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cargo new file-practice
     Created binary (application) `file-practice` package
$ cd file-practice
$ exa --tree --git-ignore ./src
./src
└── main.rs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exa&lt;/code&gt; command isn’t required. I’m using it to show file hierarchy. You can install it on Mac via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew install exa&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; directory, there’s only one file, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.rs&lt;/code&gt;. Since some people think tutorial apps are a joke, let’s make an app that tells jokes.&lt;/p&gt;

&lt;p&gt;Create a new file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;joke.rs&lt;/code&gt; by running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ touch src/joke.rs
$ exa --tree --git-ignore ./src
./src
├── joke.rs
└── main.rs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Add a function to the file:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Want to hear a joke?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then modify your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; file to use this code:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/main.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code fails with an error:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cargo test
error[E0425]: cannot find function `want_to_hear_a_joke` in this scope
 --&amp;gt; src/main.rs:4:5
  |
4 |     want_to_hear_a_joke();
  |     ^^^^^^^^^^^^^^^^^^^ not found in this scope

For more information about this error, try `rustc --explain E0425`.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You cannot use the code in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; code as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; cannot find it.&lt;/p&gt;

&lt;p&gt;When you create a file in a Rust project and get an error that it cannot be found, navigate to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; file in your VS Code editor and hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD+.&lt;/code&gt; (command key and period on Mac, or control period on Windows). You’ll get a “quick fix” prompt asking if you want:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“insert &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod joke;&lt;/code&gt;”&lt;/li&gt;
  &lt;li&gt;“insert &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub mod joke;&lt;/code&gt;”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your editor may look different than mine, but note the quick-fix menu right under my cursor:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://capture.dropbox.com/cDaZUgrZP3IiRIOK?raw=1&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you hit enter, then your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; should now look like this:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/main.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;watch-for-changes-with-cargo-watch&quot;&gt;Watch for changes with cargo-watch&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Skip if: You already know how to use cargo-watch in the VS Code terminal&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run your tests on save in the vscode terminal with cargo watch. You can open a terminal by pressing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD+SHIFT+P&lt;/code&gt; (command shift “p” on Mac or control shift p on Windows). Then type in “toggle terminal” and hit enter. This will bring up the terminal. Then, install &lt;a href=&quot;https://crates.io/crates/cargo-watch&quot;&gt;cargo watch&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cargo install cargo-watch
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now run the watch command in your terminal:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cargo watch -c -x test
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This command tells &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;watch&lt;/code&gt; the file system for changes on disk, then clear (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-c&lt;/code&gt;) the window and execute tests (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-x test&lt;/code&gt;). This will make iteration faster:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://capture.dropbox.com/2FFU2XzxKe9AaAIC?raw=1&quot; alt=&quot;Screenshot&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;back-to-the-tutorial&quot;&gt;Back to the tutorial&lt;/h2&gt;

&lt;p&gt;Make sure cargo watch is running and save the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; file. Note that tests are failing since the program still cannot compile:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   Compiling file-practice v0.1.0 (/private/tmp/file-practice)
error[E0425]: cannot find function `want_to_hear_a_joke` in this scope
 --&amp;gt; src/main.rs:6:5
  |
6 |     want_to_hear_a_joke();
  |     ^^^^^^^^^^^^^^^^^^^ not found in this scope
  |
note: function `crate::joke::want_to_hear_a_joke` exists but is inaccessible
 --&amp;gt; src/joke.rs:2:1
  |
2 | fn want_to_hear_a_joke() {
  | ^^^^^^^^^^^^^^^^^^^^^^^^ not accessible

For more information about this error, try `rustc --explain E0425`.
error: could not compile `file-practice` due to previous error
[Finished running. Exit status: 101]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The error “exists but is inaccessible” is similar to what we saw before but with additional information. If you run that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rustc&lt;/code&gt; command, it suggests:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ rustc --explain E0425
...
If the item you are importing is not defined in some super-module of the current module must also be declared as public (e.g., `pub fn`).
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The very last line gives us a great clue. We need to update the function to be public. Edit your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; file to make the function public:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Want to hear a joke?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On save, it still fails, but we’ve got a different message (always a good thing):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;error[E0425]: cannot find function `want_to_hear_a_joke` in this scope
 --&amp;gt; src/main.rs:6:5
  |
6 |     want_to_hear_a_joke();
  |     ^^^^^^^^^^^^^^^^^^^ not found in this scope
  |
help: consider importing this function
  |
2 | use crate::joke::want_to_hear_a_joke;
  |

For more information about this error, try `rustc --explain E0425`.
error: could not compile `file-practice` due to previous error
[Finished running. Exit status: 101]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It suggests “consider importing this function” by adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use crate::joke::want_to_hear_a_joke;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt;. Use the quick-fix menu &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD+.&lt;/code&gt; on the function in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://capture.dropbox.com/puwGvtCE2IjXSaC5?raw=1&quot; alt=&quot;screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After accepting that option, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; now looks like this:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/main.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;crate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When I save the file, it compiles!&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   Compiling file-practice v0.1.0 (/private/tmp/file-practice)
    Finished test [unoptimized + debuginfo] target(s) in 1.34s
     Running unittests src/main.rs (target/debug/deps/file_practice-22ac5cd70121e9ea)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

[Finished running. Exit status: 0]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To recap what happened here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We created a new file, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;To make it visible to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt;, we needed to add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt; statement. We used VS Code &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD+.&lt;/code&gt; to generate the mod statement.&lt;/li&gt;
  &lt;li&gt;We then updated the function in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; to be public (added a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub&lt;/code&gt; based on the compiler detail error message)&lt;/li&gt;
  &lt;li&gt;Finally, we needed to add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; so our code could call it. This was suggested both by the compiler and VS Code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t have to memorize EVERYTHING required. All in all, our tools either did the work or gave us a strong hint as to what to do next. Start mapping if-this-error to then-that-fix behavior while learning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt;, and file loading.&lt;/p&gt;

&lt;h2 id=&quot;tutorial-importing-nested-files&quot;&gt;Tutorial: Importing nested files&lt;/h2&gt;

&lt;p&gt;To import code from a file in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/&lt;/code&gt; directory into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt;, you must add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt; declaration to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;How do you add files in a different directory?&lt;/p&gt;

&lt;p&gt;Let’s say we want to tell several kinds of jokes, so we split them into a directory and different files. Use the results of the first tutorial and add to it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mkdir src/joke
$ touch src/joke/knock_knock.rs
$ touch src/joke/word_play.rs
$ exa --tree --git-ignore ./src
./src
├── joke
│  ├── knock_knock.rs
│  └── word_play.rs
├── joke.rs
└── main.rs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now add some code that we can import. Write a joke into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke/knock_knock.rs&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke/knock_knock.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tank_knocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Knock knock.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Who's there?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Tank&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Tank who?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;You're welcome!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And another into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke/word_play.rs&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke/word_play.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Where do cows go on a date&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;To the Moooooo-vies&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With these files saved, use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD+.&lt;/code&gt; quick-fix menu, which will prompt you to insert a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt;. Select the first option on both files and save both.&lt;/p&gt;

&lt;p&gt;You might be surprised it didn’t modify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; like our first tutorial. Instead, the extension modified the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; with these additions:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;// &amp;lt;== New mod&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== New mod&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Want to hear a joke?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;When you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt; in your crate root (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt;), it references a file in the same directory (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/&amp;lt;name&amp;gt;.rs&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;When you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt; in a non-root file (like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt;), it references a file in a directory with that name (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke/&amp;lt;name&amp;gt;.rs&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; to use the contents of those modules now:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Want to hear a joke?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;      &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;
  &lt;span class=&quot;nn&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tank_knocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These two lines implicitly use the module imported above to its own namespace. You can also make this explicit using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; keyword:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Want to hear a joke?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;      &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tank_knocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can save and run this code. Make sure you’re sitting down so you don’t end up rolling on the floor laughing.&lt;/p&gt;

&lt;p&gt;To recap what happened here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We created two files in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke/&lt;/code&gt; directory. Each file has one public function.&lt;/li&gt;
  &lt;li&gt;We added two mod statements to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; (via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CTRL+.&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;We used those two new namespaces inside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; to call the two new functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;calling-cow_date-from-main&quot;&gt;Calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cow_date&lt;/code&gt; from main&lt;/h2&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;want_to_hear_a_joke&lt;/code&gt; function will output two jokes. Sometimes my kids think a joke is so funny that they want to hear it again. Let’s add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cow_date&lt;/code&gt; again. This time put it directly in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main() {}&lt;/code&gt; function:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/main.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;crate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you save, you’ll get an error:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   Compiling file-practice v0.1.0 (/private/tmp/file-practice)
error[E0425]: cannot find function `cow_date` in this scope
 --&amp;gt; src/main.rs:8:5
  |
8 |     cow_date();
  |     ^^^^^^^^ not found in this scope
  |
note: function `crate::joke::word_play::cow_date` exists but is inaccessible
 --&amp;gt; src/joke/word_play.rs:2:1
  |
2 | pub fn cow_date() {
  | ^^^^^^^^^^^^^^^^^ not accessible

For more information about this error, try `rustc --explain E0425`.
error: could not compile `file-practice` due to previous error
[Finished running. Exit status: 101]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ve seen this error before “cannot find function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;name&amp;gt;&lt;/code&gt; in this scope”. The help ends with this line:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“If the item you are importing is not defined in some super-module of the current module, then it must also be declared as public (e.g., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub fn&lt;/code&gt;).”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s not super helpful since the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cow_date&lt;/code&gt; function is already public:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke/word_play.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Note the `pub` here&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Where do cows go on a date&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;To the Moooooo-vies&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Before we saw that these two invocations are basically the same thing:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke.rs&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Explicit self&lt;/span&gt;
      &lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Implicit self&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So you might guess that calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cow_date()&lt;/code&gt; inside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; is the same as calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self::cow_date()&lt;/code&gt;, which makes it a bit more explicit. You might also notice that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cow_date&lt;/code&gt; is nowhere in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt;. Where did it come from?&lt;/p&gt;

&lt;p&gt;That function came from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke/word_play.rs&lt;/code&gt;, but Rust cannot find it. The first time we called the function, we started with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; and traversed the path. Let’s try that same technique:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/main.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;crate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== HERE&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Did that work?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;error[E0603]: module `word_play` is private
 --&amp;gt; src/main.rs:8:17
  |
8 |     self::joke::word_play::cow_date();
  |                 ^^^^^^^^^ private module
  |
note: the module `word_play` is defined here
 --&amp;gt; src/joke.rs:2:1
  |
2 | mod word_play;
  | ^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0603`.
error: could not compile `file-practice` due to previous error
[Finished running. Exit status: 101]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;No. But, our message changed again (always worth celebrating). Before, the error said that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cow_date&lt;/code&gt; was “inaccessible.” Now it’s saying that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;word_play&lt;/code&gt; is a private module.&lt;/p&gt;

&lt;p&gt;It points at a line in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt;, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;word_play&lt;/code&gt; is defined for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self::joke::word_play&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hover over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;word_play&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; and press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD+.&lt;/code&gt;. It asks if you want to change the visibility of the module:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://capture.dropbox.com/i00cCJhE7iP3SDAb?raw=1&quot; alt=&quot;Screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Accept that change and save. Then the file compiles!&lt;/p&gt;

&lt;p&gt;To recap what happened here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We added a function call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cow_date()&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Main.rs couldn’t find our code, so we used the full path &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self::joke::word_play::cow_date()&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;That gave us an error hinting the problem was privacy related.&lt;/li&gt;
  &lt;li&gt;We changed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod word_play;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub(crate) mod word_play;&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; (by using the quick fix menu &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD+.&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Now the program works&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might wonder, “What’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub(crate)&lt;/code&gt; and how is it different from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub(crate)&lt;/code&gt; declaration sets visibility to public but is limited to the crate’s scope. This is less privileged than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub&lt;/code&gt;. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub&lt;/code&gt; declaration allows a different crate with access to your code (via FFI or a library) to use that code. By setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub(crate)&lt;/code&gt;, you indicate a semi-private state. Changing that code might affect code in other files of your project, but it won’t break other people’s code.&lt;/p&gt;

&lt;p&gt;I prefer using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub(crate)&lt;/code&gt; by default and only elevating to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub&lt;/code&gt; as needed. However, the core part of this exercise was seeing how far we could get by letting our tools figure out the problem for us.&lt;/p&gt;

&lt;h2 id=&quot;how-can-i-load-that-file&quot;&gt;How can I load that file?&lt;/h2&gt;

&lt;p&gt;If all you want to do is put code in a file and load it from another file, you’re good to go. This is the high-level cheatsheet for what we did above:&lt;/p&gt;

&lt;p&gt;Reference Rust code in a file fast by:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Starting from the bottom of your file tree in the project, use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD+.&lt;/code&gt; and insert mod statements.&lt;/li&gt;
  &lt;li&gt;Continue the first step until you’ve reached the root of your crate (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/lib.rs&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Try to use the code where you want while making the best guess for the correct path (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self::joke::word_play::cow_date&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Use compiler errors and the quick-fix tool (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD+.&lt;/code&gt;) to insert &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; statements or adjust visibility with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub(crate)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-is-that-modrs-file&quot;&gt;What is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod.rs&lt;/code&gt; file?&lt;/h2&gt;

&lt;p&gt;If you use the above methodology, that rust analyzer will create files. There are two ways to load files from a directory. Before, I used the convention that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; loaded all the files in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke&lt;/code&gt; directory. That’s &lt;a href=&quot;https://doc.rust-lang.org/reference/items/modules.html&quot;&gt;technically the preferred way&lt;/a&gt; of loading files in a directory, but there’s one other method which is: putting a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod.rs&lt;/code&gt; file in the directory you want to expose.&lt;/p&gt;

&lt;p&gt;In short, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke/mod.rs&lt;/code&gt; would do the same thing as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt;. Please don’t take my word for it. Try it out:&lt;/p&gt;

&lt;p&gt;Here’s the current file structure:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ exa --tree --git-ignore ./src
./src
├── joke
│  ├── knock_knock.rs
│  └── word_play.rs
├── joke.rs
└── main.rs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now move the joke file to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke/mod.rs&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mv src/joke.rs src/joke/mod.rs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now here’s what our directory looks like&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ exa --tree --git-ignore ./src
./src
├── joke
│  ├── knock_knock.rs
│  ├── mod.rs
│  └── word_play.rs
└── main.rs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you re-run tests. They still pass! As far as Rust is concerned, this code is identical to what we had before.&lt;/p&gt;

&lt;h2 id=&quot;information-dump&quot;&gt;Information dump&lt;/h2&gt;

&lt;p&gt;Now that you’ve tasted our lovely IDE productivity cake. It’s time to learn about each of the ingredients. Here’s what we’ll cover:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Code paths in Rust
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; and (unqualified)&lt;/li&gt;
      &lt;li&gt;Differences between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; and (unqualified)&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;Renaming paths with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Glob imports with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Import an extension trait with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;A file state cheatsheet with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; cheatsheet&lt;/li&gt;
  &lt;li&gt;Modules != files&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;code-paths-in-rust&quot;&gt;Code paths in Rust&lt;/h2&gt;

&lt;p&gt;In the above example, we saw that we can reference code via a path starting with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self::joke::word_play::cow_date&lt;/code&gt;. Here’s what you can start a code path within Rust:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;(unqualified)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;code-paths-starting-with-self-and-unqualified&quot;&gt;Code paths starting with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; and (unqualified)&lt;/h3&gt;

&lt;p&gt;Starting a path with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; indicates that the code path is relative to the current module. Before we saw this code:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Want to hear a joke?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;      &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tank_knocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this case, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; is inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; module. This keyword is optional in this case. You can remove it, and the code will behave exactly the same. Most published libraries do not use ‘ self ‘ because it’s less typing to omit the word altogether. Most would write that code like this:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Want to hear a joke?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;      &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;
  &lt;span class=&quot;nn&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tank_knocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this code, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; is implied because we’re not using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; can be helpful as a reminder that you’re using a relative path when you’re starting. If you’re struggling to correct a path, try mentally substituting the module’s name (in this case, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;joke&lt;/code&gt;) for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; keyword as a litmus test to see if it still makes sense.&lt;/p&gt;

&lt;h3 id=&quot;differences-between-self-and-unqualified&quot;&gt;Differences between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; and (unqualified)&lt;/h3&gt;

&lt;p&gt;While it might seem that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; and unqualified are interchangeable, they are not. This code will compile without self:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// Compiles fine&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, this code will not:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// Fails to compile&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With error:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;error[E0433]: failed to resolve: could not find `println` in the crate root
 --&amp;gt; src/main.rs:6:11
  |
6 |     self::println!(&quot;Hello, world!&quot;);
  |           ^^^^^^^ could not find `println` in the crate root
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In addition to being an implicit reference to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt;, using an unqualified path also allows you access to elements that ship with Rust, like the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;println!&lt;/code&gt; macro or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std&lt;/code&gt; namespace:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Cargo.toml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self::&lt;/code&gt; in front of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::fs&lt;/code&gt; above, it would fail to compile.&lt;/p&gt;

&lt;p&gt;Using an unqualified path also gives you access to any crates you import via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cargo.toml&lt;/code&gt;. For example, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;regex&lt;/code&gt; crate:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cargo add regex
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;regex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Regex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;lol&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self::&lt;/code&gt; in front of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;regex&lt;/code&gt; above, it would fail to compile.&lt;/p&gt;

&lt;h3 id=&quot;code-paths-starting-with-crate&quot;&gt;Code paths starting with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;A code path that starts with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate&lt;/code&gt; is like an absolute file path. This keyword maps to the crate root (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/lib.rs&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In our above example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; was also &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate&lt;/code&gt; since we were in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt;, so we could have written this code like this:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/main.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As this:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/main.rs&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;crate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; refers to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt;, these two lines of code are identical. However, if you try to copy and paste them to another file, only the one that starts from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate&lt;/code&gt; will continue to work.&lt;/p&gt;

&lt;p&gt;You can use an absolute path in any code module as long as all the parts of that path are visible to the current module. For example, here’s the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; file using a mix of absolute and relative paths:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;crate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Want to hear a joke?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nn&quot;&gt;crate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Absolute&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tank_knocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Relative&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cow_date&lt;/code&gt; via its absolute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo&lt;/code&gt; path and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tank_knocks&lt;/code&gt; from a relative path.&lt;/p&gt;

&lt;h3 id=&quot;code-paths-starting-with-super&quot;&gt;Code paths starting with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super&lt;/code&gt; path references the path of a parent. In this case, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super&lt;/code&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/joke.rs&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/&lt;/code&gt; (otherwise known as the crate root). You can re-write the above code using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super&lt;/code&gt; then as a replacement for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate&lt;/code&gt; like this:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// src/joke.rs&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;crate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;want_to_hear_a_joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Want to hear a joke?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nn&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;joke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;word_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cow_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== HERE&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;knock_knock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tank_knocks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;code-path-recap&quot;&gt;Code path recap&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; - A relative path from the current module&lt;/li&gt;
  &lt;li&gt;(unqualified) - Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; but allows for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std&lt;/code&gt;, crate calls, and more&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate&lt;/code&gt; - An absolute path from crate root (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main.rs&lt;/code&gt; or `src/lib.rs).&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super&lt;/code&gt; - Relative path to the parent of the current module.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;using-use&quot;&gt;Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Renaming paths with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Glob imports with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Import an extension trait with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;renaming-paths-with-use&quot;&gt;Renaming paths with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;Skip this if: You know how to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; to rename modules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Rust, there is a filesystem module &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::fs&lt;/code&gt;, but you don’t have to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; it to well…use it. You can type out the full path:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;PathBuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/tmp/lol.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== HERE&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{out}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instead of writing out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::path::PathBuf&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::fs&lt;/code&gt; everywhere, you could tell Rust that you want to map a shorthand and rename it:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PathBuf&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PathBuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;                 &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;PathBuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/tmp/lol.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{out}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code says, “Anytime you see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PathBuf&lt;/code&gt; in this file, know what I mean is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::path::PathBuf&lt;/code&gt;”. This pattern is so common you don’t need the repetition of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;as&lt;/code&gt; at the end. Since you’re &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;-ing it as the same name, you can write this code:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PathBuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Note there's no `as`&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;            &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Note there's no `as`&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;PathBuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/tmp/lol.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{out}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All three programs are exactly the same. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; does not &lt;strong&gt;do&lt;/strong&gt; anything. It simply renames things, usually for convenience.&lt;/p&gt;

&lt;h3 id=&quot;glob-imports-with-use&quot;&gt;Glob imports with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Beyond importing a namespace or a single item (such as an enum, struct, or function), you can import ALL the items in a namespace using a glob import.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Note glob import&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Note glob import&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;PathBuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/tmp/lol.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read_to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{out}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this code, we can call the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_to_string&lt;/code&gt; function without naming it above. That’s because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_to_string&lt;/code&gt; exists at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::fs::read_to_string&lt;/code&gt;, so when we import &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::fs::*&lt;/code&gt;, it gets imported along with all of its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::fs::*&lt;/code&gt; friends.&lt;/p&gt;

&lt;p&gt;This is generally discouraged because two imports may conflict. If one-day &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::path&lt;/code&gt; introduces a new function named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::path::read_to_string&lt;/code&gt;, then there would be a conflict, and your code would fail to compile.&lt;/p&gt;

&lt;p&gt;While glob imports might save time typing, they increase the mental load while reading. They’re not currently considered “idiomatic.” The exception would be for a &lt;a href=&quot;https://stackoverflow.com/questions/36384840/what-is-the-prelude&quot;&gt;prelude file&lt;/a&gt; within a crate.&lt;/p&gt;

&lt;h3 id=&quot;import-an-extension-trait-with-use&quot;&gt;Import an extension trait with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Beyond renaming imports, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; can change your code’s behavior!&lt;/p&gt;

&lt;p&gt;In Rust, you can define a &lt;a href=&quot;https://doc.rust-lang.org/stable/book/ch10-02-traits.html&quot;&gt;trait&lt;/a&gt; on a struct that wasn’t defined in the same library through something known as an &lt;a href=&quot;https://rauljordan.com/rust-concepts-i-wish-i-learned-earlier/#understand-the-concept-of-extension-traits-when-developing-rust-libraries&quot;&gt;“extension trait”&lt;/a&gt;. This might sound exotic and weird, but it’s common. Even Rust core code uses this feature.&lt;/p&gt;

&lt;p&gt;For example, this code will not compile:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{number}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll get this compile error:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   Compiling demo v0.1.0 (/private/tmp/demo)
error[E0599]: no function or associated item named `from_str` found for type `u64` in the current scope
 --&amp;gt; src/main.rs:2:23
  |
2 |     let number = u64::from_str(&quot;99&quot;).unwrap();
  |                       ^^^^^^^^ function or associated item not found in `u64`
  |
  = help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
  |
1 | use std::str::FromStr;
  |
help: there is an associated function with a similar name
  |
2 |     let number = u64::from_str_radix(&quot;99&quot;).unwrap();
  |                       ~~~~~~~~~~~~~~

For more information about this error, try `rustc --explain E0599`.
error: could not compile `demo` due to previous error
[Finished running. Exit status: 101]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But if you add a trait via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;, now this code will compile:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FromStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// &amp;lt;== Here&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{number}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Why did that work? In this case, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; statement on the first line changes the code by bringing the &lt;a href=&quot;https://doc.rust-lang.org/std/str/trait.FromStr.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FromStr&lt;/code&gt; trait&lt;/a&gt; into scope. Also, Rust was clever enough to realize that it &lt;strong&gt;could&lt;/strong&gt; compile if we added a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::str::FromStr&lt;/code&gt; onto the code. It’s right there in the suggestion. Neat!&lt;/p&gt;

&lt;p&gt;This behavior change confused me for the longest time as most &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; documentation and tutorials just beat the “Rust does not have imports, only renaming” drum to the point that it’s not helpful information. Confusingly enough, if you define the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FromStr&lt;/code&gt; trait on your own struct, you’ll have to also &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use std::str::FromStr&lt;/code&gt; everywhere you want to use that behavior too.&lt;/p&gt;

&lt;h2 id=&quot;filestate-permutations&quot;&gt;File/State permutations&lt;/h2&gt;

&lt;p&gt;The various permutations of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; can be confusing, to say the least. I wrote this out as an exercise in understanding them. It’s more useful as a reference:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Adding the code &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod &amp;lt;name&amp;gt;;&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;Inside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;.rs&lt;/code&gt; will:
        &lt;ul&gt;
          &lt;li&gt;Allow access to contents of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;/&amp;lt;name&amp;gt;.rs&lt;/code&gt; via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;name&amp;gt;::&amp;lt;component&amp;gt;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;Allow access to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;/&amp;lt;name&amp;gt;/mod.rs&lt;/code&gt; via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;name&amp;gt;::&amp;lt;component&amp;gt;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;Allow access of submodules of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;&lt;/code&gt; to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a module root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;/mod.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Allow access to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;/&amp;lt;name&amp;gt;.rs&lt;/code&gt; via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;name&amp;gt;::&amp;lt;component&amp;gt;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;Allow access to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;/&amp;lt;name&amp;gt;/mod.rs.rs&lt;/code&gt; via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;name&amp;gt;::&amp;lt;component&amp;gt;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;Allow access of submodules of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;&lt;/code&gt; to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a crate root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.rs&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Allow access to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/&amp;lt;name&amp;gt;.rs&lt;/code&gt; via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;name&amp;gt;::&amp;lt;component&amp;gt;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;Allow access to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/&amp;lt;name&amp;gt;/mod.rs.rs&lt;/code&gt; via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;name&amp;gt;::&amp;lt;component&amp;gt;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;Allow access to submodules of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate&lt;/code&gt; to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate::&amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Adding the code  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub(crate) mod &amp;lt;name&amp;gt;;&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;Inside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;.rs&lt;/code&gt; will:
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod &amp;lt;name&amp;gt;&lt;/code&gt; plus.&lt;/li&gt;
          &lt;li&gt;Allow super to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a module root (i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;/mod.rs&lt;/code&gt;).
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod &amp;lt;name&amp;gt;&lt;/code&gt; plus.&lt;/li&gt;
          &lt;li&gt;Allow super to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt;&amp;gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a crate root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.rs&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod &amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;No additional effect.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Adding the code &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use &amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt; (relative path)
    &lt;ul&gt;
      &lt;li&gt;Inside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;.rs&lt;/code&gt; will:
        &lt;ul&gt;
          &lt;li&gt;Add an alias from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;Bring an extention trait into scope (if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; is an extension trait).&lt;/li&gt;
          &lt;li&gt;Allow sub modules to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; without using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a module root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;/mod.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Add an alias from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;Bring an extention trait into scope (if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; is an extension trait).&lt;/li&gt;
          &lt;li&gt;Allow sub modules to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; without using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a crate root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.rs&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Add an alias from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;Bring an extention trait into scope (if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; is an extension trait).&lt;/li&gt;
          &lt;li&gt;Allow all modules to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate::&amp;lt;name&amp;gt;&lt;/code&gt; without using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Adding the code  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub(crate) use &amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt; (relative path) will do everything as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use &amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt; plus:
    &lt;ul&gt;
      &lt;li&gt;Inside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;.rs&lt;/code&gt;
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use &amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt; plus.&lt;/li&gt;
          &lt;li&gt;Allow super to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; without using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a module root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;/mod.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use &amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt; plus.&lt;/li&gt;
          &lt;li&gt;Allow super to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; without using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a crate root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.rs&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use &amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Adding the code  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use crate::&amp;lt;module-path&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; (absolute path)
    &lt;ul&gt;
      &lt;li&gt;All modules in the path must be reachable with public visibility to crate root or
there will be an error.&lt;/li&gt;
      &lt;li&gt;Inside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;.rs&lt;/code&gt;
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use &amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt; but the full path must be reachable from crate root.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a crate root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.rs&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use &amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt; but the full path must be reachable from crate root.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a module root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;/mod.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use &amp;lt;module&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt; but the full path must be reachable from crate root.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Adding the code  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub(crate) use crate::&amp;lt;module-path&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; (absolute path)
    &lt;ul&gt;
      &lt;li&gt;Inside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;.rs&lt;/code&gt;
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use crate::&amp;lt;module-path&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt;&lt;/li&gt;
          &lt;li&gt;Allow all modules to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate::&amp;lt;path-to-filename&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; without referencing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module-path&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a module root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;/mod.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use crate::&amp;lt;module-path&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;Allow all modules to access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crate::&amp;lt;path-to-dirname&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt; without referencing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;module-path&amp;gt;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Inside of a crate root (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.rs&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib.rs&lt;/code&gt;)
        &lt;ul&gt;
          &lt;li&gt;Same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use crate::&amp;lt;module-path&amp;gt;::&amp;lt;name&amp;gt;;&lt;/code&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;modules--files&quot;&gt;Modules != files&lt;/h2&gt;

&lt;p&gt;I’ve focused on mapping modules and files, but you can use modules without files. &lt;a href=&quot;https://doc.rust-lang.org/rust-by-example/mod.html&quot;&gt;Rust by example modules&lt;/a&gt; gives some examples. You’ll likely find modules used without filenames as you go through various docs on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; or the module system. Aside from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod.rs&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dirname&amp;gt;/&amp;lt;modulename&amp;gt;.rs&lt;/code&gt;, most features map 1:1 whether or not your modules are backed by a file system.&lt;/p&gt;

&lt;p&gt;I really wanted to call this a “comprehensive” guide, but there’s more to this rabbit hole. If you want a depth-first kind of learner, you can dive into some further reading:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The Rust Reference (not THE book) - Talks about this stuff in detail
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://doc.rust-lang.org/reference/items/modules.html&quot;&gt;Modules&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://doc.rust-lang.org/reference/items/use-declarations.html&quot;&gt;Use declarations&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://doc.rust-lang.org/edition-guide/rust-2018/path-changes.html&quot;&gt;Rust edition guide&lt;/a&gt; talks about changes to the module system.&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 14 Jun 2023 00:00:00 +0000</pubDate>
        <link>https://www.schneems.com/2023/06/14/its-dangerous-to-go-alone-pub-mod-use-thisrs/</link>
        <guid isPermaLink="true">https://www.schneems.com/2023/06/14/its-dangerous-to-go-alone-pub-mod-use-thisrs/</guid>
      </item>
    
      <item>
        <title>What is github.com/zombocom and why most of my Ruby libraries there?</title>
        <description>&lt;p&gt;The other day I got another question about the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zombocom&lt;/code&gt; org on GitHub that prompted me to write this post. This org, &lt;a href=&quot;https://github.com/zombocom&quot;&gt;github.com/zombocom&lt;/a&gt;, holds most all of my popular libraries). Why put them in a custom GitHub org, and why name it zombocom? Let’s find out.&lt;/p&gt;

&lt;h2 id=&quot;why-a-custom-org&quot;&gt;Why a custom org?&lt;/h2&gt;

&lt;p&gt;If you’re maintaining one or two libraries, keeping them in your GitHub user’s namespace is easy enough. For me, this is &lt;a href=&quot;https://github.com&quot;&gt;https://github.com/schneems&lt;/a&gt;. The zombocom org has 18 libraries, 16 of which I created.&lt;/p&gt;

&lt;p&gt;I want to encourage people to contribute to my libraries, so I’ve taken to giving commit access to developers who land a successful PR. I still control releasing, so this permissive default is intended to allow developers with ambition to express themselves while giving me an opportunity to QA and chime in on changes.&lt;/p&gt;

&lt;p&gt;I wanted to take this a step further and give them access to ALL my libraries to make it even easier to contribute. This strategy is time-consuming if my libraries are all under &lt;a href=&quot;https://github.com/schneems&quot;&gt;github.com/schneems&lt;/a&gt;, so I moved them into a custom org name and called it a day.&lt;/p&gt;

&lt;h2 id=&quot;why-name-it-zombocom&quot;&gt;Why name it zombocom?&lt;/h2&gt;

&lt;p&gt;Making a custom org name is familiar for top-rated gems/libraries. For example, &lt;a href=&quot;https://github.com/puma&quot;&gt;github.com/puma&lt;/a&gt; or &lt;a href=&quot;https://github.com/codetriage&quot;&gt;github.com/CodeTriage&lt;/a&gt;. My most popular library on zombocom is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_process_mem&lt;/code&gt; with 52 million downloads. But an org with that same name would be overly restrictive.&lt;/p&gt;

&lt;p&gt;Custom orgs are usually named around a common theme. But my libraries don’t have a shared theme aside from being a thing I wrote that hopefully makes your life easier.&lt;/p&gt;

&lt;p&gt;I didn’t want 16 namespaces for 16 libraries. I wanted a place where anything was possible. So I named it after &lt;a href=&quot;https://zombo.com&quot;&gt;Zombo.com&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Anything is possible at Zombocom. The infinite is possible at Zombocom. The unobtainable is unknown at Zombocom. Welcome to Zombocom&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another org inspired this move &lt;a href=&quot;https://github.com/sparklemotion&quot;&gt;github.com/sparklemotion&lt;/a&gt;. This org was initially used for Nokogiri (the most popular XML/HTML parsing library for Ruby), and other libraries were added over time. The name is a joke from the movie Donnie Darko:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Sometimes I Doubt Your Commitment To Sparkle Motion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Nokogiri library and the sparklemotion org were created by &lt;a href=&quot;https://github.com/sparklemotion/nokogiri/commit/e7f98b6cb8e4b49da26aa3bd70f415fac2af5ac3&quot;&gt;Aaron Patterson, aka tenderlove&lt;/a&gt;. I asked him about it at a conference once, and he said he made it for similar reasons.&lt;/p&gt;

&lt;p&gt;Some ask if I also made &lt;a href=&quot;https://zombo.com&quot;&gt;Zombo.com&lt;/a&gt;, but the answer is no. I have nothing to do with that site and don’t know who originally wrote it. The name is a tribute to a meme before memes. If the author ever wants to share my org name to put their website source, that would be fun. After all, anything is possible.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;If you want to get started in open source, but need a helping hand, check out my (paid) book &lt;a href=&quot;https://howtoopensource.dev&quot;&gt;How to Open Source&lt;/a&gt; or (free) service &lt;a href=&quot;https://www.codetriage.com&quot;&gt;CodeTriage&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 24 Feb 2023 00:00:00 +0000</pubDate>
        <link>https://www.schneems.com/2023/02/24/what-is-githubcomzombocom-and-why-most-of-my-ruby-libraries-there/</link>
        <guid isPermaLink="true">https://www.schneems.com/2023/02/24/what-is-githubcomzombocom-and-why-most-of-my-ruby-libraries-there/</guid>
      </item>
    
      <item>
        <title>Pairing on Open Source</title>
        <description>&lt;p&gt;I came to love pairing after I hurt my hands and couldn’t type. I had to finish up the last 2 months of a graduate CS course without the ability to use a keyboard. I had never paired before but enlisted several other developers to type for me. After I got the hang of the workflow, I was surprised that even when coding in a language my pair had never written in (C or C++), they could spot bugs and problems as we went. Toward the end, I finished the assignments faster when I wasn’t touching the keyboard, than I was by myself. Talking aloud forced me to refine my thoughts before typing anything. It might be intimidating to try pairing for the first time, but as Ben puts “it’s just a way of working together.”&lt;/p&gt;

&lt;p&gt;This Hacktoberfest, I started a Slack group for the ~350 early purchasers of my book &lt;a href=&quot;https://howtoopensource.dev&quot;&gt;How to Open Source&lt;/a&gt;. In the intake survey, they told me they wanted to learn more about pairing. When I think pairing, I think of Ben Orenstein, CEO of the pairing app &lt;a href=&quot;https://tuple.app&quot;&gt;tuple.app&lt;/a&gt;. I jumped on Twitter and asked if I could interview him for the group, and he agreed!&lt;/p&gt;

&lt;p&gt;Listen in as we discuss the intersection of pairing and open source contribution. We’ll talk about how it’s different from regular pairing (or not), how to find people to pair with, and the best way to ask for help from a potential mentor.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Tml2uXn65Co&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;In addition to talking about pairing in the group, we had developers who organized together to pair for Hacktoberfest. One made their first-ever contribution after their first pairing session. Then she wrote her &lt;a href=&quot;https://neko314.hatenablog.com/entry/2022/10/19/164953&quot;&gt;first ever English blog post about the experience&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;topics&quot;&gt;Topics&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Is pairing on open source different?&lt;/li&gt;
  &lt;li&gt;How do you find people to pair with?&lt;/li&gt;
  &lt;li&gt;Can you match people in a group to pair?&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.focusmate.com/&quot;&gt;FocusMate.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Can developers of the same level pair?&lt;/li&gt;
  &lt;li&gt;What research has been done on pairing?&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learntopair.com/&quot;&gt;LearnToPair.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;How would you introduce pairing into a team?&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tuple.app/oss&quot;&gt;tuple.app/oss&lt;/a&gt; - Free license for open source maintainers&lt;/li&gt;
  &lt;li&gt;Can you pair on non-technical tasks?&lt;/li&gt;
  &lt;li&gt;How do you initiate pairing sessions?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;transcript&quot;&gt;Transcript&lt;/h2&gt;

&lt;p&gt;(If you find a glaring problem with the transcript you can send me a PR to https://github.com/schneems/schneems.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Yeah, let’s rock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Well, welcome everyone. My name is Richard Schneeman, author of &lt;a href=&quot;https://howtoopensource.dev&quot;&gt;How to Open Source&lt;/a&gt;. Today I have Ben, the ceo. CEO or CTO?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Ceo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Ceo. All right. Yeah, the big guns of &lt;a href=&quot;https://tuple.app&quot;&gt;tuple.app&lt;/a&gt;. So tuple is an application for pairing. It’s my favorite application for pairing coincidentally. So you wanna say Hi Ben?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Yeah, I’m stoked to be here. Pairing was a huge game changer in my career and so I’m stoked to talk about this topic that made a big difference for me professionally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Awesome, Awesome. Well I am very happy to have you. I recently launched a book and in doing so, asked as some of the people who bought it, what they’re interested in. A lot of ‘em indicated that they are really interested in pairing, so hence inviting Ben. And so yeah, wanted to ask you a couple of questions. One of ‘em that came up is just kind of high level, hey, are there differences pairing on open source software versus proprietary software?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Probably not in the actual act of pairing, I would assume. I can’t think of any differences that would be there. I think it’s probably, you might see some differences in what it takes to get people to pair with you. I’m not quite sure the willingness of open source maintainers to pair with other contributors. &lt;affirmative&gt;, you would sort of hope it was high, or maybe once you had earned a little bit of trust or shown a bit of promise, that could be a thing. That would be an easier pitch to make. If I were an open source maintainer and I sort had all the responsibility of my project, I would want to train other people on that project. So it's to help share some of the burden, I think. &lt;Right&gt;. And I haven't found a better thing than pairing for transmitting that sort of knowledge yet. So I would think, whereas if you're at a company and you're all are in the same team and you're all working on the same app, it's probably pretty easy to just DM somebody and say, Hey, can we pair on this thing?&lt;/Right&gt;&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Totally, totally. Yeah. I think and you kind of alluded to a little bit of as a maintainer, needing to balance your like, yes, you wanna train people up, but you also need to balance that with, well hey, am I ever gonna see this person again? So in order to pair you to be able to find people, it’s a little bit different in working in the open, working in open source have you ever seen any sort of pairing groups or have you seen any sort of patterns with either a people who say like, Hey, I have a thing I wanna share, or other people come to the table and say, Oh, I have a problem, or I need help with this. Just, or tips really in general for people looking for someone to pair with, but maybe they don’t necessarily have a pre-built pool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; It’s kind of a bigger question. I think the question of how do you find someone to pair with is a little bit like the question of how do you find colleagues or mentors? Perry is just a way of working together. It’s not a magical practice. It’s effective and it’s a big fan of it, but it’s not different than working with other people really. It’s just a type of working together. And so I think it really transposes to the question of how do I find great people to work with? And I think maybe a short answer to that is be worth working with. Be a friendly person, a productive person. Show some indications that you are going to be good to pair with, is probably a good way to start &lt;affirmative&gt;. I think if you email someone and say, Hey, I'm looking for a mentor, will you be my mentor?
No one wants to say yes to that. That's not a compelling pitch. But if you say, Hey, I've been a fan of your work. I loved this project and this project, you inspired me to do this thing and I ran into this problem, could you offer me, do you have any advice on how I might tackle this? Now you're starting a relationship with somebody. You've shown that you have done work, you've, you've done some homework already and you're showing that you are possibly worth mentoring. And I think that approach is probably likely to pay better dividends than saying just how do I find someone to pair with who wants to pair with me? Well, in the open source context, try to get a couple prs under your belt maybe or ask if the maintainers have considered a organizing a pairing day where maybe they connect a few people that are interested in becoming contributors or new contributors, something like that.&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; &lt;affirmative&gt;, have you seen anything like that in the wild? And I ask because I, it's like, hey, I'm trying to run this community and it's like I've got people who want pair, I have people who wanna work on project. It seemed,&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; It’s a weird thing. I’ve seen a lot of failed efforts here. I think it’s a pretty common programmer impulse to be like, I should build a site that matches pair people together that wanna pair programming. Cuz you could sort envision how the Apple work. And so a lot of programmers write it and I think it’s not a matching problem really &lt;affirmative&gt; just because these two people wanna pair in the same time zone and in the same language doesn't mean they're really gonna actually pair together. I think there's more social complex social dynamics at play than an app is going to solve. I think the closest thing I've seen to a direct effort to cause more pairing that has worked well has been inside a company. So one of the customers of Tuol is Shopify. They have thousands of developers using our product and they ran a internal pairing contest. They ran a contest and it was like whoever pairs the most this week or with the most people or the most time becomes eligible for these prizes. And I think the prizes were honestly just t-shirts and badges and things like that. I think they were fairly simple things, &lt;affirmative&gt; But I think you, I remain continually surprised by how much developers will do for a &lt;affirmative&gt;. So &lt;laugh&gt;, that might be a possible approach&lt;/laugh&gt;&lt;/affirmative&gt;&lt;/affirmative&gt;&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; There. A hundred percent. Yeah. I mean we’re definitely seeing that there’s actually a lot of engagement within the group with within Oktoberfest is just something to focus on, I feel like is a big thing. When you said that, I was like, Oh yeah, it’s like a contest. It’s like, yeah, who can pair the best? It’s who can be the most friendly &lt;laugh&gt;. Ah, I'm gonna crush it at being friendly.&lt;/laugh&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Totally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; That’s interesting. Yeah. Pairing contest. Yeah. And even one of the people in the group specifically called out and mentioned Focusmate. Have you ever heard of Focus Mate?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Yeah. That’s the thing where you go and you work with somebody kind of simultaneously, but separately,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Right? Yeah. So for just everybody else, you log into the site and you just say, Hey, I need to focus. I need basically somebody to keep me accountable. It pairs you up with someone, I mean not pairs. It matches you with someone. There we go. And then you basically just sit there for a block of time. Well you both do work. And the idea is if you start playing on your phone or something, the other person would see. So it’s, it’s working in a cafe. So yeah. Good. You’re familiar with it. Somebody specifically was asking like, Hey, have you ever considered integrating with topple? And it, it’s back with the matching issue. Not super keen on that, like you said, as being a technology issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Yeah, I don’t believe that you’re gonna pair random strangers together and have it worked that well. You might occasionally have success there if you get people with similar values and goals, you have higher success rate I’m sure. I think you probably want to pair strangers. I think you need to have a human in the loop &lt;affirmative&gt; and very motivated people. Otherwise it's kind of pairing up workout buddies where if any either person flakes, then the other person is also can't work out. It's now we're just tied together. In this particular foot race, we have considered building some things within teams. I think woodwork is saying Richard joined the team a week ago and hasn't paired with anyone. Maybe someone should pair with him or here's a leaderboard. Richard actually pairs with the most people in a given month. I wonder if anyone could catch him. Here's the second and third and fourth place kind of thing. And sort of take a thing that is already mostly working or is already an established social group and kind of give it nudges. I think that would possibly work. But this we should add matching into twofold to have people find other people to pair with broadly throughout the internet and among strangers. I'm pretty skeptical of, I might be wrong, maybe it would be amazing feature, but I have suspicions,&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; &lt;affirmative&gt;. Okay. Yeah, I makes sense.&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Your group might be a good different, might be an exception. So we did do some. So when I was running Upcase for Thought bot, which is a educational developer training service I think we did do some matching of pairs and there was some success. I wouldn’t say it was resounding success, but there was pairing happening. People reported that it actually occurred. So I think it is possible within a group that has kind of self-selected and identified, I wanna learn this thing, I’m willing to invest this effort to do this&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; &lt;affirmative&gt; for that. Within matching people. Of the times that you have mentioned pairing, it kind of sounds like you're mostly envisioning a different level. Senior, junior, I mean&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; No&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Actually no labels and whatnot or same level or I don’t know. Are there any other than values and character traits and just connection, are there any other things to look for that might make a good pair, a good pairing setup or a good pairing pair?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Pair programming is a more social endeavor than almost most programming activities. It’s live real time code review a little bit. And so just in a code coder view, you have to be a little careful about how you say things and you have to maybe use more emoji like positive emoji and more air on the side of politeness and friendliness and happiness than you might otherwise. &lt;affirmative&gt;. I think pairing has a little bit of that as well. I think a good pair is someone that has empathy and friendliness and a decent amount of easygoingness. And so I would seek to pair with people that you enjoy spending time with &lt;affirmative&gt;. If they're nice humans and pleasant and enjoy collaborating on things, then you'll probably have a good time pairing with them if they are the type to nerds snip you or while actually you all the time or be condescending. If you don't know something then you will not have a good time pairing with that person. And it's not parenting's fault, it just exposes a thing that was there.&lt;/affirmative&gt;&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Right. Yeah. There’s just already a relationship. It’s not gonna be literally a different person when they show up to the&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Yeah. Session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Yeah, that makes sense to me. So on the site you do have why pairing and there there’s sort a five second, five minute and maybe that’s not the delineation. One of the people in my group was asking if you know of any research in the world of pairing. So they were trying to bring pairing to their company and they’re basically just looking for more ammo, fire power like hey, we say pairing has done X, Y, and Z. Do you, you know of either a existing research or I don’t know, maybe ongoing research?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Yeah, if you Google scientific research into pair programming, our article is the top result I wrote. There’s not much is the short answer. So I wrote up what I found and it’s about six or so plausible studies that you could maybe say make a decent case for pairing &lt;affirmative&gt;. I think. So hopefully that will help. This is a site that we made learn to pair.com and that's one of the articles. There's lots of stuff there. Most of what I know about pairing, I tried to put in that site, learn&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; To pair.com. Got&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; It. You try to justify this scientific, it feels like coming up with scientific research into programming productivity measures seems very hard to me. &lt;affirmative&gt; I think coming up with a measure of how productive programmers are is tantalizing and so far I think has been fairly intractable. I'm not sure you could get something that would convince a lot of expert practitioners that you have a good metric that actually demonstrates whether a programmer is productive. It might be almost impossible. And so to say here is some scientific research into the effectiveness of pair programming sort of requires some measurement &lt;affirmative&gt; otherwise you're just doing surveys. Did you feel more productive? Did you enjoy this experience more? And the surveys there, people have done this research with surveys and the surveys do show very strong positive results. People enjoyed pairing, they felt more productive, they felt more connected.
I wouldn't probably take this track if I were trying to introduce pairing or try to justify pairing as an activity &lt;affirmative&gt;. I think I would try to no big deal my way into it. Meaning I think sometimes people think I need to convince my team, I should convince my team, my company, my management, that we should do pair programming. And so let me make the case. And it would be great if there were a study that showed that this is clearly a great idea. And I think what you probably should do instead is go to a coworker that you have a great relationship with and who is warm and friendly and empathetic and say, Hey, will you gimme a second set of eyes on this real quick and fire up something like Tuol or some other screen sharing tool or whatever. Or have them pull up a chair next to you.
If you are working in person, plug in an extra keyboard and just write some code together. And don't even call it pairing. Don't make a big deal of it. Just be like, call it a second set of eyes. Cuz that's fundamentally what pairing is at the end of the day. It's two people looking at the same code at the same time. And all the rules or strategies or tactics about who types when and who has a mouse and who has a keyboard and where's the monitor And all this really is of implementation details, but the fundamental activity is two programmers looking at the same code. And so you could do that in person, you could do that remote and I would just casually start this practice and see how it goes. And if you like it and it works well try to do it some more and maybe do it with some other people.
And maybe when someone at a standup says, Oh, I'm still stuck on that, whatever. And say, Oh, do you want me to come take a look at that with you at some point? And then lo and behold you're pairing with them or hey maybe, and Mary should pair on that thing or should work on that thing together. I would go at it grassroots and subtly and not make a big deal about it. And pairing is not for every team, so it might not work &lt;affirmative&gt; but I think that's probably gives you a pretty good chance of success. Top down, we will all pair can work occasionally. There are some organizations that have that and have done that and have become pairing organizations so it can work &lt;affirmative&gt; that's not how I would introduce pairing at an existing organization that is not already pairing or doesn't have extreme buy-in from the top who are willing to say, Yes, we're gonna make this. The priority of the quarter is everyone's gonna pair and we'll see how it goes&lt;/affirmative&gt;&lt;/affirmative&gt;&lt;/affirmative&gt;&lt;/affirmative&gt;&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; &lt;affirmative&gt;. So I think the reason behind maybe why they were asking, it's like, Oh hey, how can I get this top down buy in? It's maybe they were looking for a credit card, they're looking for the sign in, they're looking for like, Oh hey, I it's specifically, it's like, oh, I heard there's this great app that I would like to get access to. Yeah. I don't know. It's like&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; What are their options?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Yeah, yeah. Well it’s, You have a free trial period, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Yes, we do have a free trial. It doesn’t require a credit card card. So we see lots of our customers sign up as entry level developers or developers without a credit card. And then they try it and they like it and they ask someone high up the chain for permission to buy it and then become paid customers. But also and relevant to this group, I suspect, is that we give away the app for open source teams. So you can get a permanently free team if you are open source maintainers or working on open source and it’s just two.app/oss and fill out that form with what you’re working on. And we grant this to probably almost everyone that fills it out. So we leverage, we use a lot of open source software in our tool and our company is built on top of it. So we were very happy to give back to the OSS community in this way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; And full disclosure, I am a recipient of this, a happy recipient of this program. And I think only so one, both people don’t need to have paid for, is that correct? Yeah. Okay. So it’s if you wanna pair with somebody who’s never done it, it’s they don’t have to make this big investment. If one person has a license, you can invite somebody else to pair with you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Right? Exactly. Yep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Okay, cool. I think that helps ease that transition and gets to the heart of the, it’s like research maybe not, but it’s really, it’s how can we hit that ground running? How can we do more pairing? And I think to makes total sense to me. Have you ever heard of, I guess, pairing on non non-technical tasks or just almost like&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Yeah,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Writing an RFC or research or I guess you can speak to that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; I pair on non-technical tasks all the time every week. And this actually happens a lot at our company. A lot of non programming things happen on two sessions as a part of a pair. A lot of the benefit of pairing, there’s a lot of benefits of pairing that you get, even if there’s no code on the screen. So it’s often less boring to work on something or if something is boring, it’s less boring it’s less likely to be boring &lt;affirmative&gt; it's more engaging. If someone is there with you, it's harder to slack off if you're sharing your screen. So you tend to stay more focused and get the thing done faster with fewer Twitter accidental long Twitter breaks or email breaks or slack breaks or things like that. &lt;affirmative&gt; it spreads knowledge around the team. If I watch someone do a process or writing a doc or something, I'm learning that thing at the same time.
So we are having all this rich communication happening. You can see how someone else works, how someone else thinks, which is really useful for just becoming more awesome yourself. You might see they have a wonderful Macs tool that you wanna steal or Oh hey, I didn't know you could do that in our app with this shortcut, or how did you make that thing happen through while programming, but also through while just using your computer. So there's all these benefits that are available to you. And I think it shines particularly well on coding because programming is so hard and it's just so easy to write bugs that having a second set of eyes is, and it's so hard to make a great design, a good coding design &lt;affirmative&gt; that it feels extra worth having another person there because bugs in production can be very costly. They're not fun to fix. They slow you down a lot. Bad design decisions probably that even worse. True for all those things, but even stronger probably &lt;affirmative&gt;. And so it shines a lot there. But we have found quite a lot of value in just pairing on running payroll or figuring out what's the policy on this thing or what are our goals for next quarter, all those sorts of&lt;/affirmative&gt;&lt;/affirmative&gt;&lt;/affirmative&gt;&lt;/affirmative&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Things. Okay, cool. How do you initiate those conversations? Or do you just say, Hey, I’m working on this, somebody wanted somebody wanna do it with me?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Yeah yeah. Same sort of casual way. I have standing pairing meetings, kinda like you my calendar with people that I work with a lot. True. That’s probably true for a number of our employees as well. But yeah, I think there’s also just a lot of, there’s a fair amount of ad hoc pairing happening where someone just call somebody else, I’m like, Oh, this is tricky. Let me just call this person. And just they get a notification that I’m calling and there’s my screen and we start talking and it happens just kind of fairly fluidly and we’ve built a cult. Our culture is sort of steeped in this as you’d imagine making a pairing app for a living. Great. There’s a lot of pairing happening. Everyone’s expecting it. No one is shocked when you wanna do it with them. So that does a lot of work there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schneems:&lt;/strong&gt; Cool. Very cool. Well yeah, I really appreciate you coming on board and answering some of the open source communities questions. So yeah, thank you Ben from couple.app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ben:&lt;/strong&gt; Yeah, my pleasure. For.&lt;/p&gt;

</description>
        <pubDate>Wed, 09 Nov 2022 00:00:00 +0000</pubDate>
        <link>https://www.schneems.com/2022/11/09/pairing-on-open-source/</link>
        <guid isPermaLink="true">https://www.schneems.com/2022/11/09/pairing-on-open-source/</guid>
      </item>
    
      <item>
        <title>My book 'How to Open Source' launches today!</title>
        <description>&lt;p&gt;Today is the day. &lt;em&gt;How to Open Source&lt;/em&gt; is now available for purchase at &lt;a href=&quot;https://howtoopensource.dev&quot;&gt;howtoopensource.dev&lt;/a&gt;.&lt;/p&gt;

&lt;div align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://howtoopensource.dev&quot;&gt;
    &lt;img alt=&quot;book cover&quot; style=&quot;&quot; src=&quot;https://howtoopensource.dev/images/cover-email.png&quot; /&gt;
  &lt;/a&gt;
&lt;p&gt;
&lt;a href=&quot;https://howtoopensource.dev&quot; style=&quot;background-color: rgb(0, 123, 255);
background-image: linear-gradient(122deg, rgb(253, 55, 142) 0%, rgb(229, 69, 149) 100%);
border-bottom-color: rgb(255, 255, 255);
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
border-bottom-style: none;
border-bottom-width: 0px;
border-image-outset: 0;
border-image-repeat: stretch;
border-image-slice: 100%;
border-image-source: none;
border-image-width: 1;
border-left-color: rgb(255, 255, 255);
border-left-style: none;
border-left-width: 0px;
border-right-color: rgb(255, 255, 255);
border-right-style: none;
border-right-width: 0px;
border-top-color: rgb(255, 255, 255);
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border-top-style: none;
border-top-width: 0px;
box-shadow: rgba(0, 0, 0, 0.3) 0px 9px 32px 0px;
box-sizing: border-box;
color: rgb(255, 255, 255);
display: inline-block;
font-family: 'Rubik', sans-serif;
font-size: 14px;
font-weight: 500;
line-height: 21px;
padding-bottom: 11.2px;
padding-left: 25.6px;
padding-right: 25.6px;
padding-top: 11.2px;
text-align: center;
text-decoration-color: rgb(255, 255, 255);
text-decoration-line: none;
text-decoration-style: solid;
text-decoration-thickness: auto;
text-transform: uppercase;
touch-action: manipulation;
transition-delay: 0s;
transition-duration: 0.3s;
transition-property: all;
transition-timing-function: ease;
user-select: none;
vertical-align: middle;
white-space: nowrap;
margin-top: 1.5rem;
&quot;&gt;Buy the Book&lt;/a&gt;

&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;I shared a pre-release copy of the book with some community members, and here’s what they thought of the book:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Richard is a rare voice in the open source community: skilled and experienced, but also empathetic, caring and welcoming. If you’re venturing into the open-source bazaar, let Richard be your guide.” - &lt;strong&gt;Nate Berkopec, Maintainer of Puma webserver&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Richard does an excellent job of balancing the why of contributing to open source, with the how. This book will help you overcome common challenges folks face when navigating open source projects. If you find yourself on the other side of the table in the future, it will serve as a guide for how to make your project more welcoming to others. This book may be intended for those who already know how to code but is a useful read for anyone with a keen interest in open source.” - &lt;strong&gt;Anna Tumadóttir, Chief Operating Officer at Creative Commons&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;“How To Open Source provides practical, real world examples of getting started in Open Source. This book is a must read for anyone that wants to get started in the open source world.” - &lt;strong&gt;Aaron Patterson, Ruby core contributor &amp;amp; cat owner&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read more about the book over at &lt;a href=&quot;https://howtoopensource.dev&quot;&gt;howtoopensource.dev&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;launch-week-is-now-over&quot;&gt;Launch week is now over&lt;/h3&gt;

&lt;p&gt;Launch week was Sept 26 to Sept 30, 2022 and is now over. It was an amazing response. Thank you to everyone who bought! I’m sending out invites to the Slack channel and the discount codes have expired.&lt;/p&gt;

&lt;p&gt;To hear about future promotions sign up for my &lt;strong&gt;mailing list below&lt;/strong&gt; and &lt;strong&gt;follow &lt;a href=&quot;https://twitter.com/HowToOpenSource&quot;&gt;@HowToOpenSource&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 26 Sep 2022 00:00:00 +0000</pubDate>
        <link>https://www.schneems.com/2022/09/26/my-book-how-to-open-source-launches-today/</link>
        <guid isPermaLink="true">https://www.schneems.com/2022/09/26/my-book-how-to-open-source-launches-today/</guid>
      </item>
    
      <item>
        <title>The room where it happens: How Rails gets made</title>
        <description>&lt;p&gt;Today I’m going to share my perspective on how Ruby on Rails is developed and governed and how I feel the Basecamp “incident” impacts the future of Rails. I’m going to start out telling you what I know for sure, dip into some unknowns, and dive into some hypotheticals for fun.&lt;/p&gt;

&lt;p&gt;First off, who am I? Find me &lt;a href=&quot;https://www.twitter.com/schneems&quot;&gt;@schneems&lt;/a&gt; on Twitter and GitHub. I first contributed to Ruby on Rails ten years ago in 2011, and I’m in the top 50 contributors (by commits). I help maintain a few open source projects, including Puma and the Ruby Buildpack for Heroku (my day job). I’ve got  1,611,289,709 and counting gem &lt;a href=&quot;https://rubygems.org/profiles/schneems&quot;&gt;downloads to my name&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the Ruby on Rails ecosystem, I am known as a “contributor”. In Rails speak, that means that I also have commit access to the project.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note “getting commit” means that the person can merge PRs, close issues, and (depending on configuration) push to the main branch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-basecamp-incident&quot;&gt;The Basecamp incident&lt;/h2&gt;

&lt;p&gt;The reason I’m bringing this all up is that the Rails world has been reeling. DHH is not only the creator of Rails but also the Co-Founder of Basecamp. Basecamp has been in the news a lot after having ~1/3 of their employees quit. Many have also asked about Rails’ governance. To bring you up to speed on the loose timeline of the Basecamp news:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://world.hey.com/jason/changes-at-basecamp-7f32afc5&quot;&gt;Jason Fried made a highly criticized blog post&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://world.hey.com/dhh/basecamp-s-new-etiquette-regarding-societal-politics-at-work-b44bef69&quot;&gt;DHH doubled down&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://world.hey.com/dhh/mosaics-of-positions-ae6d4d9e&quot;&gt;Then again&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.platformer.news/p/-what-really-happened-at-basecamp&quot;&gt;Then Casey Newton broke the story of the events that lead to that seemingly-out-of-nowhere series of posts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://world.hey.com/dhh/let-it-all-out-78485e8e&quot;&gt;DHH Responded to the story&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/_breeeeen_/status/1388198260603506693&quot;&gt;The week ended with over 1/3 of Basecamp Employees quitting&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.platformer.news/p/-how-basecamp-blew-up&quot;&gt;Then Casey posted again with some more details of an interaction with Ryan Singer the day most employees left&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s hard to fully capture the response of the Twitterverse to the original announcements and news. A few voices of opposition were louder on my timeline than others and I wanted to share them for additional context:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/KimCrayton1&quot;&gt;Kim Crayton&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://twitter.com/KimCrayton1/status/1386837814202052612&quot;&gt;Apr 26 Twitter thread&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://twitter.com/KimCrayton1/status/1387419490560978951&quot;&gt;Apr 28 tweet&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://twitter.com/KimCrayton1/status/1388144198851891200&quot;&gt;Apr 30 tweet&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://twitter.com/KimCrayton1/status/1388552534713905152&quot;&gt;May 1 tweet and video link&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://twitter.com/KimCrayton1/status/1389518660427997185&quot;&gt;May 4 thread&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/_breeeeen_&quot;&gt;John Breen&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://breen.tech/post/cringe-camp/&quot;&gt;Blog post response&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://twitter.com/_breeeeen_/status/1388198260603506693&quot;&gt;Twitter thread following people leaving Basecamp&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/emilypothast&quot;&gt;Emily Pothast&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://marker.medium.com/the-pyramid-of-hate-that-brought-down-basecamp-838b63ca27e&quot;&gt;“The Pyramid of Hate that brought down Basecamp”&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;basecamp-concerns-hit-rails&quot;&gt;Basecamp concerns hit Rails&lt;/h2&gt;

&lt;p&gt;To many, DHH is the face of Rails. DHH created Rails, he holds the Rails trademark, and he keynotes at RailsConf every year. When Basecamp news hit Twitter, concern became increasingly apparent with this &lt;a href=&quot;https://discuss.rubyonrails.org/t/effect-of-the-last-week-on-ruby-on-rails/77702&quot;&gt;Rails forum thread on Discuss&lt;/a&gt; that many people are worried about how the situation unfolding with Basecamp will affect Rails. The new word of the day was “governance”.&lt;/p&gt;

&lt;p&gt;Basically, to most people, “how rails gets made” is a mystery box. That concern triggered Rails Core drafting and publishing &lt;a href=&quot;https://weblog.rubyonrails.org/2021/5/2/rails-governance/&quot;&gt;a Rails Governance blog post&lt;/a&gt; which lists out who exactly is on Rails Core (helpful) and states that they operate through “consensus”.&lt;/p&gt;

&lt;p&gt;Many people responded to the blog post. One that popped up on my timeline was &lt;a href=&quot;https://twitter.com/steveklabnik/status/1388954169936171020&quot;&gt;Steve Klabnick’s “My “the project isn’t run by one person or company” T-shirt sure is raising a lot of questions answered by my shirt” tweet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I interacted with that thread a bit, ultimately leading to this point where I’m now typing up a blog post.&lt;/p&gt;

&lt;h2 id=&quot;how-does-someone-get-commit-access-to-rails&quot;&gt;How does someone get commit access to Rails?&lt;/h2&gt;

&lt;p&gt;I will start with how I got commit access. I wrote a script that emails me a Rails issue a day. I read the issues as they came in, learned from them, and eventually started commenting. My “issue triage” efforts were noticed, and I was invited to the “issue team”. In this process, I was granted commit to the project and given access to the #contributor Basecamp instance where chat happens.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I eventually turned that script into a service named &lt;a href=&quot;https://www.codetriage.com&quot;&gt;CodeTriage&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve been around when others have been added as contributors. Generally, someone in the #contributors channel mentions they’ve noticed that a particular developer is helping a lot. They then suggest that we invite them to become a “contributor”. Usually, by that time, many in the #contributors’ room know this developer and agree to the addition. I don’t think I’ve ever seen someone be brought up for commit and get rejected, though I can’t guarantee it’s never happened. As far as I know, there’s not a formal process, and anyone in the #contributors channel could suggest/request someone else to be added.&lt;/p&gt;

&lt;p&gt;In short, it comes down to this: A developer gets commit access to Rails after doing a significant amount of work on the project and is independently recognized or asked for commit access.&lt;/p&gt;

&lt;p&gt;I don’t know if there are more formal criteria for getting access. I’ve been told that most activity happens in #contributors (as opposed to #core), and I’m inclined to believe there’s not much more to the process.&lt;/p&gt;

&lt;h2 id=&quot;rails-contributors-vs-rails-core&quot;&gt;Rails Contributors vs. Rails Core&lt;/h2&gt;

&lt;p&gt;In addition to #contributors, developers can also become a member of “Rails Core”. While I took ownership of a significant Rails dependency (Sprockets, which frankly needs yet another new owner), I am not in Rails Core.&lt;/p&gt;

&lt;p&gt;What does “Rails Core” mean if it doesn’t mean “commit”? This point is tricky and perhaps a bit subtle. It’s not written down anywhere. I’ve learned via trial and error and observation. One significant differentiator is that Rails Core members have release access to Rails itself. While I can cut a release of Sprockets, I do not have access to Rails/Railties/ActiveRecord, etc. The other big thing is seniority/authority/ownership.&lt;/p&gt;

&lt;p&gt;Different Rails Core members seem to own different parts of the framework. Some more obviously than others. For instance, &lt;a href=&quot;https://twitter.com/fxn&quot;&gt;Xavier Noria&lt;/a&gt; previously owned the Rails autoloading code and now owns the Zeitwerk gem used by rails to replace the old autoloading code. Even though it’s not written down who-owns-what (that I know of), you can go into a part of the project and look at the commit history. If there is an owner of a specific file or sub-library, then usually, it’s not hard to pick out a recurring name.&lt;/p&gt;

&lt;p&gt;In addition to ownership, there’s a seniority/authority aspect. I had a case where a Rails core member left me feedback on a documentation PR. They expressed disapproval of some of my decisions. I didn’t fully respond to the disapproval and merged the commit in myself. Later I got a private message from a Rails Core member (and Basecamp employee) saying that my behavior was not okay and that I shouldn’t go around/over/behind a decision from Rails Core. I apologized and listed actions I could take to help prevent it from happening again.&lt;/p&gt;

&lt;p&gt;It makes sense that an official opinion from #core will over-write one from a #contributor, especially when the issue at hand is the contributor’s own work. I don’t make a habit of ignoring feedback, but also, at the time that I decided to merge my commit (I won’t try to justify it), I didn’t understand the severity of the action.&lt;/p&gt;

&lt;p&gt;This dynamic of not knowing the full repercussions of administrative actions also exists outside Rails and open source. Recently at work, I submitted a PR to an internal project. The person who owns the project “approved” it, but it was a little unclear if they expected me to merge it or if they’re waiting for something else. In that case, I have their chat and know their working hours. I got the ambiguity resolved in 5 seconds by messaging them. In the fully async world of open source issues, a comment might take days to get a response (and sometimes never).&lt;/p&gt;

&lt;h2 id=&quot;how-i-guess-someone-gets-on-rails-core&quot;&gt;How I GUESS someone gets on Rails Core&lt;/h2&gt;

&lt;p&gt;While I’m not in “Rails Core,” I’ve seen many go into the ranks. I’ve also seen a number NOT become Rails core despite their massive involvement with the project. Based on those sets of people and my own experiences, I have some fan theories.&lt;/p&gt;

&lt;p&gt;I’ve already mentioned some prereqs. A developer has to do a lot of work on Rails and already be recognized as a contributor. They’ve got to be willing to take over a large amount of code to maintain. They need to be active and present in their maintenance. I don’t know if this is a strict requirement or merely a coincidence, or a rite-of-passage, but it seems they must be willing to shepherd at least one Rails release.&lt;/p&gt;

&lt;p&gt;From the outside-in, the most common cause for a prolific contributor to not be given the #core title seems to be disagreement with another core member. That being said, the only core member I know this has happened with for sure is DHH. I honestly don’t know if people are nominated, or they need to ask, or what. I’ve previously guessed that it would be possible for me to attain this “rank” if I could drop other open source commitments, be able to focus more time on Rails, and bring some “large” feature to the table. For instance, I’ve questioned/wondered if I could pitch making &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;derailed_benchmarks&lt;/code&gt; a first-class Rails feature and if that would be a “large” enough project to merit a nomination. I’ve never asked publicly or privately, though, because I’ve not felt that I had the time or energy to take on the role fully. Would that work? I don’t know. I’ve not tried. But that’s my best guess.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Also, I want to take a moment to say a huge thanks to Rails Contributors but especially Rails Core…the amount of work they put into the project is enormous. While being on Rails Core is an honor, it’s also a significant commitment and a lot of work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;how-do-major-features-happen-in-rails&quot;&gt;How do major features happen in Rails?&lt;/h2&gt;

&lt;p&gt;I view two sides of software - the tactical and strategic:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Tactical: Made or carried out with only a limited or immediate end in view (i.e., short-term goals)&lt;/li&gt;
  &lt;li&gt;Strategic: Of great importance within an integrated whole or to a planned effect (i.e., long-term goals)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the tactical work of Rails happens out in the open, mainly on GitHub. Some parts of it happen in the #contributor room (but less than you might think). Strategic work is a bit harder to pin down.&lt;/p&gt;

&lt;p&gt;My strategy for doing any work in Rails has been to break up any “strategic” initiatives into smaller individually comprehensible “tactical” initiatives. For example, in my blog post &lt;a href=&quot;https://www.schneems.com/blogs/container_ready_rails_5&quot;&gt;Container Ready Rails 5&lt;/a&gt;, I am pitching a larger feature/ability in Rails to manage a “containerized” Rails app better. In reality, this was a series of PRs made individually (which are linked in the post). While I had a strategy and this end goal, I didn’t start with a top-down design doc. I made PRs where I saw opportunities. I had conversations on GitHub issues, on Twitter, in Basecamp, and in person at conferences.&lt;/p&gt;

&lt;p&gt;I was able to ship this project because I could break it down into smaller components. For more prominent features like Hotwire, Action Text, Action Storage, or DB sharding: the project is too big to squeeze in one-pr-at-a-time. In my view, there are two mainline ways to get a prominent feature that cannot be broken up into Rails.&lt;/p&gt;

&lt;p&gt;The first way is to lay out the plans and get buy-in. This process might look like posting a design doc and asking for collaboration. The most recent time I’ve seen this happen is with a doc on “sharding” in Basecamp. I’ve never shipped something that large in Rails, but I’m guessing there’s also a decent amount of “whipping the votes” and getting buy-in. Even if it’s as tiny as mentioning “I wish we had feature X in Rails, don’t you?” at a conference.&lt;/p&gt;

&lt;p&gt;The second path to getting a prominent feature in Rails is to extract it from Basecamp. There’s still a measure of buy-in and communication. There still may be plans posted, design docs, and the rest…but it’s a different tone in the conversation. When the Rails feature is coming as an extraction from Basecamp, it’s typically a conversation about “how” rather than “if”. It might be possible to get #core or #contributors to reject and block one of these extractions, but I’ve not seen it happen.&lt;/p&gt;

&lt;p&gt;In the past, this strategy has served the community well as Rails ends up with features that have usually been semi-battle tested in production. It means that the implementers know the problem space well and are not designing features in a vacuum. Critics have noted that some features might be over-optimized for the Basecamp use case on the flip side. It’s a common enough joke that Rails is “Basecamp in a box”.&lt;/p&gt;

&lt;h2 id=&quot;explicit-and-implicit-governance&quot;&gt;Explicit and Implicit Governance&lt;/h2&gt;

&lt;p&gt;Most of what I’ve laid out here is based on my observations. Not much about how “Rails gets made” is written down or official. This fact makes sense to me. While some Rails Core and Contributors work for companies that allow them to work on open source work hours, I don’t believe any of them are “full-time” on Rails. On the #core list, everyone is a programmer and a contributor first. Project management, people management, and coordination happen, but they’re emergent properties of the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I like about this system:&lt;/strong&gt; I’ve benefitted from this system. I’m at the top(ish) of that leaderboard. I have a great relationship with many/most other contributors. I love the Rails community. I think we’re a solid group. I’ve navigated this implicit system very well. I’ve taken risks to push the boundaries of what I’ve believed was acceptable action and discourse. I’ve also patiently built consensus very slowly for divisive changes over the years. The funny thing is that I didn’t even realize that’s what I was doing. I loved a system driven by programmers because it “made sense to me” about getting changes in. I loved that if I “did the work”, then I got the benefits. If there’s something in Rails I didn’t like, I felt empowered to change it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I don’t like about this system:&lt;/strong&gt; Every step along the way, I’ve had to sit back, watch, and guess. This system fits my personality and style well. I’m used to finding ways to “work-steal” from others and find consensus. But there’s also plenty of times where I felt stuck. I listed one above. A PR was blocking other work, and I thought merging it wasn’t a big deal. I guessed wrong.&lt;/p&gt;

&lt;p&gt;I feel confident working in this system, but I also feel afraid of working in this system. It can be exhausting to backchannel and “find buy-in” for every little thing. I don’t like that when I read the Basecamp news and had a visceral reaction, my first thought was, “Will my commit access be revoked if I share what’s on my mind?” It’s incredibly unclear what mechanisms exist to remove commit access from someone against their will, and also unclear what recourse those people can take to get it re-instated.&lt;/p&gt;

&lt;p&gt;I also didn’t know what would happen to Rails, or what even &lt;strong&gt;could&lt;/strong&gt; happen to Rails? While I’ve taken it for granted as a given that any Basecamp feature extractions are “guaranteed” (my opinion) to get into Rails, why? What could change that would make that not true anymore? As you’ve read this post, you’ve probably seen me mention the word “Basecamp” concerning how Rails works quite a bit. Would we continue to use it? Is it even possible &lt;strong&gt;not&lt;/strong&gt; to use it? While these are specific questions that popped into my mind, the critical question is that I don’t even know if I can ask these questions without fear of retribution. Even hypothetically.&lt;/p&gt;

&lt;h2 id=&quot;moving-forwards&quot;&gt;Moving forwards&lt;/h2&gt;

&lt;p&gt;Writing all this down made it clear to me that Rails is fundamentally built on politics. Every step of the journey involves implicit relationships that developers must navigate. The irony of the “no-politics” controversy is that it doesn’t remove the politics. It just removes the ability to talk about politics. It forces those private conversations to be public. Will there be repercussions for me writing this? I don’t know, and that’s a bit worrying.&lt;/p&gt;

&lt;p&gt;What is my relationship with DHH? I’ve primarily done most of my work by flying under his radar. I’ve met him in person a few times. He’s always been personable, IRL. He’s never followed me on Twitter, and other than being involved in asking me to take ownership of Sprockets, I’m not sure he’s aware that I exist. I wish they had walked back their statements on Monday instead of doubling and tripling down, instead of going on network news to advocate others should do the same. I wish they came out strongly in favor of diversity and inclusion efforts. I wish they were vocal through their actions in dismantling white supremacy and in being anti-racist. Jason posted “an update”, which seems to be perhaps the beginning of some kind of acknowledgment. I think the damage has been done, and this event has highlighted deep, fundamental problems that cannot be so quickly whisked away.&lt;/p&gt;

&lt;p&gt;DHH has not been in the Basecamp Rails #contributor chat since “the incident”. I don’t know what he plans on doing. I don’t speak for him. I don’t speak for anyone else in Rails Core or Contributors. These are my observations and opinions.&lt;/p&gt;

&lt;p&gt;There’s a little activity in the contributor room, mostly about everyday getting-stuff-done topics. Some mentions of the forum post and chatter around it, but it’s quieter than I would have thought.&lt;/p&gt;

&lt;p&gt;I want to use &lt;a href=&quot;https://en.wikipedia.org/wiki/Nonviolent_Communication&quot;&gt;a framework called Nonviolent Communication (NVC) that has been valuable to my personal and professional life&lt;/a&gt; to try to be as straightforward as possible given the circumstances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observe:&lt;/strong&gt; I wrote about the dynamics of how Rails is governed and the events that lead up to this post above. It’s not all pure “objective fact,” but instead, it’s what I’m seeing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Emotion:&lt;/strong&gt; I’m anxious over the future of Rails and the Rails community. I’ve got a fear that I should be doing something about that anxiety. I’ve also feared that if I take the wrong action, there will be some negative retribution. I’m feeling defensiveness in myself and the Ruby community. I’ve distinctly noticed that this topic hasn’t even come up at all on /r/ruby, even while it’s basically been the “twitter character of the day” on my feed for over a week and has regularly topped the orange site. I’m frustrated. I wrote this post in a partial attempt to process these feelings. &lt;a href=&quot;https://feelu.vercel.app/&quot;&gt;wheel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Need:&lt;/strong&gt;  What I want is to build software as a community. I want to see from my community leaders that they can respect, value, and learn from the community. If a decision is made or action is taken that causes an uproar, I want reassurance that those voices have been heard. The ability to acknowledge a problem or disagreement is foundational to being able to work together.&lt;/p&gt;

&lt;p&gt;I want to build software in a collaborative way where I can feel confident that I understand the impacts of my actions. I want to work with those that “Dare Greatly” (Brene Brown) and can let their guards down, be vulnerable, and (as a result) produce the best possible outcomes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request:&lt;/strong&gt; This is where I narrow down a specific request to the listener so that my words won’t be misinterpreted (as much). It’s not a demand. It can be heard and acknowledged without the REQUIREMENT that it be acted upon. I ask you to open up about how you’re feeling and what you want to see happen. I see lots of bomb-throwing, I think it’s backed by a lot of rage, but I don’t know. I appreciate clarity from using NVC and might invite you to try on that framework. I don’t have a specific “fix” to recommend for this situation. Some have jumped to solutions. Some of those solutions may help. I’m not asking for a coup or trying to prevent one, for that matter. What I want is to be heard. I want clarity on the situation. I want to talk with more people and not feel entirely so alone in all of this.&lt;/p&gt;
</description>
        <pubDate>Wed, 12 May 2021 00:00:00 +0000</pubDate>
        <link>https://www.schneems.com/2021/05/12/the-room-where-it-happens-how-rails-gets-made/</link>
        <guid isPermaLink="true">https://www.schneems.com/2021/05/12/the-room-where-it-happens-how-rails-gets-made/</guid>
      </item>
    
      <item>
        <title>Migrating a Ruby Library from TravisCI to CircleCI</title>
        <description>&lt;p&gt;TravisCI.org is dead. Long live the new CI! TravisCI.org was THE way to run CI for an open source Ruby library. It was so easy that it was seemingly effortless. Even better, it was free. Since the slow-motion collapse of the product, developers have been pushed to other CI providers. I was recently tasked with transferring CI away from Travis for my library &lt;a href=&quot;https://github.com/schneems/derailed_benchmarks&quot;&gt;derailed_benchmarks&lt;/a&gt; and chose CircleCI. This post is a little about why I chose CircleCI, a little about how the transition worked, and a little about nostalgia.&lt;/p&gt;

&lt;h2 id=&quot;nostalgia-first&quot;&gt;Nostalgia first&lt;/h2&gt;

&lt;p&gt;I vividly remember when I was working for Gowalla, we had no CI. I didn’t even KNOW what CI was until we got a new employee &lt;a href=&quot;https://twitter.com/h3h&quot;&gt;Brad Fults&lt;/a&gt;. Brad set up Jenkins on a mac mini, and slowly my world was transformed.&lt;/p&gt;

&lt;p&gt;We didn’t have a strong testing culture when I started at Gowalla. I printed out a sign that read “ask me about Rspec” and taped it above my desk. We did have tests though. One issue that I had with testing is that everyone was expected to run tests locally before pushing to GitHub (or prod). But people wouldn’t, or they would forget. Sometimes it would work on their machine but break on someone else’s. One of my first OSS libraries was attempting to fix this problem I named it &lt;a href=&quot;https://github.com/schneems/git_test&quot;&gt;git_test&lt;/a&gt;. It would store the test results in git so you could see who was running tests and if they passed or failed.&lt;/p&gt;

&lt;p&gt;After Brad set up the CI server, I didn’t love it at first. The CI machine was always down, or tests would pass locally but fail on CI. It took me longer than I would like to admit to realize that my ambition around creating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git_test&lt;/code&gt; was solved by having CI and that CI was a far superior solution.&lt;/p&gt;

&lt;p&gt;About the time Brad was setting up Jenkins, waves were being made about a hot new startup called TravisCI. It had a slick interface and unbelievably was all open source. I don’t remember if we ever transitioned over to using a CI provider at Gowalla. Still, I’ve come to rely on having a managed CI provider as an integral part of my software development practices over the years.&lt;/p&gt;

&lt;p&gt;As Travis is shutting down their TravisCI.org, it feels like it’s the marker of an end of an era. It felt a golden age filled with promise and hope for collaboration between open source and private companies. The “free” offering felt less like a marketing gimmick and more like a bold declaration, that a company could make money, open source it’s own software, and support the community all at the same time. I mourn that.&lt;/p&gt;

&lt;h2 id=&quot;why-circleci&quot;&gt;Why CircleCI?&lt;/h2&gt;

&lt;p&gt;I now work for Heroku, and we have our CI product &lt;a href=&quot;https://www.heroku.com/continuous-integration&quot;&gt;Heroku CI&lt;/a&gt;. I love it for application development. Heroku CI is not a great fit for testing libraries, especially since there’s no way to give global public access to the test output. In my day job, I use Heroku CI, and increasingly I’ve been using CircleCI for non-app testing. I like their tooling. You can use a local CLI for debugging and an easy “Run with SSH” option to debug in the cloud on their Hardware. That is a feature Travis never had. I’ve also interacted with their support several times and had great experiences.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: This is 100% my opinion. No one from Heroku has reviewed this post. This message is not endorsed by anyone other than me.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While I know many people are transitioning over to GitHub Actions for CI, I changed my Twitter avatar to “GitHub drop ICE” to protest their contract with the (known human rights abusing) ICE agency. I try not to use GitHub actions when I don’t have to. A less-political reason is performance.&lt;/p&gt;

&lt;p&gt;I still use some actions like &lt;a href=&quot;https://github.com/zombocom/dead_end/blob/320dcfa4f314ed48e77d30e7b0151610205d8d8c/.github/workflows/check_changelog.yml&quot;&gt;this one that checks if a PR touched the CHANGELOG.md&lt;/a&gt;. On this project, I also have CircleCI tests in Ruby, and frequently the CircleCI tests will boot, execute, and finish before the GitHub action even starts firing. While most of my test suites are dominated by the suite’s length, those extra seconds can add up.&lt;/p&gt;

&lt;p&gt;Debugability and tooling are also on my mind. I can run a CircleCI test locally via the CLI, but cannot with GitHub Actions.&lt;/p&gt;

&lt;p&gt;I also feel like CircleCI is a CI company while Github Actions is a feature tacked on to a git hosting service. It’s getting lots of love and resources now, but I don’t know ten years from now if that will still be true.&lt;/p&gt;

&lt;h2 id=&quot;transitioning-from-travisyml-to-circleciconfigyml&quot;&gt;Transitioning from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.travis.yml&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.circleci/config.yml&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Here’s my original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.travis.yml&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;derailed_benchmarks&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;language&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;rvm&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2.2.10&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2.5.8&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2.7.1&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;gemfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gemfiles/rails_5_1.gemfile&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gemfiles/rails_6_0.gemfile&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gemfiles/rails_git.gemfile&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;allow_failures&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rvm&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2.2.10&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;gemfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gemfiles/rails_6_0.gemfile&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rvm&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2.2.10&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;gemfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gemfiles/rails_git.gemfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I love the compactness of this config. TravisCI was initially built with Ruby library maintainers in mind, and the compactness of the config in this case shows. It runs the tests (implicitly it knows to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rake test&lt;/code&gt;). It will run these tests against a “matrix” of Ruby versions (listed under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rvm&lt;/code&gt;) and different gemfile contents (listed under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemfile&lt;/code&gt;). You can also see where I’ve configured it to skip some combinations that I know don’t work (Ruby 2.2 does not work with Rails 6.0).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Since this post is talking about config, you might want to view the &lt;a href=&quot;https://github.com/schneems/derailed_benchmarks/tree/main/.circleci&quot;&gt;latest derailed_benchmarks config&lt;/a&gt;. I won’t be keeping this post up-to-date with changes there, but the following may help you understand what the config is doing and how it fits together.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now here’s what I ended up with for a roughly equivalent CircleCI config (with some Ruby versions changed):&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2.1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;orbs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ruby&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci/ruby@1.1.2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;run_tests&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;run_tests&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run test suite&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake test&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Needed because tests execute raw git commands&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;set_git_config&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;set_git_config&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set Git config&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;git config --global user.email &quot;you@example.com&quot;; git config --global user.name &quot;Your Name&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;restore&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;restore&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restore_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1_bundler_deps-&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;save&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;save_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./vendor/bundle&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1_bundler_deps-&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# CIRCLE_JOB e.g. &quot;ruby-2.5&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;bundle&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;install dependencies&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_JOBS=4&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_RETRY=3&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_PATH=$(pwd)/vendor/bundle&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_GEMFILE=$(pwd)/gemfiles/$GEMFILE_NAME&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;source $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;bundle install&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;bundle update&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;bundle clean&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ruby_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;gemfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;circleci/ruby:&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;parameters.ruby_version&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;gt;&amp;gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;GEMFILE_NAME&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;parameters.gemfile&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*set_git_config&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*restore&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*bundle&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*run_tests&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*save&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;workflows&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;all-tests&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;ruby_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2.5&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2.7&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.0&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;gemfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rails_5_2.gemfile&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rails_6_1.gemfile&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rails_git.gemfile&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ruby_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.0&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;gemfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rails_5_2.gemfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;break-it-down-start-at-the-end&quot;&gt;Break it down, start at the end&lt;/h2&gt;

&lt;p&gt;The most important part of the CircleCI config file is at the bottom. This is where I’m telling it about how to run my tests:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;workflows&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;all-tests&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;ruby_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2.5&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2.7&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.0&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;gemfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rails_5_2.gemfile&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rails_6_1.gemfile&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rails_git.gemfile&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ruby_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.0&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;gemfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rails_5_2.gemfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workflows&lt;/code&gt; key is a special key, just like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jobs&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version&lt;/code&gt;. This last part says to define a workflow where it will run my job named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt; (defined above) with a test matrix that looks like this:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;rails_5_2.gemfile&lt;/th&gt;
      &lt;th&gt;rails_6_1.gemfile&lt;/th&gt;
      &lt;th&gt;rails_git.gemfile&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Ruby 2.5&lt;/td&gt;
      &lt;td&gt;rails_5_2.gemfile-2.5&lt;/td&gt;
      &lt;td&gt;rails_6_1.gemfile-2.5&lt;/td&gt;
      &lt;td&gt;rails_git.gemfile-2.5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ruby 2.7&lt;/td&gt;
      &lt;td&gt;rails_5_2.gemfile-2.6&lt;/td&gt;
      &lt;td&gt;rails_6_1.gemfile-2.6&lt;/td&gt;
      &lt;td&gt;rails_git.gemfile-2.6&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ruby 3.0&lt;/td&gt;
      &lt;td&gt;skip&lt;/td&gt;
      &lt;td&gt;rails_6_1.gemfile-3.0&lt;/td&gt;
      &lt;td&gt;rails_git.gemfile-3.0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Unlike TravisCI, Circle has no built-in understanding of Ruby or the gemfile, so we’ve got to define some lower-level primitives. In this case, we’re creating a parameter named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruby_version&lt;/code&gt; and another named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemfile&lt;/code&gt;, then the permutations of these two parameters will be used to build the test matrix.&lt;/p&gt;

&lt;p&gt;You can also see that there’s a similar ability to “skip” combinations, though Circle uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exclude&lt;/code&gt; keyword.&lt;/p&gt;

&lt;h2 id=&quot;consuming-parameters&quot;&gt;Consuming parameters&lt;/h2&gt;

&lt;p&gt;Now let’s look at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt; job using the special keyword &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jobs&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ruby_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;gemfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;circleci/ruby:&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;parameters.ruby_version&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;gt;&amp;gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;GEMFILE_NAME&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;parameters.gemfile&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*set_git_config&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*restore&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*bundle&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*run_tests&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;First, the job declares that it accepts two parameters and should both be treated as a “string”.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;ruby_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;gemfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next is the docker/machine declaration:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    docker:
      - image: &quot;circleci/ruby:&amp;lt;&amp;lt; parameters.ruby_version &amp;gt;&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;CircleCI has a large number of pre-built docker instances that can be used. It doesn’t give me quite as much control as an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rvm install&lt;/code&gt; via Travis, but since this is a library, I am mostly concerned with major and minor ruby versions. The parameters are substituted into this value using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;&amp;lt; &amp;gt;&amp;gt;&lt;/code&gt; syntax:&lt;/p&gt;

&lt;p&gt;Next I set an environment variable via my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemfile&lt;/code&gt; parameter to be used in a script:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;GEMFILE_NAME&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;parameters.gemfile&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The last thing in this section is the set of commands (or “steps”) that will execute for each test:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*set_git_config&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*restore&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*bundle&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*run_tests&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Some steps are provided by CircleCI (like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;checkout&lt;/code&gt;), which checks out your source code, but you can either define yours inline like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;- run: &quot;echo 'lol'&quot;&lt;/code&gt; or you can use a reference as I’ve done here.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;p&gt;Each reference is defined with a name and a command. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;restore&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save&lt;/code&gt; references use some other pre-defined keys like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;restore_cache&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save_cache&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;run_tests&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;run_tests&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run test suite&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake test&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Needed because tests execute raw git commands&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;set_git_config&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;set_git_config&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set Git config&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;git config --global user.email &quot;you@example.com&quot;; git config --global user.name &quot;Your Name&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;restore&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;restore&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restore_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1_bundler_deps-&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;save&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;save_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./vendor/bundle&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1_bundler_deps-&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# CIRCLE_JOB e.g. &quot;ruby-2.5&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;bundle&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;install dependencies&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_JOBS=4&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_RETRY=3&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_PATH=$(pwd)/vendor/bundle&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_GEMFILE=$(pwd)/gemfiles/$GEMFILE_NAME&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;source $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;bundle install&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;bundle update&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;bundle clean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s look at this in step order:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*set_git_config&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*restore&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*bundle&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*run_tests&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;First is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set_git_config&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;c1&quot;&gt;# Needed because tests execute raw git commands&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;set_git_config&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;set_git_config&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set Git config&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;git config --global user.email &quot;you@example.com&quot;; git config --global user.name &quot;Your Name&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As the comment states, I only need this because some derailed tests are using raw git commands. When it executes, it will run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git config --global user.email &quot;you@example.com&quot;; git config --global user.name &quot;Your Name&quot;&lt;/code&gt; on the shell.&lt;/p&gt;

&lt;p&gt;Next is restore:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;na&quot;&gt;restore&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;restore&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restore_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1_bundler_deps-&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here we’re setting a cache key based on the name of the CircleCI job. I want to store Gem dependencies in the cache so that test runs are faster. Setting this cache key is how it knows which cache to restore.&lt;/p&gt;

&lt;p&gt;Once the cache is loaded I can install dependencies:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;na&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;bundle&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;install dependencies&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_JOBS=4&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_RETRY=3&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_PATH=$(pwd)/vendor/bundle&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;export BUNDLE_GEMFILE=$(pwd)/gemfiles/$GEMFILE_NAME&quot; &amp;gt;&amp;gt; $BASH_ENV&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;source $BASH_ENV&lt;/span&gt;

        &lt;span class=&quot;s&quot;&gt;bundle install&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;bundle update&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;bundle clean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A lot is going on here. I’m choosing to use bundler env vars instead of flags since some flags are deprecated. CircleCI needs to know about environment variable modifications between commands, so I’m writing to a file stored at $BASH_ENV that is sourced before every command.&lt;/p&gt;

&lt;p&gt;Since derailed needs to test against many different Rails versions, it uses different Gemfile contents in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemfiles/&lt;/code&gt; folder. To set the correct one based on the parameters I used before, I am reading from the GEMFILE_NAME env var:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;export BUNDLE_GEMFILE=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;PWD&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/gemfiles/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GEMFILE_NAME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$BASH_ENV&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I needed this value to be an absolute path since the tests execute sub-shells in different directories, so the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(PWD)&lt;/code&gt; here gets expanded to be an absolute path.&lt;/p&gt;

&lt;p&gt;When all that config is written, then I source the file:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$BASH_ENV&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then I install dependencies via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle install&lt;/code&gt;. Perhaps surprisingly, I then run a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle update&lt;/code&gt;. Since derailed is a library, I don’t check in any Gemfile.lock since I don’t control the specific library versions that apps will use. Instead, this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle update&lt;/code&gt; is telling bundler to check for more recent dependencies. I also need this because I’m caching dependencies and don’t want to get stuck on some ancient versions accidentally.&lt;/p&gt;

&lt;p&gt;Finally, I execute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle clean&lt;/code&gt;. That will remove any gem that’s not currently in the recently generated Gemfile.lock, which prevents the cache from getting bloated with many gems that we no longer need.&lt;/p&gt;

&lt;p&gt;CircleCI does provide a Ruby “orb,” which is effectively some pre-packaged references that can be re-used. This concept is similar to GitHub action’s “marketplace”. The orb comes with some references to install a specific ruby version with RVM (which I don’t need since the docker container already has it). The orb can also be used for installing dependencies via bundler. Unfortunately, the Ruby orb is mainly optimized around application development and doesn’t expect things like the Gemfile to be in a different directory needed for library development. My method is verbose, but I feel pretty straightforward.&lt;/p&gt;

&lt;p&gt;After dependencies are installed then they’re cached:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;na&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;save&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;save_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./vendor/bundle&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1_bundler_deps-&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# CIRCLE_JOB e.g. &quot;ruby-2.5&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The last reference to mention is pretty self-explanatory:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  run_tests: &amp;amp;run_tests
    run:
      name: Run test suite
      command: bundle exec rake test
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It runs my tests via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle exec rake test&lt;/code&gt;. After all that, then my matrix of tests is up-and-running ship-shape.&lt;/p&gt;

&lt;h2 id=&quot;version-and-orbs&quot;&gt;Version and orbs&lt;/h2&gt;

&lt;p&gt;The last thing to mention is the version and orb declaration:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2.1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;orbs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ruby&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci/ruby@1.1.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The version, I believe, refers to the version of YAML API that you’re using. You can validate your YAML using the CLI &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ circleci config validate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I previously mentioned orbs. I use them in other places to install dependencies, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pack&lt;/code&gt; for &lt;a href=&quot;https://github.com/heroku/heroku-buildpack-ruby/blob/ff7927dc5f93d89a45683895ac157d739bfbb2f1/.circleci/config.yml#L3&quot;&gt;building Cloud Native docker images&lt;/a&gt;. I initially thought that I might want to have more control over my specific Ruby version (such as Ruby 2.7.2 instead of whatever comes on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;circleci/ruby:2.7&lt;/code&gt;). However, I ended up not needing that flexibility. I kept the orb here to have an excuse to talk a bit more about orbs, though, because if you end up using CircleCI, you’ll end up using orbs.&lt;/p&gt;

&lt;p&gt;Here is an example of using the ruby orb via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruby/install-deps&lt;/code&gt; reference they provide in &lt;a href=&quot;https://github.com/CircleCI-Public/circleci-demo-ruby-rails/blob/efc68981242c66aa8a6373029ae3f896eb19d778/.circleci/config.yml#L36&quot;&gt;their example Rails config&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;retro&quot;&gt;Retro&lt;/h2&gt;

&lt;p&gt;Honestly, this took a lot longer than I thought it would. I’ve not seen other people talk about using CircleCI for testing libraries, so I wanted to see what the result would look like. Overall I’m happy with the config results. Now that I’ve got the skeleton in place, making changes seems very easy, but it was a process to get here. I’m curious if anyone else uses CircleCI to test a library with multiple Ruby versions and multiple Gem files. If so, shoot me a link on Twitter &lt;a href=&quot;https://twitter.com/schneems&quot;&gt;@schneems&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s some other examples of CircleCI test config for libraries:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/hashicorp/vault-rails/blob/024ac42761a1e9491f4e502ad9c55c85f5c59d24/.circleci/config.yml&quot;&gt;valut-rails config.yml&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/rails-on-services/apartment/blob/30f08c4b41b448172b319a5c40a9ad69302359ef/.circleci/config.yml&quot;&gt;apartment config.yml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Wed, 13 Jan 2021 00:00:00 +0000</pubDate>
        <link>https://www.schneems.com/2021/01/13/migrating-a-ruby-library-from-travisci-to-circleci/</link>
        <guid isPermaLink="true">https://www.schneems.com/2021/01/13/migrating-a-ruby-library-from-travisci-to-circleci/</guid>
      </item>
    
      <item>
        <title>Squash Unexpected-End errors with syntax_search</title>
        <description>&lt;p&gt;Have you ever hit an error that you just plain hate? Back in 2006, I was learning to program Ruby and following an example from a book. I typed in what I saw, hit enter, and ran into a supremely frustrating error message:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upcase&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# =&amp;gt;  syntax error, unexpected `end', expecting end-of-input&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I was beyond confused about this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unexpected end&lt;/code&gt; error. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end&lt;/code&gt; is a keyword. Right? How could Ruby not expect &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end&lt;/code&gt;? After staring at the code for a while, I realized my mistake. My &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map |x|&lt;/code&gt; line should be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map do |x|&lt;/code&gt; (missing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do&lt;/code&gt;). That moment was the start of a 14-year long hatred of that error message and a search for something better. This post is about how I wrote a gem that you can use to improve this error message. Keep reading!&lt;/p&gt;

&lt;blockquote class=&quot;imgur-embed-pub&quot; lang=&quot;en&quot; data-id=&quot;a/vlWfwS4&quot;&gt;&lt;a href=&quot;//imgur.com/a/vlWfwS4&quot;&gt;Syntax Search: Extra end&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//s.imgur.com/min/embed.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;blockquote&gt;
  &lt;p&gt;Update: I changed the name of the lib from “syntax_search” to “dead_end”. All the explanations about how the code work still hold up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TLDR;&lt;/h2&gt;

&lt;p&gt;To get improved “unexpected end” syntax errors in your project, add this to your Gemfile:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dead_end&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then make sure it’s required in your code:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Bundle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# If you're using Rails, this is the default.&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'dead_end'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re using rspec, add this line to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.rspec&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--require dead_end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;This is needed because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle exec rspec path/to/file.rb&lt;/code&gt; will throw a syntax error before the gem gets loaded&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now when you run your code, you get a helpful error:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SyntaxSearch: Unmatched `end` detected

This code has an unmatched `end`. Ensure that all `end` lines
in your code have a matching syntax keyword  (`def`,  `do`, etc.),
and that you don't have any extra `end` lines.

file: ~/spec/unit/env_proxy_spec.rb
simplified:

        1  # frozen_string_literal: true
        2
        3  require_relative &quot;../spec_helper.rb&quot;
        4
        5  module HerokuBuildpackRuby
        6    RSpec.describe &quot;env proxy&quot; do
    ❯   7      before(:all)
    ❯  10      end
       11
       # ...
      246    end
      247  end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;back-story&quot;&gt;Back Story&lt;/h2&gt;

&lt;p&gt;After that initial experience with this error, I mostly trained my brain that “unexpected end” (for me) means that I’m missing a “do” somewhere. That’s where most programmers stop. The error becomes background noise in their programming life.  I’ve seen developers develop defense mechanisms for syntax errors like this. For example: making sure to run their code frequently, or making sure to have small commits so they can revert to working code quickly, or they scan the git diff. Either way, you slice it, the error message ultimately is not that helpful. Programmers adopt these practices as a work-around for this message. It’s like if there was a hole in your house, and instead of covering it up, you just spent time walking around it.&lt;/p&gt;

&lt;p&gt;I remember vividly around 2013 I was working for Heroku, and we had Matz, Nobu, and Koichi from the Ruby core team fly into the office. We recorded an audio interview with them, and on the way out, I told Koichi and Nobu about this pain point. They listened, and we ultimately concluded that it was a difficult, if not impossible, problem to solve. I shelved the idea.&lt;/p&gt;

&lt;h2 id=&quot;the-impossible-goal&quot;&gt;The impossible goal&lt;/h2&gt;

&lt;p&gt;Why is it so difficult to improve this error message? Here’s an example of code with a missing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;touches a file&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/tmp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;FileUtils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;touch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;myfile&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code has a syntax error, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dir.chdir(&quot;/tmp&quot;)&lt;/code&gt; is missing a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do&lt;/code&gt;. BUT that’s not what triggers the error. The parser tries to match each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end&lt;/code&gt; to a corresponding syntax keyword (begin/def/do/if/while/etc.).&lt;/p&gt;

&lt;p&gt;When Ruby tries to parse this code. It mistakenly thinks that the first &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end&lt;/code&gt; belongs to the first do:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;touches a file&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &amp;lt;== Here&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Dir.chdir(&quot;/tmp&quot;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#   FileUtils.touch(&quot;myfile&quot;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &amp;lt;== Here&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It then keeps parsing, and it tries to match the last &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end&lt;/code&gt;, but it can’t. The parser hits the end of the file which is unexpected. Then an error is raised.&lt;/p&gt;

&lt;p&gt;In this example, it might be obvious to a human where the missing syntax was, but a computer cannot know the programmer’s intent. In this case, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dir.chdir()&lt;/code&gt; by itself without a block is VALID ruby code and will change the directory. Imagine if you found this code:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;touches a file&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/tmp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;FileUtils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;touch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;myfile&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One of these is expected to be a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chdir&lt;/code&gt; with a block, but not the other. Without more information, a computer can’t KNOW which line is supposed to have the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do&lt;/code&gt;. But you, my human friend, likely have a pretty good idea because there is extra information: the indentation.&lt;/p&gt;

&lt;h2 id=&quot;relax-constraints-and-add-information-indentation-informed-syntax&quot;&gt;Relax constraints and add information: Indentation informed syntax&lt;/h2&gt;

&lt;p&gt;Since it’s impossible to prove which line was intended to have the syntax, I relaxed my goals from “tell the user the problem” to “narrow the search space for the user to likely locations with a problem”. Finding the line that caused a syntax error isn’t impossible, which is why this blog post didn’t just stop above.&lt;/p&gt;

&lt;p&gt;Humans use indentation to inform our decisions about what the author of the code intended it to do. Why not cut out some of the middle work and have the program narrow our search for us? Some IDEs will warn you if you have an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end&lt;/code&gt; that does not have a corresponding syntax keyword on the same indentation. For example, my vim setup:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.dropbox.com/s/ack4j3im9ogmjqf/Screen%20Shot%202020-11-17%20at%2010.56.18%20AM.png?raw=1&quot; alt=&quot;screenshot of indentation in vim&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Originally I had the idea I could use that same detection idea, but I wanted to show the offending line, and not just the most likely &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end&lt;/code&gt; causing the problem. Beyond that, I wanted it to be robust for when indentation was a little off. By definition, when a syntax error occurs, the programmer has done something wrong. Only giving them a decent error when they’ve done everything else perfectly is not ideal. Also, I wanted to boost the signal to noise. Was it possible to remove code that I was pretty sure didn’t contain the error?&lt;/p&gt;

&lt;p&gt;With all this in mind, I began to hunt for a solution.&lt;/p&gt;

&lt;h2 id=&quot;search-for-syntax&quot;&gt;Search for syntax&lt;/h2&gt;

&lt;p&gt;When you’re trying to drive from one place to another, you’ll find many ways to reach the destination. The way that Google maps works is it searches for a solution and has a heuristic for success. When it finds a solution that provably minimizes the heuristic (such as time to destination), it returns this to the user.&lt;/p&gt;

&lt;p&gt;Usually, when you’re routing somewhere, you don’t want to be routed into a dead end. But what if your plan already had a dead-end in it? If you can remove a part of the plan and end up at your destination, then you know you’ve removed the dead end.&lt;/p&gt;

&lt;p&gt;I view syntax error like a dead end in code. If we can remove a dead end from our route then we have a valid path. Another way to put this: if you remove a syntax error from a document, then the document becomes valid (it can be parsed). I used this concept to “search” for one or more syntax errors in a document.&lt;/p&gt;

&lt;p&gt;Here’s a video of the process:&lt;/p&gt;

&lt;blockquote class=&quot;imgur-embed-pub&quot; lang=&quot;en&quot; data-id=&quot;a/DQLlPRp&quot;&gt;&lt;a href=&quot;//imgur.com/a/DQLlPRp&quot;&gt;Syntax Search: Def missing an end&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//s.imgur.com/min/embed.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;defining-the-roads&quot;&gt;Defining the roads&lt;/h2&gt;

&lt;p&gt;When navigating between two locations in a car, there are natural constraints for how it moves. It’s constrained by gas and brake pedals, engaged gear direction, and steering wheel angle at the low level. But google maps does not need to go into that much detail. Instead, it just looks at the intersections.&lt;/p&gt;

&lt;p&gt;Code has similar low-level constraints. Programs are made of individual characters but assembled into larger grammars that span multiple lines and eventually make an entire source file on disk. To search for a dead-end in the code, we need to find the right granularity. I chose to have my smallest unit be a line of source code and chunk the code into multiple lines to form “code blocks”.&lt;/p&gt;

&lt;p&gt;Once we have one or more code lines, we can remove it to see if we’ve found our solution. We can also independently verify if that block can be parsed or not.&lt;/p&gt;

&lt;p&gt;Like how Google maps mimics how a driver would behave, we can mimic how a human would begin to break apart logically and chunk source code. For example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Cat&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;eat&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;speak&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# &amp;lt;==&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;meow&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# &amp;lt;==&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# &amp;lt;==&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this case, you might be able to see that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def speak&lt;/code&gt; “block” is valid. We can run it through a parser to confirm this programmatically. If the code block is valid, it cannot contain the syntax error, so it can effectively be commented out. My algorithm reduces it to:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  1  class Cat
❯ 2    def eat
  7  end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def speak&lt;/code&gt; block was commented out, it then checked the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def eat&lt;/code&gt; line and saw the document was valid without it. The code then returned. We’ve found the syntax error!&lt;/p&gt;

&lt;h2 id=&quot;turn-by-turn&quot;&gt;Turn-by-turn&lt;/h2&gt;

&lt;p&gt;The bulk of the heavy lifting is done in the code block generation. It starts at the furthest most indentation. I will then expand outwards until it hits a change in indentation. We parse all the internal code with the same indentation before “expanding” a block to a lower indentation level. As it works the program also marks and comments-out code that it’s found to be valid. It’s easier to visualize with an animation:&lt;/p&gt;

&lt;blockquote class=&quot;imgur-embed-pub&quot; lang=&quot;en&quot; data-id=&quot;a/fVYBAT3&quot;&gt;&lt;a href=&quot;//imgur.com/a/fVYBAT3&quot;&gt;Syntax Search: Missing do&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//s.imgur.com/min/embed.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;This process simplifies the code as it runs and has a high likelihood of producing a code block that contains the syntax error(s).&lt;/p&gt;

&lt;h2 id=&quot;complicating-scenarios&quot;&gt;Complicating scenarios&lt;/h2&gt;

&lt;p&gt;Since I started this project knowing that the actual goal to “show you what line is missing syntax” is impossible, I’m left knowing that I’ll have to live with some unsatisfactory results. They’re imperfect because the intent is unclear.&lt;/p&gt;

&lt;p&gt;Here’s an example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Cat&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;eat&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;nomnom&quot;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;speak&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;meow&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Without involving a human, there could be two possible errors here. Maybe &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def eat&lt;/code&gt; is missing an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end&lt;/code&gt;, but it’s also possible that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def speak&lt;/code&gt; line was supposed to be removed and the contents consolidated into one single block. Without more information, we cannot know. When my program tries simplifying this, it spits out:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  1  class Cat
❯ 2    def eat
❯ 5    def speak
❯ 7    end
  8  end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can see a little more clearly that we could make this code valid by either removing line two or five or adding an end after line 2. That ambiguity is important and can be left up to the programmer.&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;

&lt;p&gt;Try it out! Seriously. Syntax errors in the wild might look different than in the “lab” here. I need you, and more importantly, I need your syntax errors.&lt;/p&gt;

&lt;p&gt;Installation instructions are found at the readme: &lt;a href=&quot;https://github.com/zombocom/syntax_search&quot;&gt;install syntax_search in your codebase today!&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 01 Dec 2020 00:00:00 +0000</pubDate>
        <link>https://www.schneems.com/2020/12/01/squash-unexpectedend-errors-with-syntaxsearch/</link>
        <guid isPermaLink="true">https://www.schneems.com/2020/12/01/squash-unexpectedend-errors-with-syntaxsearch/</guid>
      </item>
    
      <item>
        <title>Triage with Me - 11 issues &amp; 2 PRs in 1.5 hours</title>
        <description>&lt;p&gt;Contributing to open-source can be intimidating, especially when you’re getting started. In this post and video series, join me as I triage 11 issues on a repo that I didn’t create and don’t have much experience with.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Update: For additional tips on creating and responding to issues, check out the &lt;a href=&quot;https://howtoopensource.dev/&quot;&gt;How to Open Source&lt;/a&gt; book.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn’t edit the videos, so any mistakes and accomplishments are raw and live to see. I re-watched the videos and took notes on the types of problems I encountered and the questions and solutions that I explored along the way. I cleaned up my notes and put them all here along with the videos so you can follow along. Afterward, if you want to come back to a piece of advice or technique, you can skim my notes instead of re-watching the whole video.&lt;/p&gt;

&lt;p&gt;While I don’t recommend you sit down and try to respond to every open issue in the repository, hopefully, by watching me triage issues, you can help get a sense of how you might be able to dig in and start contributing. As you’re watching, try asking yourself how you would respond and what questions you might ask.&lt;/p&gt;

&lt;p&gt;The videos are split up into 4 sections with a bonus video at the end of my work on a PR to remove some deprecations. This open-source repo is written in Ruby, but the issues tended to be about higher-level concepts such as stdout/stderr and accessing APIs via HTTP. As a result, they should be accessible to someone from any programming language background. I’ve never triaged the issues on this repo before, so be prepared for a raw and real-time triaging session.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Recommended: Increasing your resolution on YouTube to the maximum so you can read the text better.
Recommended: Increasing the playback speed to 1.5x or higher since I didn’t cut out the “umms” and pauses either.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you like this “with me” series, find me on &lt;a href=&quot;https://twitter.com/schneems&quot;&gt;twitter @schneems&lt;/a&gt; and pitch me what you would like for me to work on live and record for another session.&lt;/p&gt;

&lt;h2 id=&quot;triage-session-14&quot;&gt;Triage session 1/4&lt;/h2&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Y8keqeNXizo&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;issue-1-heroics-should-use-keep-alive-connections-16&quot;&gt;Issue 1 &lt;a href=&quot;https://github.com/interagent/heroics/issues/16&quot;&gt;Heroics should use keep-alive connections #16&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Use the GitHub UI to identify commentators of note Contributor/Author/Member&lt;/li&gt;
  &lt;li&gt;Ask: “Do I understand the issue?”
    &lt;ul&gt;
      &lt;li&gt;List your assumptions, ask for clarification if needed&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Ask: “Is this still a priority?”
    &lt;ul&gt;
      &lt;li&gt;If we don’t know if it’s a priority, how could we find out? Is there a way for us to test our assumptions about prioritization? i.e., how could we write a benchmark to quantify “give some nice speedups.”&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;View: Code that’s mentioned &lt;a href=&quot;https://github.com/interagent/heroics/blob/3278cc6dbedc64b2d06571f869f249dbce574f18/lib/heroics/link.rb#L25-L102&quot;&gt;Link#run&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;Grab some example code or write some pseudo-code to validate we understand each other correctly.&lt;/li&gt;
      &lt;li&gt;Read the docs of the mentioned code&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Ask: “Can you point me at another location where this is implemented?”&lt;/li&gt;
  &lt;li&gt;Thoughts:
    &lt;ul&gt;
      &lt;li&gt;As the triager, you do not need to fully understand every aspect of the conversation, but you need to work to drive the issue to completion.&lt;/li&gt;
      &lt;li&gt;When someone talks about performance, ask for benchmarks or other ways we can quantify it. If they give benchmarks, run them, and verify the results.&lt;/li&gt;
      &lt;li&gt;Context building is an important activity. If you don’t have enough context from the issue, likely, others don’t have enough either.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;issue-2-examples-produced-by-the-cli-dont-show-correctly-help-options-28&quot;&gt;Issue 2 &lt;a href=&quot;https://github.com/interagent/heroics/issues/28&quot;&gt;Examples produced by the CLI don’t show correctly help options #28&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Click: Check linked issues for possible additional context.&lt;/li&gt;
  &lt;li&gt;View: If the reporter mentions code you’ve never seen before, find it.
    &lt;ul&gt;
      &lt;li&gt;In addition to the code referenced, try to see if there are other references to the code. What is the context around that code?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Reproduce: On bug reports with reproduction steps, see if you can follow the reproduction steps and verify the bug.
    &lt;ul&gt;
      &lt;li&gt;In the video, this is where we found the bug “uninitialized constant Moneta.”&lt;/li&gt;
      &lt;li&gt;Ask: “Where is this code or constant defined.”
        &lt;ul&gt;
          &lt;li&gt;Search for where Moneta is defined (Is it internal? Is it external?)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Moneta is defined as a dependency in the Gemspec&lt;/li&gt;
      &lt;li&gt;Add the missing require to fix the failure&lt;/li&gt;
      &lt;li&gt;The reproduction example runs now that the original reporter gave us without an error&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Document: Deprecation warnings and other problems that come up that might be good for future contributions&lt;/li&gt;
  &lt;li&gt;Thought: Many issues straddle the line between feature and bug report. Bug reports are easier to work with than feature proposals. See if you can treat the issue as a bug.&lt;/li&gt;
  &lt;li&gt;When I tried to reproduce their example, I couldn’t, and it looks like the issue had already been fixed.&lt;/li&gt;
  &lt;li&gt;Thought: It takes work to verify a reproduction is valid or not, and the process can yield a lot of context (in this case, a PR to fix the moneta require issue even).&lt;/li&gt;
  &lt;li&gt;In writing out a response, state what you’ve observed and then what you believe to be accurate based on those observations.
    &lt;ul&gt;
      &lt;li&gt;Explicitly state what you think the next steps should be (i.e., close the issue, more research, asked for clarification, etc.) and support the statement.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;We took the fix for the moneta failure we saw earlier and made it into a PR.&lt;/li&gt;
  &lt;li&gt;Tool: I use the open-source GUI &lt;a href=&quot;https://rowanj.github.io/gitx/&quot;&gt;gitx&lt;/a&gt; to stage commits and write messages. It’s not required, though.&lt;/li&gt;
  &lt;li&gt;Ask: When making a PR, “Does this need a changelog?” “Does this need tests?”, or “do tests need to be updated?”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;triage-session-24&quot;&gt;Triage session 2/4&lt;/h2&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Wo69bL0XhpA&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;issue-3-feature-automatic-generated-web-admin-ui-36&quot;&gt;Issue 3 &lt;a href=&quot;https://github.com/interagent/heroics/issues/36&quot;&gt;feature: automatic generated web admin ui #36&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;For feature requests: What’s the context, opportunity, and implementation?
    &lt;ul&gt;
      &lt;li&gt;Context: Looking for an easier way for non-programmers to interact with an API&lt;/li&gt;
      &lt;li&gt;Opportunity: This could be solved by a web interface&lt;/li&gt;
      &lt;li&gt;Implementation: They’re asking for this feature to be baked into this library, but no other details.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Try to find empathy and understanding. What drove the original creator to ask the issue?&lt;/li&gt;
  &lt;li&gt;Ask: For a feature request, is the best place for this feature inside of this library
    &lt;ul&gt;
      &lt;li&gt;Can the feature be provided externally, via another library, plugin, or extension?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;I say “sorry” not because I’m personally sorry in this case. However, instead, I empathize that the experience of getting no response is not great and that I still value their participation in the open-source community.&lt;/li&gt;
  &lt;li&gt;When suggesting issues should be closed, I suggest ways the issue creator can continue even though the issue is being closed. For example: porting the suggested feature to its own library.&lt;/li&gt;
  &lt;li&gt;Even closing issues requires empathy when doing it respectfully, and that empathy takes energy. The more people are helping, the more energy to go around.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;issue-4-errors-should-go-to-stderr-40&quot;&gt;Issue 4 &lt;a href=&quot;https://github.com/interagent/heroics/issues/40&quot;&gt;Errors should go to stderr #40&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;I start off trying to do an ad-hoc demonstration of stdout and stderr
    &lt;ul&gt;
      &lt;li&gt;Deprecation warnings go to stderr so that captured and piped output can be sent to other sources such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;To &lt;a href=&quot;https://youtu.be/Wo69bL0XhpA?t=886&quot;&gt;skip past me explaining what stdout and stderr are: jump to 14:46&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The issue included a script to run that shows the problem. I try to reproduce the bug.
    &lt;ul&gt;
      &lt;li&gt;Verify stated behavior is the actual behavior&lt;/li&gt;
      &lt;li&gt;In the process of reproducing this stated bad behavior, I found another issue where the exit code &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$?&lt;/code&gt; returns a success status code (i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;) even when the command failed due to an incorrect flag.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The report referenced a command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroics-generate&lt;/code&gt;. Unsure of its use, I looked it up in the project and found documentation on the README.md
    &lt;ul&gt;
      &lt;li&gt;I see the issue reported is using a suggested pattern from the README&lt;/li&gt;
      &lt;li&gt;While the issue states what they desire “to use stderr instead of stdout,” I see another way that could be used to address the issue using CLI arguments&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;I opened a new issue with the exit code behavior I saw before
Issues need to describe the problem and describe what you expected to happen and what actually happened. Also, add relevant details like version numbers, operating system, etc. where relevant.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;triage-session-34&quot;&gt;Triage session 3/4&lt;/h2&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/6-XthOjomOw&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;issue-5-escape-identifiers-41&quot;&gt;Issue 5 &lt;a href=&quot;https://github.com/interagent/heroics/issues/41&quot;&gt;Escape identifiers #41&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Review linked resources
    &lt;ul&gt;
      &lt;li&gt;Click “y” on a GitHub page to get a URL with the commit SHA in it. When you post the link back to the issue, it should always stay valid and point to the code at that point in time, even if the file is deleted or modified later.&lt;/li&gt;
      &lt;li&gt;If someone links to a resource that is no longer valid, it’s helpful to find what they’re talking about and comment back with a link to it.&lt;/li&gt;
      &lt;li&gt;Consider copying relevant information from linked resources (in addition to linking) so people can get context without having to move away from the issue.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;You might notice that I use block quotes to reference things someone else has said and then reply to them. It’s unnecessary, but I find this convention can cut the confusion, especially if you find yourself wanting to respond to multiple things.&lt;/li&gt;
  &lt;li&gt;Ask: Is it possible that someone is using/abusing this bug as a feature
    &lt;ul&gt;
      &lt;li&gt;If so, ask how likely that use is, what’s the impact to this person of fixing the bug, and if there’s any way to safely detect this usage so it can be warned/errored/deprecated.&lt;/li&gt;
      &lt;li&gt;Even if you don’t see a “valid” reason why someone might use something non-standard, breaking it is still a breaking change. If you value stability for your project, you need to consider the migration experience. Keyword: Backwards compatibility.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;If the issue contains a bug, ask for a reproduction or try to reproduce it.
    &lt;ul&gt;
      &lt;li&gt;The reporter might be describing behavior that has been fixed. They might be using an older version. Ask for versions.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;I use Grammarly for help with spelling and grammar. Reading sentences with grammar errors makes it harder to understand the underlying message. Taking time to clean up and clarify your comment means less time for other people reading.
    &lt;ul&gt;
      &lt;li&gt;This is also true in any distributed communication environment, such as a remote job where you’re communicating via slack/email/issues.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;issue-6-chunked-encoding-support-42&quot;&gt;Issue 6 &lt;a href=&quot;https://github.com/interagent/heroics/issues/42&quot;&gt;chunked encoding support #42&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;I didn’t explain it, but a chunked response is when you send parts of your response back at a time, imagine 1/4 of a web-page sent four times, instead of all of the data in a single request.&lt;/li&gt;
  &lt;li&gt;When you can treat an issue as a bug report, it becomes easier to work with.&lt;/li&gt;
  &lt;li&gt;If the reporter only gives the expected response, ask for the current response/behavior.&lt;/li&gt;
  &lt;li&gt;Ask for a way to reproduce the issue.
    &lt;ul&gt;
      &lt;li&gt;I link to https://www.codetriage.com/example_app to give the poster more context over what I’m asking for. Previously I would say, “can you give me a reproduction” and people would respond with several things from “what does that mean” to “my app is private, I can’t give you the source code.” This article intends to pre-emptively answer as many of these common questions as possible to guide the poster to give you what you need to drive the issue to completion.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;issue-7-validating-request-payload-client-side-51&quot;&gt;Issue 7 &lt;a href=&quot;https://github.com/interagent/heroics/issues/51&quot;&gt;Validating request payload client side #51&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;The more context required to understand an issue that isn’t explicitly laid out, the harder it will be to understand the problem, and the less likely progress will be made on the issue.
    &lt;ul&gt;
      &lt;li&gt;The feature request is vague enough that I felt I had to spend time explaining it in the video. I could have asked for clarification instead.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Ask: Is the behavior the reporter is describing true?
    &lt;ul&gt;
      &lt;li&gt;Gonna sound like a broken record: Ask for a reproduction&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Read what other commenters have said, try to understand their comments.&lt;/li&gt;
  &lt;li&gt;A commenter left links to files that no longer are valid
    &lt;ul&gt;
      &lt;li&gt;Find the correct links and get a “y” version (link to a specific commit).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Ask: If the proposed behavior is implemented, what are the consequences? Both good and bad. What if it’s not implemented? Is the bad that bad? Is the good that good?&lt;/li&gt;
  &lt;li&gt;Ask: Is there another way to achieve the requested goals?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;triage-session-44&quot;&gt;Triage session 4/4&lt;/h2&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/H-u46L33oHo&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;issue-8-add-connect_url-or-similar-method-to-generated-clients-52&quot;&gt;Issue 8 [Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connect_url&lt;/code&gt; or similar method to generated clients #52&lt;/h3&gt;
&lt;p&gt;](https://github.com/interagent/heroics/issues/52)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Read the issue, read the comments. Understand the feature request.&lt;/li&gt;
  &lt;li&gt;Consider the age of the feature request and the movement/momentum on the issue. Even if something is a good idea, it might not make sense to prioritize it.&lt;/li&gt;
  &lt;li&gt;This was the fastest issue that I closed so far. I mention that it might be I’m running out of emotional energy (i.e., patience). You need to be protective of your energy and time, but also you don’t want to do a sub-par job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;issue-9-exposed-etags-63&quot;&gt;Issue 9 &lt;a href=&quot;https://github.com/interagent/heroics/issues/63&quot;&gt;Exposed ETags #63&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;I explain heroics (again) and then etags. If I didn’t know what etags are, I would have searched for them, likely read Wikipedia and look for some examples. You could ask for how to do what they’re asking (make requests to the Heroku API with etags) without Heroics as one possible path forwards.&lt;/li&gt;
  &lt;li&gt;I wanted to take the issue and expand on it. Instead, I decided to hold off and validate their request. I did mention my own needs/desires for a more consistent low-level interface.&lt;/li&gt;
  &lt;li&gt;I suggest closing the issue since “no one is actively working on it right now.”
    &lt;ul&gt;
      &lt;li&gt;Instead of just closing, I provide an example of what could be useful to move the issue forwards: specific ideas of API design or example code, a PR, etc.&lt;/li&gt;
      &lt;li&gt;Whenever I close an issue, I want to leave guidance for what might prompt me to re-open it (when relevant).&lt;/li&gt;
      &lt;li&gt;Related to my real-life experience of “arguing effectively.” When I need to walk away from a situation/argument, it will feel rude and abrupt if you just leave. This could spark further conflict. Instead, I state that I’m leaving and making a plan for when we can revisit and come back to the issue.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;issue-10-utf-8-with-bom-fails-generating-a-client&quot;&gt;Issue 10 UTF-8 with bom fails generating a client&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: I’m purposefully not linking to this one because I got frustrated writing this response. I don’t want anyone posting anything negative to the thread. Later on, in the video, I enumerate some common types of seemingly “bad behavior” that might be attributed to other things such as English not being the first language (just an example, I don’t know this person at all). At the end of the day, my goal is to help the open-source project. I appreciate they took the time to open an issue. At this point in my marathon triaging session, I’m a little out of patience. I wanted to leave these issues in my videos as I think they represent real feelings and experiences you’ll have in the open-source community. Again: I don’t want to shame, or diss, or hate on this poster. Please take my comments in the general sense and with a grain of salt. Be nice. Be kind. Build community, not hate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Update: I looked it up, and they’re from Saint Petersburg, so likely my read of “English not their first language” is likely correct.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;I am frustrated by this issue. They were asked to provide more information and then didn’t describe the issue as “it is simple.”
    &lt;ul&gt;
      &lt;li&gt;“99% of triaging issues is asking for reproductions/example-apps and verifying that they behave as described.”&lt;/li&gt;
      &lt;li&gt;If you’re opening an issue, please be considerate of the maintainer’s time and energy. Don’t ask them to do things that you can do for them.&lt;/li&gt;
      &lt;li&gt;If you’re frustrated with an interaction, try to center yourself on the common goals: In this case, everyone wants the open-source library to be better.&lt;/li&gt;
      &lt;li&gt;If you must write a snarky response, don’t send it. Or maybe send it to a friend or something. Be nice. Issue reporters and commenters are contributing too. Empathy generates empathy, snark generates frustration and more snark.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While I’m somewhat accidentally on the topic of behavior that I don’t like: I encourage you to try to have empathy and understand if maybe there’s another way a comment could be interpreted. At the same time, if you see a pattern of bad behavior or a comment crosses a personal line, be protective of yourself and your community. 99% of the time that I’ve messaged someone privately saying, “when you said &lt;x&gt; it made me feel &lt;y&gt;, and that's bad,&quot; they apologized and fixed the behavior. This experience is where my comment of &quot;maybe English is not their first language&quot; came from. Most of those people I told them their words were hurtful had no idea and genuinely appreciated the opportunity to improve. For actual bad actors, there are mod tools. In general, I recommend the communication technique Non-Violent Communication - NVC. At a high level: state your observation, say how it made you feel, state what you require, explicitly state what you need from the person.&lt;/y&gt;&lt;/x&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Stating, “I don’t know what this means” is difficult, especially on the internet. It’s helpful to state what you didn’t understand because if you have a question, it’s likely someone else does. By taking that risk, that leap of stating what wasn’t clear, you’re normalizing the community’s behavior.&lt;/li&gt;
  &lt;li&gt;Some people who create issues don’t know that not having a reproduction attached to an issue is the blocker to moving forwards. Sometimes you, as the issue triager, need to push back a little stronger. If they’ve already been dragging their feet, then stating, “let’s close this until we can get a reproduction” might motivate them to work on providing the needed example.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;issue-11-i-dont-have-definitions-in-my-json-schemas-67&quot;&gt;Issue 11 I dont have definitions in my json schemas #67&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: Also not linking to this one due to my previous “frustration” comments. It’s the same person. Here’s where I first realize that English might not be their first language.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Triaging multiple issues in the same repo helps show you patterns and context you wouldn’t otherwise get. For example, the fact that one person created multiple issues. Even if they’re not explicitly linked via URLs, they might be related.&lt;/li&gt;
  &lt;li&gt;It’s okay to not understand an issue AT ALL when you read it. When this happens, you need to figure out if you want to invest the time and energy to learn more or cut your losses and move on to another issue. When you’re first getting started, you’ll be confused more often than you’ll know what you’re doing. One “hack” is to timebox your involvement with issues. I’m shooting for about 10 minutes here roughly. If, after that time, I still had no clue, I could choose to ask some clarifying questions possibly or to instead invest my energy in another issue.&lt;/li&gt;
  &lt;li&gt;When I said, “that’s what I would have said as well.” I’m referring to the statement by them to ask the poster to provide a PR for additional docs.&lt;/li&gt;
  &lt;li&gt;At this point, I mention the grammar of the issue and introduce the idea that maybe English is not their first language.&lt;/li&gt;
  &lt;li&gt;If you’re opening up an issue, try not to accidentally write in a commanding tone. Instead of “you should point to this problem in your readme,” maybe a softer, “the readme should point to this problem.” Describe the end goal instead of the action to get to the end goal.&lt;/li&gt;
  &lt;li&gt;Closing issues is better when you yell out “CLOSED” in a happy and accomplished voice.&lt;/li&gt;
  &lt;li&gt;If you didn’t know, you can edit the README or docs without ever needing to leave the web interface of GitHub.&lt;/li&gt;
  &lt;li&gt;Triaging issues is exhausting. My patience is wearing thin, and it’s showing in my responses. The more people who can help, the better responses and interactions the community will have.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, there were 2 issues left. One of them was about deprecations, and I already wanted to work on it. I had previously commented on it. The last issue was one that we opened in a prior issue triaging session.&lt;/p&gt;

&lt;h2 id=&quot;bonus-paring-pull-request-session-54&quot;&gt;(Bonus) Paring pull-request session 5/4&lt;/h2&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Ow9H2gvFBjE&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;ul&gt;
  &lt;li&gt;If you’re going to deprecate a method, please provide a suitable replacement suggestion instead of “this method is obsolete.”
    &lt;ul&gt;
      &lt;li&gt;URI.escape and URI.unescape are both deprecated in Ruby&lt;/li&gt;
      &lt;li&gt;The docs recommend CGI.escape&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I use Duck Duck Go as a search engine mostly because I don’t like Google AMP and want to use the same engine on my phone and desktop. With DDG, you can “fall back” to a google search by adding “g!” to your query, which sends you to google. I don’t use it often but need to here.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;The decision to deprecate URI.escape is 10 years old.&lt;/li&gt;
  &lt;li&gt;Docs that come up from APIdoc often show up first but frequently have wrong info. I want ruby-doc.org instead when possible.&lt;/li&gt;
  &lt;li&gt;When I got to the CGI page on ruby-doc.org, I searched “escape” in the browser and couldn’t find that method. When that happens, sometimes the method comes from an included module, so I click through to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CGI::Util&lt;/code&gt; module. This module is where the method is defined.&lt;/li&gt;
  &lt;li&gt;I started to blindly replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.escape&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CGI.escape&lt;/code&gt; in the code.&lt;/li&gt;
  &lt;li&gt;A tab I previously opened was linked to a PR that got rid of the warning. I used this to investigate how they fixed the issue. They used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI::DEFAULT_PARSER.escape&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;When I ran into that constant, I wanted to investigate it because it was unfamiliar. I loaded up IRB to investigate.&lt;/li&gt;
  &lt;li&gt;I re-read their PR to see if they left more context or comments.&lt;/li&gt;
  &lt;li&gt;At this point, I thought since the docs recommended CGI, I was more comfortable using that.&lt;/li&gt;
  &lt;li&gt;I decided to test it in IRB since I already had it open.&lt;/li&gt;
  &lt;li&gt;CGI.escape is not a suitable replacement.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;require 'cgi'
irb(main):003:0&amp;gt; CGI.escape(&quot;url here&quot;)
=&amp;gt; &quot;url+here&quot;
irb(main):004:0&amp;gt; URI.escape(&quot;url here&quot;)
(irb):4: warning: URI.escape is obsolete
=&amp;gt; &quot;url%20here&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: that the results are different&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;I decided to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI::DEFAULT_PARSER&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CGI&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;After making the change, I ran the tests to see if that got rid of all the deprecation warnings in the output.&lt;/li&gt;
  &lt;li&gt;I tried updating my gems to get rid of deprecation warnings. Investigating the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;turn&lt;/code&gt; I see it’s not had a new release since 2014, and the repo is marked as unmaintained.&lt;/li&gt;
  &lt;li&gt;Question: Are we using this library that is marked as unmaintained? Removing the gem gives us an error.&lt;/li&gt;
  &lt;li&gt;At this point, I don’t know what it does and don’t know how long it will take to remove it. I decided to clean up the other warnings and save that for last if there’s time.&lt;/li&gt;
  &lt;li&gt;For the warning “assigned but unused,” you can use the character underscore &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt; as a variable to tell Ruby that you don’t care about using the variable.&lt;/li&gt;
  &lt;li&gt;For the duplicate test method, my initial thought is someone copied and pasted the whole method again by accident. On closer investigation, they’re similar but not the same. I rename one of the methods.&lt;/li&gt;
  &lt;li&gt;You can start a commit message with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[close #&amp;lt;issue number&amp;gt;]&lt;/code&gt;, and when it’s merged into the default branch, it auto closes that issue. It’s also helpful for bookkeeping later to ensure it’s clear the issue and the PR are related.&lt;/li&gt;
  &lt;li&gt;When writing a commit message, try to give the maximum amount of context. Imagine if you had amnesia and found yourself in front of the commit message and need to understand why you did what you did. In this case, I wanted to link to external resources. I also realized I didn’t read through the history of why &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.escape&lt;/code&gt; is deprecated and might want to later, so I use the commit as a place to store the link.&lt;/li&gt;
  &lt;li&gt;Usually, when I make a PR, I bundle all the changes into one commit via &lt;a href=&quot;https://www.codetriage.com/squash&quot;&gt;squashing my commits&lt;/a&gt;. Conventions vary based on the project. Some people try to “pad” their contribution with extra commits, so it looks like they’re more active/helpful than they really are on a repo. Don’t do that. Do what’s right for the conventions of the community you’re working with and what’s best for the project. I said “rebase” but meant “squash.”&lt;/li&gt;
  &lt;li&gt;Since I don’t know how deeply integrated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;turn&lt;/code&gt; is into the project, I want to timebox removing it.&lt;/li&gt;
  &lt;li&gt;I deleted the references to turn, and the tests pass.&lt;/li&gt;
  &lt;li&gt;It might seem redundant to re-write a good PR message since there are already links to the commits with individual contexts. It’s worth spending the extra time and writing good PR messages and descriptions, so all the appropriate context is available with minimal maintainer effort.&lt;/li&gt;
  &lt;li&gt;Notice that I’m re-using my notes from working on the issue while making the PR.&lt;/li&gt;
  &lt;li&gt;It’s not guaranteed that triaging issues will show you pull request opportunities, but it’s an excellent place to start looking and start building context.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;wrap-up&quot;&gt;Wrap Up&lt;/h2&gt;

&lt;p&gt;If you’re inspired to go work on some open-source issues and don’t know where to get started, I recommend &lt;a href=&quot;https://www.codetriage.com&quot;&gt;signing up for CodeTriage&lt;/a&gt;. If you like this “with me” series, find me on &lt;a href=&quot;https://twitter.com/schneems&quot;&gt;twitter @schneems&lt;/a&gt; and pitch me what you would like for me to work on live and record for another session. You can also &lt;a href=&quot;https://schneems.com/mailinglist&quot;&gt;subscribe to my email newsletter&lt;/a&gt; to get more content.&lt;/p&gt;

</description>
        <pubDate>Tue, 22 Sep 2020 00:00:00 +0000</pubDate>
        <link>https://www.schneems.com/2020/09/22/triage-with-me-11-issues-2-prs-in-15-hours/</link>
        <guid isPermaLink="true">https://www.schneems.com/2020/09/22/triage-with-me-11-issues-2-prs-in-15-hours/</guid>
      </item>
    
      <item>
        <title>The Life-Changing Magic of Tidying Ruby Object Allocations</title>
        <description>&lt;p&gt;Your app is slow. It does not spark joy. This post will show you how to use memory allocation profiling tools to discover performance hotspots, even when they’re coming from inside a library. We will use this technique with a real-world application to identify a piece of optimizable code in Active Record that ultimately leads to a patch with a substantial impact on page speed.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Read the &lt;a href=&quot;https://blog.heroku.com/tidying-ruby-object-allocations&quot;&gt;original post on the Heroku engineering blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In addition to the talk, I’ve gone back and written a full technical recap of each section to revisit it any time you want without going through the video.&lt;/p&gt;

&lt;p&gt;I make heavy use of theatrics here, including a Japanese voiceover artist, animoji, and some edited clips of Marie Kondo’s Netflix TV show. This recording was done at EuRuKo on a boat. If you’ve got the time, here’s the talk:&lt;/p&gt;

&lt;div class=&quot;embedded-video-wrapper&quot;&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/Aczy01drwkg?start=287&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&quot;jump-to&quot;&gt;Jump to:&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#intro-to-tidying-object-allocations&quot;&gt;Intro to Tidying Object Allocations&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tidying-example-1-active-record-respond_to-logic&quot;&gt;Tidying Example 1: Active Record respond_to? logic&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#performance-and-statistical-significance&quot;&gt;Performance and Statistical Significance&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tidying-example-2-converting-strings-to-time-takes-time&quot;&gt;Tidying example 2: Converting strings to time takes time&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;tidying-example-3-lightning-fast-cache-keys&quot;&gt;Tidying Example 3: Lightning fast cache keys&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;intro-to-tidying-object-allocations&quot;&gt;Intro to Tidying Object Allocations&lt;/h2&gt;

&lt;p&gt;The core premise of this talk is that we all want faster applications. Here I’m making the pitch that you can get significant speedups by focusing on your object allocations. To do that, I’ll eventually show you a few real-world cases of PRs I made to Rails along with a “how-to” that shows how I used profiling and benchmarking to find and fix the hotspots.&lt;/p&gt;

&lt;p&gt;At a high level, the “tidying” technique looks like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Take all your object allocations and put them in a pile where you can see them&lt;/li&gt;
  &lt;li&gt;Consider each one: Does it spark joy?&lt;/li&gt;
  &lt;li&gt;Keep only the objects that spark joy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An object sparks joy if it is useful, keeps your code clean, and does not cause performance problems. If an object is absolutely necessary, and removing it causes your code to crash, it sparks joy.&lt;/p&gt;

&lt;p&gt;To put object allocations in front of us we’ll use:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/SamSaffron/memory_profiler&quot;&gt;memory_profiler&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/schneems/derailed_benchmarks&quot;&gt;derailed_benchmarks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get a sense of the cost of object allocation, we can benchmark two different ways to perform the same logic. One of these allocates an array while the other does not.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'benchmark/ips'&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;compare_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;allocate_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &amp;lt;===== Array allocation here&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;max&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ips&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;allocate_max&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;allocate_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;compare_max &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;compare_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compare!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This gives us the results:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;Warming up --------------------------------------
        allocate_max   258.788k i/100ms
        compare_max    307.196k i/100ms
Calculating -------------------------------------
        allocate_max      6.665M (±14.6%) i/s -     32.090M in   5.033786s
        compare_max      13.597M (± 6.0%) i/s -     67.890M in   5.011819s

Comparison:
        compare_max : 13596747.2 i/s
        allocate_max:  6664605.5 i/s - 2.04x  slower
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this example, allocating an array is 2x slower than making a direct comparison. It’s a truism in most languages that allocating memory or creating objects is slow. In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C&lt;/code&gt; programming language, it’s a truism that “malloc is slow.”&lt;/p&gt;

&lt;p&gt;Since we know that allocating in Ruby is slow, we can make our programs faster by removing allocations. As a simplifying assumption, I’ve found that a decrease in bytes allocated roughly corresponds to performance improvement. For example, if I can reduce the number of bytes allocated by 1% in a request, then on average, the request will have been sped up by about 1%. This assumption helps us benchmark faster as it’s much easier to measure memory allocated than it is to repeatedly run hundreds or thousands of timing benchmarks.&lt;/p&gt;

&lt;h2 id=&quot;tidying-example-1-active-record-respond_to-logic&quot;&gt;Tidying Example 1: Active Record &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;respond_to?&lt;/code&gt; logic&lt;/h2&gt;

&lt;p&gt;Using the target application &lt;a href=&quot;https://www.codetriage.com&quot;&gt;CodeTriage.com&lt;/a&gt; and derailed benchmarks, we get a “pile” of memory allocations:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;$ bundle exec derailed exec perf:objects

allocated memory by gem
-----------------------------------
    227058  activesupport/lib
    134366  codetriage/app
    # ...


allocated memory by file
-----------------------------------
    126489  …/code/rails/activesupport/lib/active_support/core_ext/string/output_safety.rb
     49448  …/code/codetriage/app/views/layouts/_app.html.slim
     49328  …/code/codetriage/app/views/layouts/application.html.slim
     36097  …/code/rails/activemodel/lib/active_model/type/helpers/time_value.rb
     25096  …/code/codetriage/app/views/pages/_repos_with_pagination.html.slim
     24432  …/code/rails/activesupport/lib/active_support/core_ext/object/to_query.rb
     23526  …/code/codetriage/.gem/ruby/2.5.3/gems/rack-mini-profiler-1.0.0/lib/patches/db/pg.rb
     21912  …/code/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
     18000  …/code/rails/activemodel/lib/active_model/attribute_set/builder.rb
     15888  …/code/rails/activerecord/lib/active_record/result.rb
     14610  …/code/rails/activesupport/lib/active_support/cache.rb
     11109  …/code/codetriage/.gem/ruby/2.5.3/gems/rack-mini-profiler-1.0.0/lib/mini_profiler/storage/file_store.rb
      9824  …/code/rails/actionpack/lib/abstract_controller/caching/fragments.rb
      9360  …/.rubies/ruby-2.5.3/lib/ruby/2.5.0/logger.rb
      8440  …/code/rails/activerecord/lib/active_record/attribute_methods.rb
      8304  …/code/rails/activemodel/lib/active_model/attribute.rb
      8160  …/code/rails/actionview/lib/action_view/renderer/partial_renderer.rb
      8000  …/code/rails/activerecord/lib/active_record/integration.rb
      7880  …/code/rails/actionview/lib/action_view/log_subscriber.rb
      7478  …/code/rails/actionview/lib/action_view/helpers/tag_helper.rb
      7096  …/code/rails/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
      # ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;a href=&quot;https://gist.github.com/schneems/5ed597c85a0a49659413456652a1befc&quot;&gt;full output is massive&lt;/a&gt;, so I’ve truncated it here.&lt;/p&gt;

&lt;p&gt;Once you’ve got your memory in a pile. I like to look at the “allocated memory” by file. I start at the top and look at each in turn. In this case, we’ll look at this file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;      8440  …/code/rails/activerecord/lib/active_record/attribute_methods.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once you have a file you want to look at, you can focus on it in derailed like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;$ ALLOW_FILES=active_record/attribute_methods.rb \
  bundle exec derailed exec perf:objects

allocated memory by file
-----------------------------------
      8440  …/code/rails/activerecord/lib/active_record/attribute_methods.rb

allocated memory by location
-----------------------------------
      8000  …/code/rails/activerecord/lib/active_record/attribute_methods.rb:270
       320  …/code/rails/activerecord/lib/active_record/attribute_methods.rb:221
        80  …/code/rails/activerecord/lib/active_record/attribute_methods.rb:189
        40  …/code/rails/activerecord/lib/active_record/attribute_methods.rb:187
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now we can see exactly where the memory is being allocated in this file. Starting at the top of the locations, I’ll work my way down to understand how memory is allocated and used. Looking first at this line:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;      8000  …/code/rails/activerecord/lib/active_record/attribute_methods.rb:270
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We can open this in an editor and navigate to that location:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;$ bundle open activerecord
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In that file, here’s the line allocating the most memory:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond_to?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;include_private&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:to_partial_path&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;to_partial_path&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:to_model&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;to_model&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &amp;lt;=== Line 270 here&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# If the result is true then check for the select case.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# For queries selecting a subset of columns, return false for unselected columns.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# We check defined?(@attributes) not to issue warnings if called on objects that&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# have been allocated but not yet initialized.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;defined?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;column_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_attribute?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here we can see on line 270 that it’s allocating a string. But why? To answer that question, we need more context. We need to understand how this code is used. When we call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;respond_to&lt;/code&gt; on an object, we want to know if a method by that name exists. Because Active Record is backed by a database, it needs to see if a column exists with that name.&lt;/p&gt;

&lt;p&gt;Typically when you call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;respond_to&lt;/code&gt; you pass in a symbol, for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user.respond_to?(:email)&lt;/code&gt;. But in Active Record, columns are stored as strings. On line 270, we’re ensuring that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; value is always a string.&lt;/p&gt;

&lt;p&gt;This is the code where name is used:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;defined?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;column_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;column_names&lt;/code&gt; returns an array of column names, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include?&lt;/code&gt; method will iterate over each until it finds the column with that name, or its nothing (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;To determine if we can get rid of this allocation, we have to figure out if there’s a way to replace it without allocating memory. We need to refactor this code while maintaining correctness. I decided to add a method that converted the array of column names into a hash with symbol keys and string values:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# lib/activerecord/model_schema.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;symbol_column_to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name_symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# :nodoc:&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@symbol_column_to_string_name_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;index_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:to_sym&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@symbol_column_to_string_name_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name_symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is how you would use it:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;symbol_column_to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;email&quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;symbol_column_to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since the value that is being returned every time by this method is from the same hash, we can re-use the same string and not have to allocate. The refactored &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;respond_to&lt;/code&gt; code ends up looking like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond_to?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;include_private&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# If the result is true then check for the select case.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# For queries selecting a subset of columns, return false for unselected columns.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# We check defined?(@attributes) not to issue warnings if called on objects that&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# have been allocated but not yet initialized.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;defined?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;symbol_column_to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sym&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_attribute?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running our benchmarks, this patch yielded a reduction in memory of 1%. Using code that eventually became &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;derailed exec perf:library&lt;/code&gt;, I verified that the patch made end-to-end request/response page speed on CodeTriage 1% faster.&lt;/p&gt;

&lt;h2 id=&quot;performance-and-statistical-significance&quot;&gt;Performance and Statistical Significance&lt;/h2&gt;

&lt;p&gt;When talking about benchmarks, it’s important to talk about statistics and their impact. I talk a bit about this in &lt;a href=&quot;https://schneems.com/2020/03/17/lies-damned-lies-and-averages-perc50-perc95-ex
plained-for-programmers/&quot;&gt;Lies, Damned Lies, and Averages: Perc50, Perc95 explained for Programmers&lt;/a&gt;. Essentially any time you measure a value, there’s a chance that it could result from randomness. If you run a benchmark 3 times, it will give you 3 different results. If it shows that it was faster twice and slower once, how can you be certain that the results are because of the change and not random chance?&lt;/p&gt;

&lt;p&gt;That’s precisely the question that “statistical significance” tries to answer. While we can never know, we can make an informed decision. How? Well, if you took a measurement of the same code many times, you would know any variation was the result of randomness. This would give you a distribution of randomness. Then you could use this distribution to understand how likely it is that your change was caused by randomness.&lt;/p&gt;

&lt;p&gt;In the talk, I go into detail on the origins of “Student’s T-Test.” In derailed, I’ve switched to using Kolmogorov-Smirnov instead. When I ran benchmarks on CodeTriage, I wanted to be sure that my results were valid, so I ran them multiple times and ran Kolmogorov Smirnov on them. This gives me a confidence interval. If my results are in that interval, then I can say with 95% certainty that my results are not the result of random chance i.e., that they’re valid and are statistically significant.&lt;/p&gt;

&lt;p&gt;If it’s not significant, it could mean that the change is too small to detect, that you need more samples, or that there is no difference.&lt;/p&gt;

&lt;p&gt;In addition to running a significance check on your change, it’s useful to see the distribution. Derailed benchmarks does this for you by default now. Here is a result from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;derailed exec perf:library&lt;/code&gt; used to compare the performance difference of two different commits in a library dependency:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;                  Histogram - [winner] &quot;I am the new commit.&quot;
                           ┌                                        ┐
            [11.2 , 11.28) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 12
            [11.28, 11.36) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 22
            [11.35, 11.43) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 30
            [11.43, 11.51) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 17
   Time (s) [11.5 , 11.58) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 13
            [11.58, 11.66) ┤▇▇▇▇▇▇▇ 6
            [11.65, 11.73) ┤ 0
            [11.73, 11.81) ┤ 0
            [11.8 , 11.88) ┤ 0
                           └                                        ┘
                                      # of runs in range



                  Histogram - [loser] &quot;Old commit&quot;
                           ┌                                        ┐
            [11.2 , 11.28) ┤▇▇▇▇ 3
            [11.28, 11.36) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 19
            [11.35, 11.43) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 17
            [11.43, 11.51) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 25
   Time (s) [11.5 , 11.58) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 15
            [11.58, 11.66) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 13
            [11.65, 11.73) ┤▇▇▇▇ 3
            [11.73, 11.81) ┤▇▇▇▇ 3
            [11.8 , 11.88) ┤▇▇▇ 2
                           └                                        ┘
                                      # of runs in range
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The TLDR of this whole section is that in addition to showing my change as being faster, I was also able to show that the improvement was statistically significant.&lt;/p&gt;

&lt;h2 id=&quot;tidying-example-2-converting-strings-to-time-takes-time&quot;&gt;Tidying example 2: Converting strings to time takes time&lt;/h2&gt;

&lt;p&gt;One percent faster is good, but it could be better. Let’s do it again. First, get a pile of objects:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;$ bundle exec derailed exec perf:objects

# ...

allocated memory by file
-----------------------------------
    126489  …/code/rails/activesupport/lib/active_support/core_ext/string/output_safety.rb
     49448  …/code/codetriage/app/views/layouts/_app.html.slim
     49328  …/code/codetriage/app/views/layouts/application.html.slim
     36097  …/code/rails/activemodel/lib/active_model/type/helpers/time_value.rb
     25096  …/code/codetriage/app/views/pages/_repos_with_pagination.html.slim
     24432  …/code/rails/activesupport/lib/active_support/core_ext/object/to_query.rb
     23526  …/code/codetriage/.gem/ruby/2.5.3/gems/rack-mini-profiler-1.0.0/lib/patches/db/pg.rb
     21912  …/code/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
     18000  …/code/rails/activemodel/lib/active_model/attribute_set/builder.rb
     15888  …/code/rails/activerecord/lib/active_record/result.rb
     14610  …/code/rails/activesupport/lib/active_support/cache.rb
     11148  …/code/codetriage/.gem/ruby/2.5.3/gems/rack-mini-profiler-1.0.0/lib/mini_profiler/storage/file_store.rb
      9824  …/code/rails/actionpack/lib/abstract_controller/caching/fragments.rb
      9360  …/.rubies/ruby-2.5.3/lib/ruby/2.5.0/logger.rb
      8304  …/code/rails/activemodel/lib/active_model/attribute.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Zoom in on a file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;     36097  …/code/rails/activemodel/lib/active_model/type/helpers/time_value.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Isolate the file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;$ ALLOW_FILE=active_model/type/helpers/time_value.rb \
  bundle exec derailed exec perf:objects

Total allocated: 39617 bytes (600 objects)
Total retained:  0 bytes (0 objects)

allocated memory by gem
-----------------------------------
     39617  activemodel/lib

allocated memory by file
-----------------------------------
     39617  …/code/rails/activemodel/lib/active_model/type/helpers/time_value.rb

allocated memory by location
-----------------------------------
     17317  …/code/rails/activemodel/lib/active_model/type/helpers/time_value.rb:72
     12000  …/code/rails/activemodel/lib/active_model/type/helpers/time_value.rb:74
      6000  …/code/rails/activemodel/lib/active_model/type/helpers/time_value.rb:73
      4300  …/code/rails/activemodel/lib/active_model/type/helpers/time_value.rb:64
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We’re going to do the same thing by starting to look at the top location:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;     17317  …/code/rails/activemodel/lib/active_model/type/helpers/time_value.rb:72
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here’s the code:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fast_string_to_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ISO_DATETIME&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &amp;lt;=== line 72 Here&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;microsec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;$7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1_000_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;new_time&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;microsec&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On line 72, we are matching the input string with a regular expression constant. This allocates a lot of memory because each grouped match of the regular expression allocates a new string. To understand if we can make this faster, we have to understand how it’s used.&lt;/p&gt;

&lt;p&gt;This method takes in a string, then uses a regex to split it into parts, and then sends those parts to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_time&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;There’s not much going on that can be sped up there, but what’s happening on this line:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   &lt;span class=&quot;n&quot;&gt;microsec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;$7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1_000_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s the regex:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;ISO_DATETIME&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When I ran the code and output $7 from the regex match, I found that it would contain a string that starts with a dot and then has numbers, for example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$7&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;.1234567&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code wants microseconds as an integer, so it turns it into a “rational” and then multiplies it by a million and turns it into an integer.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;$7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1_000_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; 1234567&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You might notice that it looks like we’re basically dropping the period and then turning it into an integer. So why not do that directly?&lt;/p&gt;

&lt;p&gt;Here’s what it looks like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fast_string_to_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ISO_DATETIME&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;microsec_part&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$7&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;microsec_part&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;microsec_part&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_with?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;microsec_part&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;microsec_part&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;         &lt;span class=&quot;c1&quot;&gt;# &amp;lt;=== HERE&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;microsec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;microsec_part&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &amp;lt;=== HERE&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;microsec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;microsec_part&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1_000_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;new_time&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;microsec&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ve got to guard this case by checking for the conditions of our optimization. Now the question is: is this faster?&lt;/p&gt;

&lt;p&gt;Here’s a microbenchmark:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;original_string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;.443959&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'benchmark/ips'&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ips&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;multiply&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dup&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1_000_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;new     &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dup&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_with?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compare!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Warming up --------------------------------------&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#             multiply   125.783k i/100ms&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#             new        146.543k i/100ms&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Calculating -------------------------------------&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#             multiply      1.751M (± 3.3%) i/s -      8.805M in   5.033779s&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#             new           2.225M (± 2.1%) i/s -     11.137M in   5.007110s&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Comparison:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#             new     :  2225289.7 i/s&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#             multiply:  1751254.2 i/s - 1.27x  slower&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The original code is 1.27x slower. YAY!&lt;/p&gt;

&lt;h3 id=&quot;tidying-example-3-lightning-fast-cache-keys&quot;&gt;Tidying Example 3: Lightning fast cache keys&lt;/h3&gt;

&lt;p&gt;The last speedup is kind of underwhelming, so you might wonder why I added it. If you remember our first example of optimizing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;respond_to&lt;/code&gt;, it helped to understand the broader context of how it’s used. Since this is such an expensive object allocation location, is there an opportunity to call it less or not call it at all?&lt;/p&gt;

&lt;p&gt;To find out, I added a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puts caller&lt;/code&gt; in the code and re-ran it. Here’s part of a backtrace:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;====================================================================================================
…/code/rails/activemodel/lib/active_model/type/date_time.rb:25:in `cast_value'
…/code/rails/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb:16:in `cast_value'
…/code/rails/activemodel/lib/active_model/type/value.rb:38:in `cast'
…/code/rails/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb:12:in `block in initialize'
…/code/rails/activemodel/lib/active_model/type/value.rb:24:in `deserialize'
…/.rubies/ruby-2.5.3/lib/ruby/2.5.0/delegate.rb:349:in `block in delegating_block'
…/code/rails/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb:8:in `deserialize'
…/code/rails/activemodel/lib/active_model/attribute.rb:164:in `type_cast'
…/code/rails/activemodel/lib/active_model/attribute.rb:42:in `value'
…/code/rails/activemodel/lib/active_model/attribute_set.rb:48:in `fetch_value'
…/code/rails/activerecord/lib/active_record/attribute_methods/read.rb:77:in `_read_attribute'
…/code/rails/activerecord/lib/active_record/attribute_methods/read.rb:40:in `__temp__57074616475646f51647'
…/code/rails/activesupport/lib/active_support/core_ext/object/try.rb:16:in `public_send'
…/code/rails/activesupport/lib/active_support/core_ext/object/try.rb:16:in `try'
…/code/rails/activerecord/lib/active_record/integration.rb:99:in `cache_version'
…/code/rails/activerecord/lib/active_record/integration.rb:68:in `cache_key'
…/code/rails/activesupport/lib/active_support/cache.rb:639:in `expanded_key'
…/code/rails/activesupport/lib/active_support/cache.rb:644:in `block in expanded_key'
…/code/rails/activesupport/lib/active_support/cache.rb:644:in `collect'
…/code/rails/activesupport/lib/active_support/cache.rb:644:in `expanded_key'
…/code/rails/activesupport/lib/active_support/cache.rb:608:in `normalize_key'
…/code/rails/activesupport/lib/active_support/cache.rb:565:in `block in read_multi_entries'
…/code/rails/activesupport/lib/active_support/cache.rb:564:in `each'
…/code/rails/activesupport/lib/active_support/cache.rb:564:in `read_multi_entries'
…/code/rails/activesupport/lib/active_support/cache.rb:387:in `block in read_multi'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I followed it backwards until I hit these two places:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;…/code/rails/activerecord/lib/active_record/integration.rb:99:in `cache_version'
…/code/rails/activerecord/lib/active_record/integration.rb:68:in `cache_key'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It looks like this expensive code is being called while generating a cache key.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cache_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_record?&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cache_key&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/new&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache_version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;none?&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &amp;lt;== line 68 here&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cache_key&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;any?&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Deprecation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MSG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;squish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
          Specifying a timestamp name for #cache_key has been deprecated in favor of
          the explicit #cache_version method that can be overwritten.
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;        MSG&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;max_updated_column_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;max_updated_column_timestamp&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;utc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache_timestamp_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cache_key&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cache_key&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On line 68 in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cache_key&lt;/code&gt; code it calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cache_version&lt;/code&gt;. Here’s the code for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cache_version&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cache_version&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &amp;lt;== line 99 here&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache_versioning&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:updated_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;utc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:usec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here is our culprit:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:updated_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What is happening is that some database adapters, such as the one for Postgres, returned their values from the database driver as strings. Then active record will lazily cast them into Ruby objects when they are needed. In this case, our time value method is being called to convert the updated timestamp into a time object so we can use it to generate a cache version string.&lt;/p&gt;

&lt;p&gt;Here’s the value before it’s converted:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;updated_at_before_type_cast&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;2019-04-24 21:21:09.232249&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here’s the value after it’s converted:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;updated_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:usec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;20190424212109232249&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Basically, all the code is doing is trimming out the non-integer characters. Like before, we need a guard that our optimization can be applied:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Detects if the value before type cast&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# can be used to generate a cache_version.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# The fast cache version only works with a&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# string value directly from the database.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# We also must check if the timestamp format has been changed&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# or if the timezone is not set to UTC then&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# we cannot apply our transformations correctly.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;can_use_fast_cache_version?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cache_timestamp_format&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:usec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;default_timezone&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:utc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;updated_at_came_from_user?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then once we’re in that state, we can modify the string directly:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Converts a raw database string to `:usec`&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# format.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Example:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   timestamp = &quot;2018-10-15 20:02:15.266505&quot;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   raw_timestamp_to_cache_version(timestamp)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   # =&amp;gt; &quot;20181015200215266505&quot;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# PostgreSQL truncates trailing zeros,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# https://github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# to account for this we pad the output with zeros&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;raw_timestamp_to_cache_version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;- :.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ljust&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s some extra logic due to the Postgres truncation behavior linked above. The resulting code to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cache_version&lt;/code&gt; becomes:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cache_version&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache_versioning&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_attribute?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updated_at_before_type_cast&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;can_use_fast_cache_version?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;raw_timestamp_to_cache_version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elsif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updated_at&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;utc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache_timestamp_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s the opportunity. What’s the impact?&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-lang-term&quot;&gt;Before: Total allocated: 743842 bytes (6626 objects)
After:  Total allocated: 702955 bytes (6063 objects)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The bytes reduced is 5% fewer allocations. Which is pretty good. How does it translate to speed?&lt;/p&gt;

&lt;p&gt;It turns out that time conversion is very CPU intensive and changing this code makes the target application up to 1.12x faster. This means that if your app previously required 100 servers to run, it can now run with about 88 servers.&lt;/p&gt;

&lt;h2 id=&quot;wrap-up&quot;&gt;Wrap up&lt;/h2&gt;

&lt;p&gt;Adding together these optimizations and others brings the overall performance improvement to 1.23x or a net reduction of 19 servers. Basically, it’s like buying 4 servers and getting 1 for free.&lt;/p&gt;

&lt;p&gt;These examples were picked from my changes to the Rails codebase, but you can use them to optimize your applications. The general framework looks like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Get a list of all your memory&lt;/li&gt;
  &lt;li&gt;Zoom in on a hotspot&lt;/li&gt;
  &lt;li&gt;Figure out what is causing that memory to be allocated inside of your code&lt;/li&gt;
  &lt;li&gt;Ask if you can refactor your code to not depend on those allocations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to learn more about memory, here are my recommendations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.schneems.com/2019/11/07/why-does-my-apps-memory-usage-grow-asymptotically-over-time/&quot;&gt;Why does my App’s Memory Use Grow Over Time?&lt;/a&gt;  - Start here, an excellent high-level overview of what causes a system’s memory to grow that will help you develop an understanding of how Ruby allocates and uses memory at the application level.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.railsspeed.com&quot;&gt;Complete Guide to Rails Performance (Book)&lt;/a&gt; - This book is by Nate Berkopec and is excellent. I recommend it to someone at least once a week.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.sitepoint.com/ruby-uses-memory/&quot;&gt;How Ruby uses memory&lt;/a&gt; - This is a lower level look at precisely what “retained” and “allocated” memory means. It uses small scripts to demonstrate Ruby memory behavior. It also explains why the “total max” memory of our system rarely goes down.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.schneems.com/2015/05/11/how-ruby-uses-memory.html&quot;&gt;How Ruby uses memory (Video)&lt;/a&gt; - If you’re new to the concepts of object allocation, this might be an excellent place to start (you can skip the first story in the video, the rest are about memory). Memory stuff starts at 13 minutes&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.schneems.com/2017/04/12/jumping-off-the-memory-cliff/&quot;&gt;Jumping off the Ruby Memory Cliff&lt;/a&gt; - Sometimes you might see a ‘cliff’ in your memory metrics or a saw-tooth pattern. This article explores why that behavior exists and what it means.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://devcenter.heroku.com/articles/ruby-memory-use&quot;&gt;Ruby Memory Use (Heroku Devcenter article I maintain)&lt;/a&gt; - This article focuses on alleviating the symptoms of high memory use.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.codeship.com/debugging-a-memory-leak-on-heroku/&quot;&gt;Debugging a memory leak on Heroku&lt;/a&gt; - TLDR; It’s probably not a leak. Still worth reading to see how you can come to the same conclusions yourself. Content is valid for other environments than Heroku. Lots of examples of using the tool &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;derailed_benchmarks&lt;/code&gt; (that I wrote).&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://schneems.com/2020/09/16/the-lifechanging-magic-of-tidying-ruby-object-allocations/&quot;&gt;The Life-Changing Magic of Tidying Active Record Allocations (Post + Video)&lt;/a&gt; - This talk shows how I used tools to track down and eliminate memory allocations in real life. All of the examples are from patches I submitted to Rails, but the process works the same for finding allocations caused by your application logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Get ahold of Richard and stay up-to-date with Ruby, Rails, and other programming related content through a &lt;a href=&quot;https://www.schneems.com/mailinglist&quot;&gt;subscription to his mailing list&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
        <pubDate>Wed, 16 Sep 2020 00:00:00 +0000</pubDate>
        <link>https://www.schneems.com/2020/09/16/the-lifechanging-magic-of-tidying-ruby-object-allocations/</link>
        <guid isPermaLink="true">https://www.schneems.com/2020/09/16/the-lifechanging-magic-of-tidying-ruby-object-allocations/</guid>
      </item>
    
      <item>
        <title>A Fast Car Needs Good Brakes: How We Added Client Rate Throttling to the Platform API Gem</title>
        <description>&lt;p&gt;When API requests are made one-after-the-other they’ll quickly hit rate limits and when that happens:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;If you provide an API client that doesn&amp;#39;t include rate limiting, you don&amp;#39;t really have an API client. You&amp;#39;ve got an exception generator with a remote timer.&lt;/p&gt;&amp;mdash; Richard Schneeman 🤠 Stay Inside (@schneems) &lt;a href=&quot;https://twitter.com/schneems/status/1138899094137651200?ref_src=twsrc%5Etfw&quot;&gt;June 12, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;That tweet spawned a discussion that generated a quest to add rate throttling logic to the &lt;a href=&quot;https://rubygems.org/gems/platform-api&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;platform-api&lt;/code&gt;&lt;/a&gt; gem that Heroku maintains for talking to its API in Ruby.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;This blog post is originally published on the &lt;a href=&quot;https://blog.heroku.com/rate-throttle-api-client&quot;&gt;Heroku Engineering Blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;If the term “rate throttling” is new to you, read &lt;a href=&quot;https://schneems.com/2020/06/25/rate-limiting-rate-throttling-and-how-they-work-together/&quot;&gt;Rate limiting, rate throttling, and how they work together&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Heroku API uses &lt;a href=&quot;https://brandur.org/rate-limiting&quot;&gt;Genetic Cell Rate Algorithm (GCRA) as described by Brandur in this post&lt;/a&gt; on the server-side. Heroku’s &lt;a href=&quot;https://devcenter.heroku.com/articles/platform-api-reference#rate-limits&quot;&gt;API docs&lt;/a&gt; state:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The API limits the number of requests each user can make per hour to protect against abuse and buggy code. Each account has a pool of request tokens that can hold at most 4500 tokens. Each API call removes one token from the pool. Tokens are added to the account pool at a rate of roughly 75 per minute (or 4500 per hour), up to a maximum of 4500. If no tokens remain, further calls will return 429 Too Many Requests until more tokens become available.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I needed to write an algorithm that never errored as a result of a 429 response. A “simple” solution would be to add a retry to all requests when they see a 429, but that would effectively DDoS the API. I made it a goal for the rate throttling client also to minimize its retry rate. That is, if the client makes 100 requests, and 10 of them are a 429 response that its retry rate is 10%. Since the code needed to be contained entirely in the client library, it needed to be able to function without distributed coordination between multiple clients on multiple machines except for whatever information the Heroku API returned.&lt;/p&gt;

&lt;h2 id=&quot;making-client-throttling-maintainable&quot;&gt;Making client throttling maintainable&lt;/h2&gt;

&lt;p&gt;Before we can get into what logic goes into a quality rate throttling algorithm, I want to talk about the process that I used as I think the journey is just as fascinating as the destination.&lt;/p&gt;

&lt;p&gt;I initially started wanting to write tests for my rate throttling strategy. I quickly realized that while testing the behavior “retries a request after a 429 response,” it is easy to check. I also found that checking for quality “this rate throttle strategy is better than others” could not be checked quite as easily. The solution that I came up with was to write a simulator in addition to tests. I would simulate the server’s behavior, and then boot up several processes and threads and hit the simulated server with requests to observe the system’s behavior.&lt;/p&gt;

&lt;p&gt;I initially just output values to the CLI as the simulation ran, but found it challenging to make sense of them all, so I added charting. I found my simulation took too long to run and so I added a mechanism to speed up the simulated time. I used those two outputs to write what I thought was a pretty good rate throttling algorithm. The next task was wiring it up to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;platform-api&lt;/code&gt; gem.&lt;/p&gt;

&lt;p&gt;To help out I paired with &lt;a href=&quot;https://twitter.com/lolaodelola&quot;&gt;a Heroku Engineer, Lola&lt;/a&gt;, we ended up making several PRs to a bunch of related projects, and that’s its own story to tell. Finally, the day came where we were ready to get rate throttling into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;platform-api&lt;/code&gt; gem; all we needed was a review.&lt;/p&gt;

&lt;p&gt;Unfortunately, the algorithm I developed from “watching some charts for a few hours” didn’t make a whole lot of sense, and it was painfully apparent that it wasn’t maintainable. While I had developed a good gut feel for what a “good” algorithm did and how it behaved, I had no way of solidifying that knowledge into something that others could run with. Imagine someone in the future wants to make a change to the algorithm, and I’m no longer here. The tests I had could prevent them from breaking some expectations, but there was nothing to help them make a better algorithm.&lt;/p&gt;

&lt;h2 id=&quot;the-making-of-an-algorithm&quot;&gt;The making of an algorithm&lt;/h2&gt;

&lt;p&gt;At this point, I could explain the approach I had taken to build an algorithm, but I had no way to quantify the “goodness” of my algorithm. That’s when I decided to throw it all away and start from first principles. Instead of asking “what would make my algorithm better,” I asked, “how would I know a change to my algorithm is better” and then worked to develop some ways to quantify what “better” meant. Here are the goals I ended up coming up with:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Minimize average retry rate: The fewer failed API requests, the better&lt;/li&gt;
  &lt;li&gt;Minimize maximum sleep time: Rate throttling involves waiting, and no one wants to wait for too long&lt;/li&gt;
  &lt;li&gt;Minimize variance of request count between clients: No one likes working with a greedy co-worker, API clients are no different. No client in the distributed system should be an extended outlier&lt;/li&gt;
  &lt;li&gt;Minimize time to clear a large request capacity: As the system changes, clients should respond quickly to changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I figured that if I could generate metrics on my rate-throttle algorithm and compare it to simpler algorithms, then I could show why individual decisions were made.&lt;/p&gt;

&lt;p&gt;I moved my hacky scripts for my simulation into a separate repo and, rather than relying on watching charts and logs, moved to have my simulation &lt;a href=&quot;https://github.com/zombocom/rate_throttle_client/blob/master/lib/rate_throttle_client/demo.rb&quot;&gt;produce numbers that could be used to quantify and compare algorithms&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With that work under my belt, I threw away everything I knew about rate-throttling and decided to use science and measurement to guide my way.&lt;/p&gt;

&lt;h2 id=&quot;writing-a-better-rate-throttling-algorithm-with-science-exponential-backoff&quot;&gt;Writing a better rate-throttling algorithm with science: exponential backoff&lt;/h2&gt;

&lt;p&gt;Earlier I mentioned that a “simple” algorithm would be to retry requests. A step up in complexity and functionality would be to retry requests after an exponential backoff. I coded it up and got some numbers for a simulated 30-minute run (which takes 3 minutes of real-time):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Avg retry rate:      60.08 %
Max sleep time:      854.89 seconds
Stdev Request Count: 387.82

Time to clear workload (4500 requests, starting_sleep: 1s):
74.23 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now that we’ve got baseline numbers, how could we work to minimize any of these values? In my initial exponential backoff model, I multiplied sleep by a factor of 2.0, what would happen if I increased it to 3.0 or decreased it to 1.2?&lt;/p&gt;

&lt;p&gt;To find out, I plugged in those values and re-ran my simulations. I found that there was a correlation between retry rate and max sleep value with the backoff factor, but they were inverse. I could lower the retry rate by increasing the factor (to 3.0), but this increased my maximum sleep time. I could reduce the maximum sleep time by decreasing the factor (to 1.2), but it increased my retry rate.&lt;/p&gt;

&lt;p&gt;That experiment told me that if I wanted to optimize both retry rate and sleep time, I could not do it via only changing the exponential factor since an improvement in one meant a degradation in the other value.&lt;/p&gt;

&lt;p&gt;At this point, we could theoretically do anything, but our metrics judge our success. We could put a cap on the maximum sleep time, for example, we could write code that says “don’t sleep longer than 300 seconds”, but it too would hurt the retry rate. The biggest concern for me in this example is the maximum sleep time, 854 seconds is over 14 minutes which is WAAAYY too long for a single client to be sleeping.&lt;/p&gt;

&lt;p&gt;I ended up picking the 1.2 factor to decrease that value at the cost of a worse retry-rate:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Avg retry rate:      80.41 %
Max sleep time:      46.72 seconds
Stdev Request Count: 147.84

Time to clear workload (4500 requests, starting_sleep: 1s):
74.33 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Forty-six seconds is better than 14 minutes of sleep by a long shot. How could we get the retry rate down?&lt;/p&gt;

&lt;h2 id=&quot;incremental-improvement-exponential-sleep-with-a-gradual-decrease&quot;&gt;Incremental improvement: exponential sleep with a gradual decrease&lt;/h2&gt;

&lt;p&gt;In the exponential backoff model, it backs-off once it sees a 429, but as soon as it hits a success response, it doesn’t sleep at all. One way to reduce the retry-rate would be to assume that once a request had been rate-throttled, that future requests would need to wait as well. Essentially we would make the sleep value “sticky” and sleep before all requests. If we only remembered the sleep value, our rate throttle strategy wouldn’t be responsive to any changes in the system, and it would have a poor “time to clear workload.” Instead of only remembering the sleep value, we can gradually reduce it after every successful request. This logic is very similar to &lt;a href=&quot;https://en.wikipedia.org/wiki/TCP_congestion_control#Slow_start&quot;&gt;TCP slow start&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How does it play out in the numbers?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Avg retry rate:      40.56 %
Max sleep time:      139.91 seconds
Stdev Request Count: 867.73

Time to clear workload (4500 requests, starting_sleep: 1s):
115.54 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Retry rate did go down by about half. Sleep time went up, but it’s still well under the 14-minute mark we saw earlier. But there’s a problem with a metric I’ve not talked about before, the “stdev request count.” It’s easier to understand if you look at a chart to see what’s going on:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.dropbox.com/s/ipctuotj4tz1kwa/ExponentialBackoff.png?raw=1&quot; alt=&quot;Exponential sleep with gradual decrease chart&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here you can see one client is sleeping a lot (the red client) while other clients are not sleeping at all and chewing through all the available requests at the bottom. Not all the clients are behaving equitably. This behavior makes it harder to tune the system.&lt;/p&gt;

&lt;p&gt;One reason for this inequity is that all clients are decreasing by the same constant value for every successful request. For example, let’s say we have a client A that is sleeping for 44 seconds, and client B that is sleeping for 11 seconds and both decrease their sleep value by 1 second after every request. If both clients ran for 45 seconds, it would look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Client A) Sleep 44 (Decrease value: 1)
Client B) Sleep 11 (Decrease value: 1)
Client B) Sleep 10 (Decrease value: 1)
Client B) Sleep  9 (Decrease value: 1)
Client B) Sleep  8 (Decrease value: 1)
Client B) Sleep  7 (Decrease value: 1)
Client A) Sleep 43 (Decrease value: 1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So while client A has decreased by 1 second total, client B has reduced by 4 seconds total, since it is firing 4x as fast (i.e., it’s sleep time is 4x lower). So while the decrease rate is equal, it is not equitable. Ideally, we would want all clients to decrease at the same rate.&lt;/p&gt;

&lt;h2 id=&quot;all-clients-created-equal-exponential-increase-proportional-decrease&quot;&gt;All clients created equal: exponential increase proportional decrease&lt;/h2&gt;

&lt;p&gt;Since clients cannot communicate with each other in our distributed system, one way to guaranteed proportional decreases is to use the sleep value in the decrease amount:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;decrease_value = (sleep_time) / some_value
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;some_value&lt;/code&gt; is a magic number. In this scenario the same clients A and B running for 45 seconds would look like this with a value of 100:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Client A) Sleep 44
Client B) Sleep 11
Client B) Sleep 10.89 (Decrease value: 11.00/100 = 0.1100)
Client B) Sleep 10.78 (Decrease value: 10.89/100 = 0.1089)
Client B) Sleep 10.67 (Decrease value: 10.78/100 = 0.1078)
Client B) Sleep 10.56 (Decrease value: 10.67/100 = 0.1067)
Client A) Sleep 43.56 (Decrease value: 44.00/100 = 0.4400)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now client A has had a decrease of 0.44, and client B has had a reduction of 0.4334 (11 seconds - 10.56 seconds), which is a lot more equitable than before. Since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;some_value&lt;/code&gt; is tunable, I wanted to use a larger number so that the retry rate would be lower than 40%. I chose 4500 since that’s the maximum number of requests in the GCRA bucket for Heroku’s API.&lt;/p&gt;

&lt;p&gt;Here’s what the results looked like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Avg retry rate:      3.66 %
Max sleep time:      17.31 seconds
Stdev Request Count: 101.94

Time to clear workload (4500 requests, starting_sleep: 1s):
551.10 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The retry rate went WAAAY down, which makes sense since we’re decreasing slower than before (the constant decrease value previously was 0.8). Stdev went way down as well. It’s about 8x lower. Surprisingly the max sleep time went down as well. I believe this to be a factor of a decrease in the number of required exponential backoff events. Here’s what this algorithm looks like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.dropbox.com/s/hityqgl9vgqcon8/ExponentialIncreaseProportionalDecrease.png?raw=1&quot; alt=&quot;Exponential increase proportional decrease chart&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The only problem here is that the “time to clear workload” is 5x higher than before. What exactly is being measured here? In this scenario, we’re simulating a cyclical workflow where clients are running under high load, then go through a light load, and then back to a high load. The simulation starts all clients with a sleep value, but the server’s rate-limit is reset to 4500. The time is how long it takes the client to clear all 4500 requests.&lt;/p&gt;

&lt;p&gt;What this metric of 551 seconds is telling me is that this strategy is not very responsive to a change in the system. To illustrate this problem, I ran the same algorithm starting each client at 8 seconds of sleep instead of 1 second to see how long it would take to trigger a rate limit:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://heroku-blog-files.s3.amazonaws.com/posts/1594143623-CleanShot%202020-07-07%20at%2010.39.35%402x.png&quot; alt=&quot;Exponential increase proportional decrease chart 7-hour torture test&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The graph shows that it takes about 7 hours to clear all these requests, which is not good. What we need is a way to clear requests faster when there are more requests.&lt;/p&gt;

&lt;h2 id=&quot;the-only-remaining-option-exponential-increase-proportional-remaining-decrease&quot;&gt;The only remaining option: exponential increase proportional remaining decrease&lt;/h2&gt;

&lt;p&gt;When you make a request to the Heroku API, it tells you how many requests you have left remaining in your bucket in a header. Our problem with the “proportional decrease” is mostly that when there are lots of requests remaining in the bucket, it takes a long time to clear them (if the prior sleep rate was high, such as in a varying workload). To account for this, we can decrease the sleep value quicker when the remaining bucket is full and slower when the remaining bucket is almost empty. To express that in an expression, it might look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;decrease_value = (sleep_time * request_count_remaining) / some_value
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In my case, I chose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;some_value&lt;/code&gt; to be the maximum number of requests possible in a bucket, which is 4500. You can imagine a scenario where workers were very busy for a period and being rate limited. Then no jobs came in for over an hour - perhaps the workday was over, and the number of requests remaining in the bucket re-filled to 4500. On the next request, this algorithm would reduce the sleep value by itself since 4500/4500 is one:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;decrease_value = sleep_time * 4500 / 4500
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That means it doesn’t matter how immense the sleep value is, it will adjust fairly quickly to a change in workload. Good in theory, how does it perform in the simulation?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Avg retry rate:      3.07 %
Max sleep time:      17.32 seconds
Stdev Request Count: 78.44

Time to clear workload (4500 requests, starting_sleep: 1s):
84.23 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This rate throttle strategy performs very well on all metrics. It is the best (or very close) to several metrics. Here’s a chart:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.dropbox.com/s/ixwy5quq2y8uyjw/ExponentialIncreaseProportionalRemainingDecrease.png?raw=1&quot; alt=&quot;Exponential increase proportional remaining decrease chart&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This strategy is the “winner” of my experiments and the algorithm that I  chose to go into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;platform-api&lt;/code&gt; gem.&lt;/p&gt;

&lt;h2 id=&quot;my-original-solution&quot;&gt;My original solution&lt;/h2&gt;

&lt;p&gt;While I originally built this whole elaborate scheme to prove how my solution was optimal, I did something by accident. By following a scientific and measurement-based approach, I accidentally found a simpler solution that performed better than my original answer. Which I’m happier about, it shows that the extra effort was worth it. To “prove” what I found by observation and tinkering could be not only quantified by numbers but improved upon is fantastic.&lt;/p&gt;

&lt;p&gt;While my original solution had some scripts and charts, this new solution has tests covering the behavior of the simulation and charting code. My initial solution was very brittle. I didn’t feel very comfortable coming back and making changes to it; this new solution and the accompanying support code is a joy to work with. My favorite part though is that now if anyone asks me, “what about trying &lt;x&gt;&quot; or &quot;have you considered &lt;y&gt;&quot; is that I can point them at [my rate client throttling library](https://github.com/zombocom/rate_throttle_client), they have all the tools to implement their idea, test it, and report back with a swift feedback loop.&lt;/y&gt;&lt;/x&gt;&lt;/p&gt;

&lt;h2 id=&quot;gem-platform-api--30&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gem 'platform-api', '~&amp;gt; 3.0'&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;While I mostly wanted to talk about the process of writing rate-throttling code, this whole thing started from a desire to get client rate-throttling into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;platform-api&lt;/code&gt; gem. Once I did the work to prove my solution was reasonable, we worked on a rollout strategy. We released a version of the gem in a minor bump with rate-throttling available, but with a “null” strategy that would preserve existing behavior. This release strategy allowed us to issue a warning to anyone depending on the original behavior. Then we released a major version with the rate-throttling strategy enabled by default. We did this first with “pre” release versions and then actual versions to be extra safe.&lt;/p&gt;

&lt;p&gt;So far, the feedback has been overwhelming that no one has noticed. We didn’t cause any significant breaks or introduce any severe disfunction to any applications. If you’ve not already, I invite you to upgrade to 3.0.0+ of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;platform-api&lt;/code&gt; gem and give it a spin. I would love to hear your feedback.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Get ahold of Richard and stay up-to-date with Ruby, Rails, and other programming related content through a &lt;a href=&quot;https://www.schneems.com/mailinglist&quot;&gt;subscription to his mailing list&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
        <pubDate>Wed, 08 Jul 2020 00:00:00 +0000</pubDate>
        <link>https://www.schneems.com/2020/07/08/a-fast-car-needs-good-brakes-how-we-added-client-rate-throttling-to-the-platform-api-gem/</link>
        <guid isPermaLink="true">https://www.schneems.com/2020/07/08/a-fast-car-needs-good-brakes-how-we-added-client-rate-throttling-to-the-platform-api-gem/</guid>
      </item>
    
  </channel>
</rss>
