<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://ulysseszh.github.io/feed/tags/nix.xml" rel="self" type="application/atom+xml" /><link href="https://ulysseszh.github.io/" rel="alternate" type="text/html" hreflang="en-US" /><updated>2026-04-19T16:48:01-07:00</updated><id>https://ulysseszh.github.io/feed/tags/nix.xml</id><title type="html"><![CDATA[Ulysses’ trip]]></title><subtitle>Here we are at the awesome (awful) blog written by UlyssesZhan!</subtitle><author><name>UlyssesZhan</name><email>ulysseszhan@gmail.com</email></author><entry><title type="html"><![CDATA[Ways of reducing mass rebuilds in Nix without changing Nix]]></title><link href="https://ulysseszh.github.io/programming/2026/04/16/reduce-nix-rebuild.html" rel="alternate" type="text/html" title="Ways of reducing mass rebuilds in Nix without changing Nix" /><published>2026-04-16T09:45:36-07:00</published><updated>2026-04-16T09:45:36-07:00</updated><id>https://ulysseszh.github.io/programming/2026/04/16/reduce-nix-rebuild</id><content type="html" xml:base="https://ulysseszh.github.io/programming/2026/04/16/reduce-nix-rebuild.html"><![CDATA[<h2 data-label="0.1" id="introduction">Introduction</h2>
<p>Nix as a software package manager is amazing in its reproducibility, the cost of which, however, is the problem of mass rebuilds. When a package gets an update, every package that depends on it, directly or indirectly, will be rebuilt. This means that even a small update can lead to conceptually unnecessary mass rebuilds involving thousands of packages or even much more if the update touches a package on which many packages depend, such as <code>glibc</code>. This imposes huge burden on all kinds of resources, such as CPU resources (compiling software is generally CPU intense), storage resources (different rebuilds of a software reside in different store paths), and network resources (all users of rebuilt packages have to redownload them from the binary caches).</p>
<p>Nixpkgs has staging branches to manage such mass rebuilds. Every update in Nixpkgs that triggers mass rebuilds will go through a staging process, which takes some time for it to be merged into the master branch. If updates that conceptually do not require mass rebuilds can actually be done without mass rebuilds, then they can be applied to the master branch much more quickly than going through the staging process.</p>
<p>This article aims at brainstorming some ways to reduce mass rebuilds without changing Nix itself. In other words, I will think of some ways in which we can refactor Nixpkgs or develop alternative package sets to avoid some mass rebuilds without switching to another implementation of Nix language or ditching Nix altogether.</p>
<p>The most common kinds of dependencies between Nix derivations are:</p>
<ol type="1">
<li>The output of the dependent derivation includes the out path of the depended derivation. This happens when you use <code>autoPatchelf</code> and <code>patchShebangs</code>.</li>
<li>Besides the point above, the builder of the dependent derivation actually also needs to look at the file contents of the output of the depended derivation. This happens when you compile a software against the depended library.</li>
<li>The builder of the dependent derivation runs executables or calls functions in the depended derivation. This happens when the depended software is a compiler used to compile the dependent software.</li>
</ol>
<p class="no-indent">
I am going to try to pose some ideas to improve the situation for each of the three kinds of dependencies.
</p>
<h2 data-label="0.2" id="store-path-replacements">Store path replacements</h2>
<p>For the first kind of dependencies, conceptually there is absolutely no need to rebuild the dependent software. Building the dependent derivation is bound to succeed, and the result in the output will differ by nothing more than the updated store path of the depended derivation. Therefore, the idea to solve this is simple: instead of rebuilding the dependent derivation from scratch, simply substitute the store path in the output of the dependent derivation.</p>
<p>Now, let us take a simple example. Assume that <code>foo_1</code> is a program that simply outputs <code>hello</code> to the standard output and that <code>foo_2</code> is a program that simply outputs <code>howdy</code> to the standard output. Assume that <code>bar</code> is simply a wrapper around <code>foo</code> made using <code>makeWrapper</code>. In the builder of <code>bar</code>, I will include a <code>sleep</code> command to simulate a time-consuming building process. An update in its dependent is then simulated as we switch from <code>foo = foo_1</code> to <code>foo = foo_2</code>. When you build <code>bar</code>, it will take you 5 seconds of waiting during the build. Then, when you switch to <code>foo = foo_2</code> and build <code>bar</code> again, it will take you another 5 seconds of waiting during the build, which is conceptually totally unnecessary because that part does not change for the <code>foo</code> update at all.</p>
<details>
<summary>
Supposed packaging in Nixpkgs
</summary>
<p>This is what it would look like if <code>bar</code> is packaged in Nixpkgs:</p>
<table class="rouge-table"><tbody><tr><td class="highlight language-nix"><pre><code><span class="line line-1"><span class="c"># bar.nix</span>
</span><span class="line line-2"><span class="p">{</span>
</span><span class="line line-3">	<span class="nv">lib</span><span class="p">,</span>
</span><span class="line line-4">	<span class="nv">stdenv</span><span class="p">,</span>
</span><span class="line line-5">	<span class="nv">writeShellScriptBin</span><span class="p">,</span>
</span><span class="line line-6">	<span class="nv">makeWrapper</span><span class="p">,</span>
</span><span class="line line-7"><span class="p">}:</span>
</span><span class="line line-8">
</span><span class="line line-9"><span class="kd">let</span>
</span><span class="line line-10">	<span class="nv">foo_1</span> <span class="o">=</span> <span class="nv">writeShellScriptBin</span> <span class="s2">"foo"</span> <span class="s2">"echo hello"</span><span class="p">;</span>
</span><span class="line line-11">	<span class="nv">foo_2</span> <span class="o">=</span> <span class="nv">writeShellScriptBin</span> <span class="s2">"foo"</span> <span class="s2">"echo howdy"</span><span class="p">;</span>
</span><span class="line line-12">	<span class="nv">foo</span> <span class="o">=</span> <span class="nv">foo_1</span><span class="p">;</span>
</span><span class="line line-13"><span class="kn">in</span>
</span><span class="line line-14"><span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-15">	<span class="nv">pname</span> <span class="o">=</span> <span class="s2">"bar"</span><span class="p">;</span>
</span><span class="line line-16">	<span class="nv">version</span> <span class="o">=</span> <span class="s2">"1.0.0"</span><span class="p">;</span>
</span><span class="line line-17">
</span><span class="line line-18">	<span class="nv">dontUnpack</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span><span class="line line-19">
</span><span class="line line-20">	<span class="nv">nativeBuildInputs</span> <span class="o">=</span> <span class="p">[</span> <span class="nv">makeWrapper</span> <span class="p">];</span>
</span><span class="line line-21">
</span><span class="line line-22">	<span class="nv">buildPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-23"><span class="s2">		runHook preBuild</span>
</span><span class="line line-24"><span class="s2">		sleep 5 # simulate a time-consuming building process</span>
</span><span class="line line-25"><span class="s2">		runHook postBuild</span>
</span><span class="line line-26"><span class="s2">	''</span><span class="p">;</span>
</span><span class="line line-27">
</span><span class="line line-28">	<span class="nv">installPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-29"><span class="s2">		runHook preInstall</span>
</span><span class="line line-30"><span class="s2">		makeWrapper </span><span class="si">${</span><span class="nv">lib</span><span class="o">.</span><span class="nv">getExe</span> <span class="nv">foo</span><span class="si">}</span><span class="s2"> $out/bin/bar</span>
</span><span class="line line-31"><span class="s2">		runHook postInstall</span>
</span><span class="line line-32"><span class="s2">	''</span><span class="p">;</span>
</span><span class="line line-33">
</span><span class="line line-34">	<span class="nv">meta</span><span class="o">.</span><span class="nv">mainProgram</span> <span class="o">=</span> <span class="s2">"bar"</span><span class="p">;</span>
</span><span class="line line-35"><span class="p">}</span>
</span></code></pre></td></tr></tbody></table>
<p class="no-indent">
To build it, run
</p>
<table class="rouge-table"><tbody><tr><td class="highlight language-bash"><pre><code><span class="line line-1">nix-build <span class="nt">-E</span> <span class="s1">'(import &lt;nixpkgs&gt; {}).callPackage ./bar.nix {}'</span>
</span></code></pre></td></tr></tbody></table>
<p class="no-indent">
After that, you can run <code>bar</code> by running <code>result/bin/bar</code>.
</p>
</details>
<p>How can we prevent this rebuild? We can have an intermediate derivation <code>barWithStub</code>, which references the out path of a derivation <code>fooStub</code> instead of <code>foo</code>. Because <code>fooStub</code> does not care about <code>foo</code>, neither does <code>barWithStub</code>. Then, the builder of <code>bar</code> then takes everything from the output of <code>barWithStub</code>, and replace the store path of <code>fooStub</code> with that of <code>foo</code>.</p>
<details>
<summary>
Implementation
</summary>
<p>Here is an implementation of this idea:</p>
<table class="rouge-table"><tbody><tr><td class="highlight language-nix"><pre><code><span class="line line-1"><span class="c"># bar.nix</span>
</span><span class="line line-2"><span class="p">{</span>
</span><span class="line line-3">	<span class="nv">lib</span><span class="p">,</span>
</span><span class="line line-4">	<span class="nv">stdenv</span><span class="p">,</span>
</span><span class="line line-5">	<span class="nv">writeScriptBin</span><span class="p">,</span>
</span><span class="line line-6">	<span class="nv">writeShellScriptBin</span><span class="p">,</span>
</span><span class="line line-7">	<span class="nv">makeWrapper</span><span class="p">,</span>
</span><span class="line line-8"><span class="p">}:</span>
</span><span class="line line-9">
</span><span class="line line-10"><span class="kd">let</span>
</span><span class="line line-11">	<span class="nv">fooStub</span> <span class="o">=</span> <span class="nv">writeScriptBin</span> <span class="s2">"foo"</span> <span class="s2">""</span><span class="p">;</span>
</span><span class="line line-12">
</span><span class="line line-13">	<span class="nv">foo_1</span> <span class="o">=</span> <span class="nv">writeShellScriptBin</span> <span class="s2">"foo"</span> <span class="s2">"echo hello"</span><span class="p">;</span>
</span><span class="line line-14">	<span class="nv">foo_2</span> <span class="o">=</span> <span class="nv">writeShellScriptBin</span> <span class="s2">"foo"</span> <span class="s2">"echo howdy"</span><span class="p">;</span>
</span><span class="line line-15">	<span class="nv">foo</span> <span class="o">=</span> <span class="nv">foo_1</span><span class="p">;</span>
</span><span class="line line-16">
</span><span class="line line-17">	<span class="nv">barWithStub</span> <span class="o">=</span> <span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-18">		<span class="nv">pname</span> <span class="o">=</span> <span class="s2">"bar"</span><span class="p">;</span>
</span><span class="line line-19">		<span class="nv">version</span> <span class="o">=</span> <span class="s2">"1.0.0"</span><span class="p">;</span>
</span><span class="line line-20">
</span><span class="line line-21">		<span class="nv">dontUnpack</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span><span class="line line-22">
</span><span class="line line-23">		<span class="nv">nativeBuildInputs</span> <span class="o">=</span> <span class="p">[</span> <span class="nv">makeWrapper</span> <span class="p">];</span>
</span><span class="line line-24">
</span><span class="line line-25">		<span class="nv">buildPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-26"><span class="s2">			runHook preBuild</span>
</span><span class="line line-27"><span class="s2">			sleep 5 # simulate a time-consuming building process</span>
</span><span class="line line-28"><span class="s2">			runHook postBuild</span>
</span><span class="line line-29"><span class="s2">		''</span><span class="p">;</span>
</span><span class="line line-30">
</span><span class="line line-31">		<span class="nv">installPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-32"><span class="s2">			runHook preInstall</span>
</span><span class="line line-33"><span class="s2">			makeWrapper </span><span class="si">${</span><span class="nv">lib</span><span class="o">.</span><span class="nv">getExe</span> <span class="nv">fooStub</span><span class="si">}</span><span class="s2"> $out/bin/bar</span>
</span><span class="line line-34"><span class="s2">			runHook postInstall</span>
</span><span class="line line-35"><span class="s2">		''</span><span class="p">;</span>
</span><span class="line line-36">
</span><span class="line line-37">		<span class="nv">meta</span><span class="o">.</span><span class="nv">mainProgram</span> <span class="o">=</span> <span class="s2">"bar"</span><span class="p">;</span>
</span><span class="line line-38">	<span class="p">};</span>
</span><span class="line line-39"><span class="kn">in</span>
</span><span class="line line-40"><span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-41">	<span class="kn">inherit</span> <span class="p">(</span><span class="nv">barWithStub</span><span class="p">)</span> <span class="nv">pname</span> <span class="nv">version</span> <span class="nv">meta</span><span class="p">;</span>
</span><span class="line line-42">	<span class="nv">dontUnpack</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span><span class="line line-43">
</span><span class="line line-44">	<span class="nv">installPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-45"><span class="s2">		runHook preInstall</span>
</span><span class="line line-46"><span class="s2">		cp -ar </span><span class="si">${</span><span class="nv">barWithStub</span><span class="si">}</span><span class="s2"> $out</span>
</span><span class="line line-47"><span class="s2">		chmod +w $out/bin/bar</span>
</span><span class="line line-48"><span class="s2">		substituteInPlace $out/bin/bar --replace-fail </span><span class="si">${</span><span class="nv">fooStub</span><span class="si">}</span><span class="s2"> </span><span class="si">${</span><span class="nv">foo</span><span class="si">}</span>
</span><span class="line line-49"><span class="s2">		runHook postInstall</span>
</span><span class="line line-50"><span class="s2">	''</span><span class="p">;</span>
</span><span class="line line-51"><span class="p">}</span>
</span></code></pre></td></tr></tbody></table>
<p>It still needs some polishing because the package as it is right now cannot have overridden attributes. However, you can easily fix it by exposing <code>barWithStub</code> in <code>bar.passthru</code>. The fully polished packaging is not important here as this code snippet already presents my idea neatly.</p>
<p>The code snippet also looks a bit redundant with boilerplate codes, but we can fix this by defining a packaging helper function to remove the common codes. We can also add a feature to <code>makeWrapper</code> to let it write something to <code>$out/nix-support</code> of the intermediate package with stub to instruct the builder of the final package about where and what the stub store paths are.</p>
</details>
<p>Notice that for cases resembling this specific example, there is actually another pattern commonly used in Nixpkgs, which is to have an intermediate package called <code>bar-unwrapped</code>, which does not use <code>makeWrapper</code> in its install phase. The final package <code>bar</code> is a <code>symlinkJoin</code> that takes the unwrapped intermediate package into its output through symbolic links instead of copying, and it has <code>makeWrapper</code> in its builder. This approach is better for this example for two reasons: (1) it saves the operation of copying the entire output of the intermediate package, which can be heavy on disk IO; (2) it does not require users consuming the binary caches to download the entire store path if the unwrapped package does not change. However, the approach of substituting stub store paths can be extended to more use cases than <code>makeWrapper</code>, notably those with <code>autoPatchelf</code> and <code>patchShebangs</code>. A natural thought is to combine the advantages of the two: use symbolic links for files that do not contain stub store paths and copy files that contain stub store paths. This interesting approach, however, has its own downsides: (1) it makes the stub derivation included in the closure of the final package; (2) it takes up more space than both alternatives if the copied files take up most of the space.</p>
<p>As a side note, it is actually a separate problem that a user consuming binary caches has to download entire store paths for what could be very small changes from store paths that already exist on the user’s machine. There is a <a href="https://alternativebit.fr/posts/nixos/future-of-nix-substitution" target="_blank" rel="external">blog article</a> by <a href="https://alternativebit.fr" target="_blank" rel="external">PicNoir</a> that explores how Nix may be improved to solve this very problem.</p>
<h2 data-label="0.3" id="separating-compiling-and-linking">Separating compiling and linking</h2>
<p>It sounds completely reasonable that a software should be rebuilt when the library it depends on gets an update. However, a certain fact makes this statement only half-true: building a software from its source code actually involves two steps, compiling and linking. What compiling needs from the depended library is only the header files, and what linking needs from the depended library is only the library files. The catch is that, in most cases, the resources taken in the build phase are dominantly taken by the compiling, but the library files are often the only files that get updated.</p>
<p>This observation is especially important for indirect dependencies. Supose that <code>foo</code> and <code>bar</code> are libraries, where <code>bar</code> depends on <code>foo</code>, and that <code>baz</code> is a program that depends on <code>bar</code>. When <code>foo</code> gets an update, <code>bar</code> is rebuilt. However, because <code>bar</code> does not get an update, its header files do not change, so <code>baz</code> does not need to be recompiled. However, how people typically package <code>baz</code> in Nixpkgs will make it have to recompile whenever <code>foo</code> gets an update.</p>
<details>
<summary>
Supposed packaging in Nixpkgs
</summary>
<p>First, I will give the source codes of <code>foo</code>, <code>bar</code>, and <code>baz</code>. For simplicity, they will be very simple: <code>foo</code> contains a function <code>kat</code>; <code>bar</code> contains a function <code>mar</code> that returns the return value of <code>kat</code>; and <code>baz</code> has a <code>main</code> function that returns the return value of <code>mar</code>. After that, I will give the Nix code that packages them.</p>
<table class="rouge-table"><tbody><tr><td class="highlight language-c"><pre><code><span class="line line-1"><span class="c1">// foo/foo.h</span>
</span><span class="line line-2"><span class="cp">#ifndef FOO_H</span>
</span><span class="line line-3"><span class="cp">#define FOO_H</span>
</span><span class="line line-4"><span class="cp">#ifndef FOO_VERSION</span>
</span><span class="line line-5"><span class="cp">#define FOO_VERSION 1</span>
</span><span class="line line-6"><span class="cp">#endif</span>
</span><span class="line line-7"><span class="kt">int</span> <span class="nf">kat</span><span class="p">();</span>
</span><span class="line line-8"><span class="cp">#endif</span>
</span></code></pre></td></tr></tbody></table>
<table class="rouge-table"><tbody><tr><td class="highlight language-c"><pre><code><span class="line line-1"><span class="c1">// foo/foo.c</span>
</span><span class="line line-2"><span class="cp">#include</span> <span class="cpf">"foo.h"</span>
</span><span class="line line-3"><span class="kt">int</span> <span class="nf">kat</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">FOO_VERSION</span><span class="p">;</span> <span class="p">}</span>
</span></code></pre></td></tr></tbody></table>
<table class="rouge-table"><tbody><tr><td class="highlight language-makefile"><pre><code><span class="line line-1"><span class="c"># foo/Makefile</span>
</span><span class="line line-2"><span class="nl">libfoo.so</span><span class="o">:</span> <span class="nf">foo.o</span>
</span><span class="line line-3">	<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-shared</span> <span class="nt">-o</span> libfoo.so foo.o
</span><span class="line line-4">
</span><span class="line line-5"><span class="nl">foo.o</span><span class="o">:</span>
</span><span class="line line-6">	<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-c</span> foo.c <span class="nt">-o</span> foo.o
</span><span class="line line-7">
</span><span class="line line-8"><span class="nl">install</span><span class="o">:</span> <span class="nf">install-lib install-include</span>
</span><span class="line line-9">
</span><span class="line line-10"><span class="nl">install-lib</span><span class="o">:</span> <span class="nf">libfoo.so</span>
</span><span class="line line-11">	<span class="nb">install</span> <span class="nt">-Dm644</span> libfoo.so <span class="nt">-t</span> <span class="p">$(</span>out<span class="p">)</span>/lib
</span><span class="line line-12">
</span><span class="line line-13"><span class="nl">install-include</span><span class="o">:</span>
</span><span class="line line-14">	<span class="nb">install</span> <span class="nt">-Dm644</span> foo.h <span class="nt">-t</span> <span class="p">$(</span>out<span class="p">)</span>/include
</span></code></pre></td></tr></tbody></table>
<table class="rouge-table"><tbody><tr><td class="highlight language-c"><pre><code><span class="line line-1"><span class="c1">// bar/bar.h</span>
</span><span class="line line-2"><span class="cp">#ifndef BAR_H</span>
</span><span class="line line-3"><span class="cp">#define BAR_H</span>
</span><span class="line line-4"><span class="kt">int</span> <span class="nf">mar</span><span class="p">();</span>
</span><span class="line line-5"><span class="cp">#endif</span>
</span></code></pre></td></tr></tbody></table>
<table class="rouge-table"><tbody><tr><td class="highlight language-c"><pre><code><span class="line line-1"><span class="c1">// bar/bar.c</span>
</span><span class="line line-2"><span class="cp">#include</span> <span class="cpf">"bar.h"</span>
</span><span class="line line-3"><span class="cp">#include</span> <span class="cpf">&lt;foo.h&gt;</span>
</span><span class="line line-4"><span class="kt">int</span> <span class="nf">mar</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">kat</span><span class="p">();</span> <span class="p">}</span>
</span></code></pre></td></tr></tbody></table>
<table class="rouge-table"><tbody><tr><td class="highlight language-makefile"><pre><code><span class="line line-1"><span class="c"># bar/Makefile</span>
</span><span class="line line-2"><span class="nl">libbar.so</span><span class="o">:</span> <span class="nf">bar.o</span>
</span><span class="line line-3">	<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-shared</span> <span class="nt">-o</span> libbar.so bar.o <span class="nt">-lfoo</span>
</span><span class="line line-4">
</span><span class="line line-5"><span class="nl">bar.o</span><span class="o">:</span>
</span><span class="line line-6">	<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-c</span> bar.c <span class="nt">-o</span> bar.o
</span><span class="line line-7">
</span><span class="line line-8"><span class="nl">install</span><span class="o">:</span> <span class="nf">install-include install-lib</span>
</span><span class="line line-9">
</span><span class="line line-10"><span class="nl">install-lib</span><span class="o">:</span> <span class="nf">libbar.so</span>
</span><span class="line line-11">	<span class="nb">install</span> <span class="nt">-Dm644</span> libbar.so <span class="nt">-t</span> <span class="p">$(</span>out<span class="p">)</span>/lib
</span><span class="line line-12">
</span><span class="line line-13"><span class="nl">install-include</span><span class="o">:</span>
</span><span class="line line-14">	<span class="nb">install</span> <span class="nt">-Dm644</span> bar.h <span class="nt">-t</span> <span class="p">$(</span>out<span class="p">)</span>/include
</span></code></pre></td></tr></tbody></table>
<table class="rouge-table"><tbody><tr><td class="highlight language-c"><pre><code><span class="line line-1"><span class="c1">// baz/baz.c</span>
</span><span class="line line-2"><span class="cp">#include</span> <span class="cpf">&lt;bar.h&gt;</span>
</span><span class="line line-3"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">mar</span><span class="p">();</span> <span class="p">}</span>
</span></code></pre></td></tr></tbody></table>
<table class="rouge-table"><tbody><tr><td class="highlight language-makefile"><pre><code><span class="line line-1"><span class="c"># baz/Makefile</span>
</span><span class="line line-2"><span class="nl">baz</span><span class="o">:</span> <span class="nf">baz.o</span>
</span><span class="line line-3">	<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-o</span> baz baz.o <span class="nt">-lbar</span>
</span><span class="line line-4">
</span><span class="line line-5"><span class="nl">baz.o</span><span class="o">:</span>
</span><span class="line line-6">	<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-c</span> baz.c <span class="nt">-o</span> baz.o
</span><span class="line line-7">	<span class="nb">sleep </span>5 <span class="c"># simulate time-consuming building process</span>
</span><span class="line line-8">
</span><span class="line line-9"><span class="nl">install</span><span class="o">:</span> <span class="nf">baz</span>
</span><span class="line line-10">	<span class="nb">install</span> <span class="nt">-Dm755</span> baz <span class="nt">-t</span> <span class="p">$(</span>out<span class="p">)</span>/bin
</span></code></pre></td></tr></tbody></table>
<table class="rouge-table"><tbody><tr><td class="highlight language-nix"><pre><code><span class="line line-1"><span class="c"># baz.nix</span>
</span><span class="line line-2"><span class="p">{</span> <span class="nv">stdenv</span> <span class="p">}:</span>
</span><span class="line line-3">
</span><span class="line line-4"><span class="kd">let</span>
</span><span class="line line-5">	<span class="nv">foo_1</span> <span class="o">=</span> <span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-6">		<span class="nv">pname</span> <span class="o">=</span> <span class="s2">"foo"</span><span class="p">;</span>
</span><span class="line line-7">		<span class="nv">version</span> <span class="o">=</span> <span class="s2">"1.0.0"</span><span class="p">;</span>
</span><span class="line line-8">		<span class="nv">src</span> <span class="o">=</span> <span class="sx">./foo</span><span class="p">;</span>
</span><span class="line line-9">		<span class="nv">outputs</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">"out"</span> <span class="s2">"dev"</span> <span class="p">];</span>
</span><span class="line line-10">	<span class="p">};</span>
</span><span class="line line-11">	<span class="nv">foo_2</span> <span class="o">=</span> <span class="nv">foo_1</span><span class="o">.</span><span class="nv">overrideAttrs</span> <span class="p">{</span> <span class="nv">NIX_CFLAGS_COMPILE</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">"-DFOO_VERSION=2"</span> <span class="p">];</span> <span class="p">};</span>
</span><span class="line line-12">	<span class="nv">foo</span> <span class="o">=</span> <span class="nv">foo_2</span><span class="p">;</span>
</span><span class="line line-13">
</span><span class="line line-14">	<span class="nv">bar</span> <span class="o">=</span> <span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-15">		<span class="nv">pname</span> <span class="o">=</span> <span class="s2">"bar"</span><span class="p">;</span>
</span><span class="line line-16">		<span class="nv">version</span> <span class="o">=</span> <span class="s2">"1.0.0"</span><span class="p">;</span>
</span><span class="line line-17">		<span class="nv">src</span> <span class="o">=</span> <span class="sx">./bar</span><span class="p">;</span>
</span><span class="line line-18">		<span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span> <span class="nv">foo</span> <span class="p">];</span>
</span><span class="line line-19">		<span class="nv">outputs</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">"out"</span> <span class="s2">"dev"</span> <span class="p">];</span>
</span><span class="line line-20">	<span class="p">};</span>
</span><span class="line line-21">
</span><span class="line line-22"><span class="kn">in</span>
</span><span class="line line-23"><span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-24">	<span class="nv">pname</span> <span class="o">=</span> <span class="s2">"baz"</span><span class="p">;</span>
</span><span class="line line-25">	<span class="nv">version</span> <span class="o">=</span> <span class="s2">"1.0.0"</span><span class="p">;</span>
</span><span class="line line-26">	<span class="nv">src</span> <span class="o">=</span> <span class="sx">./baz</span><span class="p">;</span>
</span><span class="line line-27">	<span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span> <span class="nv">bar</span> <span class="p">];</span>
</span><span class="line line-28">	<span class="nv">meta</span><span class="o">.</span><span class="nv">mainProgram</span> <span class="o">=</span> <span class="s2">"baz"</span><span class="p">;</span>
</span><span class="line line-29"><span class="p">}</span>
</span></code></pre></td></tr></tbody></table>
<p class="no-indent">
Build <code>baz</code> by running
</p>
<table class="rouge-table"><tbody><tr><td class="highlight language-bash"><pre><code><span class="line line-1">nix-build <span class="nt">-E</span> <span class="s1">'(import &lt;nixpkgs&gt; {}).callPackage ./baz.nix {}'</span>
</span></code></pre></td></tr></tbody></table>
<p class="no-indent">
and then you can run <code>result/bin/baz</code>. It will immediately terminate with exit code <code>1</code>. If you switch from <code>foo = foo_1</code> to <code>foo = foo_2</code> and build <code>baz</code> after that, <code>baz</code> will be recompiled, and <code>result/bin/baz</code> now will terminate with exit code <code>2</code>.
</p>
<p>Note that this is very bare-bone and overlooks a lot of things. For example, using <code>pkgsStatic.callPackage</code> on it will not work.</p>
</details>
<p>However, in principle, we should be able to skip recompiling <code>baz</code> when <code>foo</code> updates because compiling <code>baz</code> only needs the header files of <code>bar</code>. We can have an intermediate derivation <code>bazObj</code>, whose builder only compiles but not links <code>baz</code>, and the derivation output is the unlinked object files. The final package <code>baz</code> then uses <code>bazObj</code> as an input and produces the linked binaries. The input of <code>bazObj</code> consists of <code>baz.src</code> and <code>bar.dev</code>, and the input of <code>baz</code> consists of <code>bazObj</code> and <code>bar.out</code> (we are assuming <code>bar</code> has two outputs <code>dev</code> and <code>out</code>, respectively containing the header files and the library files).</p>
<p>There is still one problem here, though, which is that a change in <code>foo</code> will lead to a change in <code>bar.dev</code> for two reasons: (1) Nix derivations are input-addressed but not content-address, which means that even if the contents of <code>bar.dev</code> does not change, it is still a different derivation if the input <code>foo</code> changes; (2) the default fixup phase of <code>stdenv.mkDerivation</code> in Nixpkgs will put <code>bar.out</code>, which helplessly has to reference <code>foo</code>, in the propagated build inputs of <code>bar.dev</code>. Therefore, instead of relying on the default fixup phase of <code>stdenv.mkDerivation</code> in Nixpkgs to create <code>bar.dev</code> for us, we have to write our own implementation so that it does not reference <code>foo</code> directly or indirectly.</p>
<details>
<summary>
Implementation
</summary>
<table class="rouge-table"><tbody><tr><td class="highlight language-nix"><pre><code><span class="line line-1"><span class="c"># baz.nix</span>
</span><span class="line line-2"><span class="p">{</span> <span class="nv">stdenv</span> <span class="p">}:</span>
</span><span class="line line-3">
</span><span class="line line-4"><span class="kd">let</span>
</span><span class="line line-5">	<span class="nv">foo_1</span> <span class="o">=</span> <span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-6">		<span class="nv">pname</span> <span class="o">=</span> <span class="s2">"foo"</span><span class="p">;</span>
</span><span class="line line-7">		<span class="nv">version</span> <span class="o">=</span> <span class="s2">"1.0.0"</span><span class="p">;</span>
</span><span class="line line-8">		<span class="nv">src</span> <span class="o">=</span> <span class="sx">./foo</span><span class="p">;</span>
</span><span class="line line-9">		<span class="nv">outputs</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">"out"</span> <span class="s2">"dev"</span> <span class="p">];</span>
</span><span class="line line-10">	<span class="p">};</span>
</span><span class="line line-11">	<span class="nv">foo_2</span> <span class="o">=</span> <span class="nv">foo_1</span><span class="o">.</span><span class="nv">overrideAttrs</span> <span class="p">{</span> <span class="nv">NIX_CFLAGS_COMPILE</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">"-DFOO_VERSION=2"</span> <span class="p">];</span> <span class="p">};</span>
</span><span class="line line-12">	<span class="nv">foo</span> <span class="o">=</span> <span class="nv">foo_1</span><span class="p">;</span>
</span><span class="line line-13">
</span><span class="line line-14">	<span class="nv">bar</span> <span class="o">=</span> <span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">(</span><span class="nv">finalAttrs</span><span class="p">:</span> <span class="p">{</span>
</span><span class="line line-15">		<span class="nv">pname</span> <span class="o">=</span> <span class="s2">"bar"</span><span class="p">;</span>
</span><span class="line line-16">		<span class="nv">version</span> <span class="o">=</span> <span class="s2">"1.0.0"</span><span class="p">;</span>
</span><span class="line line-17">		<span class="nv">src</span> <span class="o">=</span> <span class="sx">./bar</span><span class="p">;</span>
</span><span class="line line-18">		<span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span> <span class="nv">foo</span> <span class="p">];</span>
</span><span class="line line-19">		<span class="nv">installTargets</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">"install-lib"</span> <span class="p">];</span>
</span><span class="line line-20">		<span class="nv">passthru</span><span class="o">.</span><span class="nv">dev</span> <span class="o">=</span> <span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-21">			<span class="kn">inherit</span> <span class="p">(</span><span class="nv">finalAttrs</span><span class="p">)</span> <span class="nv">pname</span> <span class="nv">version</span> <span class="nv">src</span><span class="p">;</span>
</span><span class="line line-22">			<span class="nv">dontBuild</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span><span class="line line-23">			<span class="nv">installTargets</span> <span class="o">=</span> <span class="s2">"install-include"</span><span class="p">;</span>
</span><span class="line line-24">		<span class="p">};</span>
</span><span class="line line-25">	<span class="p">});</span>
</span><span class="line line-26">
</span><span class="line line-27">	<span class="nv">bazObj</span> <span class="o">=</span> <span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-28">		<span class="nv">pname</span> <span class="o">=</span> <span class="s2">"baz"</span><span class="p">;</span>
</span><span class="line line-29">		<span class="nv">version</span> <span class="o">=</span> <span class="s2">"1.0.0"</span><span class="p">;</span>
</span><span class="line line-30">		<span class="nv">src</span> <span class="o">=</span> <span class="sx">./baz</span><span class="p">;</span>
</span><span class="line line-31">		<span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span> <span class="nv">bar</span><span class="o">.</span><span class="nv">dev</span> <span class="p">];</span>
</span><span class="line line-32">		<span class="nv">buildPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-33"><span class="s2">			runHook preInstall</span>
</span><span class="line line-34"><span class="s2">			make baz.o SHELL="$SHELL"</span>
</span><span class="line line-35"><span class="s2">			runHook postInstall</span>
</span><span class="line line-36"><span class="s2">		''</span><span class="p">;</span>
</span><span class="line line-37">		<span class="nv">installPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-38"><span class="s2">			runHook preInstall</span>
</span><span class="line line-39"><span class="s2">			install -Dm644 baz.o Makefile -t $out</span>
</span><span class="line line-40"><span class="s2">			runHook postInstall</span>
</span><span class="line line-41"><span class="s2">		''</span><span class="p">;</span>
</span><span class="line line-42">	<span class="p">};</span>
</span><span class="line line-43">
</span><span class="line line-44"><span class="kn">in</span>
</span><span class="line line-45"><span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-46">	<span class="kn">inherit</span> <span class="p">(</span><span class="nv">bazObj</span><span class="p">)</span> <span class="nv">pname</span> <span class="nv">version</span><span class="p">;</span>
</span><span class="line line-47">	<span class="nv">src</span> <span class="o">=</span> <span class="nv">bazObj</span><span class="p">;</span>
</span><span class="line line-48">	<span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span> <span class="nv">bar</span><span class="o">.</span><span class="nv">out</span> <span class="p">];</span>
</span><span class="line line-49">	<span class="nv">meta</span> <span class="o">=</span> <span class="nv">bazObj</span><span class="o">.</span><span class="nv">meta</span> <span class="o">//</span> <span class="p">{</span> <span class="nv">mainProgram</span> <span class="o">=</span> <span class="s2">"baz"</span><span class="p">;</span> <span class="p">};</span>
</span><span class="line line-50"><span class="p">}</span>
</span></code></pre></td></tr></tbody></table>
<p>Notice that you have to use <code>bar.out</code> instead of just <code>bar</code> in <code>buildInputs</code> of <code>baz</code> because <code>stdenv.mkDerivation</code> “cleverly” selects <code>bar.dev</code> when it sees <code>bar</code> in <code>buildInputs</code>.</p>
<p>Again, this packaging needs some further polishing, but it explains the idea of separating compiling and linking. You may try switching from <code>foo = foo_1</code> to <code>foo = foo_2</code>, and <code>baz</code> will only be compiled once.</p>
</details>
<p>This looks good, but here is the bad news: none of GNU Autotools, pkg-config, or CMake is designed with the idea that compiling and linking may happen in different environments. The configure script typically tries to compile and link small programs to test whether the build environment has all the required header files and library files. If compiling and linking are in separate environments, we really need two different configure scripts, one for checking compilers and header files and the other for checking linkers and library files. The problem about pkg-config is that the <code>.pc</code> files contain information about both the header files and the library files. Then, since <code>libbar.pc</code> has to live in <code>bar.dev</code>, this means that <code>bar.dev</code> has to reference <code>bar.out</code> and ultimately references <code>foo</code>. As for CMake, it is possible to patch CMake to ask it to always look for <code>INTERFACE</code> libraries whenever the CMake script uses <code>add_library</code> and have the properties <code>INTERFACE_INCLUDE_DIRECTORIES</code> or <code>IMPORTED_LOCATION</code> defined according to whether it is in the compiling builder or the linking builder. However, this is still a mess and requires workarounds on a case-by-case basis.</p>
<p>The only way to solve this problem is again using stubs. This time, the stub derivations are much more complicated than the ones for simple store path replacements. This time, the stub derivations have to have libraries that actually contains the symbols against which the linker can link the object files. If there were a tool that can generate stub libraries from header files, a generally useful definition of such stub derivations could in principle be implemented. However, considering the diversity of header files in C/C++ header files, such a tool is very hard to implement. If we do not restrict ourselves to keeping Nix unchanged, however, there is a way out of this: making Nix store paths content-addressed instead of input-addressed. Generating stubs from header files is difficult, but generating stubs from library files is much easier. Although the library files change when some dependency updates, the stubs generated from the library files will not change. If Nix store paths were content-addressed, this would be an ideal way of generating and using stub libraries.</p>
<h2 data-label="0.4" id="ditching-check-phases">Ditching check phases</h2>
<p>At first glance, it seems that it cannot be helped if a depended software actually runs in the dependent builder. However, a lot of such dependencies that cause mass rebuilds are actually only for tests, used in the check phase, especially things like <code>gtest</code>. Changes in such depended packages usually do not change the output of the dependent derivation. Conceptually, if a tool used for testing gets an update, we do not have to rebuild a software that uses it for tests but only have to test the built software again with the new test tools.</p>
<p>Take a simple example here again. Let us say <code>foo</code> is a test helper that compares the standard output of an executable with an expected value, and <code>bar</code> is a very simple program that outputs <code>hello</code> to the standard output. In the install check phase of <code>bar</code>, we use <code>foo</code> to test that the output of the executable is indeed <code>hello</code>. Typically for this case, in Nixpkgs, <code>foo</code> enters the <code>nativeInstallCheckInputs</code> of <code>bar</code>, so <code>bar</code> needs a rebuild when <code>foo</code> updates.</p>
<details>
<summary>
Supposed packaging in Nixpkgs
</summary>
<table class="rouge-table"><tbody><tr><td class="highlight language-nix"><pre><code><span class="line line-1"><span class="c"># bar.nix</span>
</span><span class="line line-2"><span class="p">{</span>
</span><span class="line line-3">	<span class="nv">stdenv</span><span class="p">,</span>
</span><span class="line line-4">	<span class="nv">writeShellScriptBin</span><span class="p">,</span>
</span><span class="line line-5">	<span class="nv">runtimeShell</span><span class="p">,</span>
</span><span class="line line-6"><span class="p">}:</span>
</span><span class="line line-7">
</span><span class="line line-8"><span class="kd">let</span>
</span><span class="line line-9">	<span class="nv">foo_1</span> <span class="o">=</span> <span class="nv">writeShellScriptBin</span> <span class="s2">"foo"</span> <span class="s2">"[ `$1` = $2 ]"</span><span class="p">;</span>
</span><span class="line line-10">	<span class="nv">foo_2</span> <span class="o">=</span> <span class="nv">writeShellScriptBin</span> <span class="s2">"foo"</span> <span class="s2">"[[ `$1` == $2 ]]"</span><span class="p">;</span>
</span><span class="line line-11">	<span class="nv">foo</span> <span class="o">=</span> <span class="nv">foo_1</span><span class="p">;</span>
</span><span class="line line-12"><span class="kn">in</span>
</span><span class="line line-13"><span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-14">	<span class="nv">pname</span> <span class="o">=</span> <span class="s2">"bar"</span><span class="p">;</span>
</span><span class="line line-15">	<span class="nv">version</span> <span class="o">=</span> <span class="s2">"1.0.0"</span><span class="p">;</span>
</span><span class="line line-16">	<span class="nv">dontUnpack</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span><span class="line line-17">
</span><span class="line line-18">	<span class="nv">buildPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-19"><span class="s2">		runHook preBuild</span>
</span><span class="line line-20"><span class="s2">		sleep 5 # simulate time-consuming building process</span>
</span><span class="line line-21"><span class="s2">		runHook postBuild</span>
</span><span class="line line-22"><span class="s2">	''</span><span class="p">;</span>
</span><span class="line line-23">
</span><span class="line line-24">	<span class="nv">installPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-25"><span class="s2">		runHook preInstall</span>
</span><span class="line line-26"><span class="s2">		mkdir -p $out/bin</span>
</span><span class="line line-27"><span class="s2">		echo '#!</span><span class="si">${</span><span class="nv">runtimeShell</span><span class="si">}</span><span class="s2">' &gt; $out/bin/bar</span>
</span><span class="line line-28"><span class="s2">		echo 'echo hello' &gt;&gt; $out/bin/bar</span>
</span><span class="line line-29"><span class="s2">		chmod +x $out/bin/bar</span>
</span><span class="line line-30"><span class="s2">		runHook postInstall</span>
</span><span class="line line-31"><span class="s2">	''</span><span class="p">;</span>
</span><span class="line line-32">
</span><span class="line line-33">	<span class="nv">doInstallCheck</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span><span class="line line-34">	<span class="nv">nativeInstallCheckInputs</span> <span class="o">=</span> <span class="p">[</span> <span class="nv">foo</span> <span class="p">];</span>
</span><span class="line line-35">	<span class="nv">installCheckPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-36"><span class="s2">		runHook preInstallCheck</span>
</span><span class="line line-37"><span class="s2">		foo $out/bin/bar hello</span>
</span><span class="line line-38"><span class="s2">		runHook postInstallCheck</span>
</span><span class="line line-39"><span class="s2">	''</span><span class="p">;</span>
</span><span class="line line-40"><span class="p">}</span>
</span></code></pre></td></tr></tbody></table>
<p class="no-indent">
To build it, run
</p>
<table class="rouge-table"><tbody><tr><td class="highlight language-bash"><pre><code><span class="line line-1">nix-build <span class="nt">-E</span> <span class="s1">'(import &lt;nixpkgs&gt; {}).callPackage ./bar.nix {}'</span>
</span></code></pre></td></tr></tbody></table>
<p class="no-indent">
After that, you can run <code>bar</code> by running <code>result/bin/bar</code>. Observe that <code>bar</code> needs rebuilding when you switch from <code>foo = foo_1</code> to <code>foo = foo_2</code>.
</p>
</details>
<p>For this example, preventing rebuilding is very simple: simply remove the install check phase. However, tests are still important, so we still need to include them somewhere. Fortunately, people conventionally put such tests in <code>passthru.tests</code> for this very purpose. Therefore, all we have to do is to put the tests in <code>passthru.tests</code> instead.</p>
<details>
<summary>
Implementation
</summary>
<table class="rouge-table"><tbody><tr><td class="highlight language-nix"><pre><code><span class="line line-1"><span class="c"># bar.nix</span>
</span><span class="line line-2"><span class="p">{</span>
</span><span class="line line-3">	<span class="nv">stdenv</span><span class="p">,</span>
</span><span class="line line-4">	<span class="nv">writeShellScriptBin</span><span class="p">,</span>
</span><span class="line line-5">	<span class="nv">runtimeShell</span><span class="p">,</span>
</span><span class="line line-6">	<span class="nv">runCommand</span><span class="p">,</span>
</span><span class="line line-7"><span class="p">}:</span>
</span><span class="line line-8">
</span><span class="line line-9"><span class="kd">let</span>
</span><span class="line line-10">	<span class="nv">foo_1</span> <span class="o">=</span> <span class="nv">writeShellScriptBin</span> <span class="s2">"foo"</span> <span class="s2">"[ `$1` = $2 ]"</span><span class="p">;</span>
</span><span class="line line-11">	<span class="nv">foo_2</span> <span class="o">=</span> <span class="nv">writeShellScriptBin</span> <span class="s2">"foo"</span> <span class="s2">"[[ `$1` == $2 ]]"</span><span class="p">;</span>
</span><span class="line line-12">	<span class="nv">foo</span> <span class="o">=</span> <span class="nv">foo_1</span><span class="p">;</span>
</span><span class="line line-13">
</span><span class="line line-14">	<span class="nv">bar</span> <span class="o">=</span> <span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
</span><span class="line line-15">		<span class="nv">pname</span> <span class="o">=</span> <span class="s2">"bar"</span><span class="p">;</span>
</span><span class="line line-16">		<span class="nv">version</span> <span class="o">=</span> <span class="s2">"1.0.0"</span><span class="p">;</span>
</span><span class="line line-17">		<span class="nv">dontUnpack</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span><span class="line line-18">
</span><span class="line line-19">		<span class="nv">buildPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-20"><span class="s2">			runHook preBuild</span>
</span><span class="line line-21"><span class="s2">			sleep 5 # simulate time-consuming building process</span>
</span><span class="line line-22"><span class="s2">			runHook postBuild</span>
</span><span class="line line-23"><span class="s2">		''</span><span class="p">;</span>
</span><span class="line line-24">
</span><span class="line line-25">		<span class="nv">installPhase</span> <span class="o">=</span> <span class="s2">''</span>
</span><span class="line line-26"><span class="s2">			runHook preInstall</span>
</span><span class="line line-27"><span class="s2">			mkdir -p $out/bin</span>
</span><span class="line line-28"><span class="s2">			echo '#!</span><span class="si">${</span><span class="nv">runtimeShell</span><span class="si">}</span><span class="s2">' &gt; $out/bin/bar</span>
</span><span class="line line-29"><span class="s2">			echo 'echo hello' &gt;&gt; $out/bin/bar</span>
</span><span class="line line-30"><span class="s2">			chmod +x $out/bin/bar</span>
</span><span class="line line-31"><span class="s2">			runHook postInstall</span>
</span><span class="line line-32"><span class="s2">		''</span><span class="p">;</span>
</span><span class="line line-33">
</span><span class="line line-34">		<span class="nv">passthru</span><span class="o">.</span><span class="nv">tests</span><span class="o">.</span><span class="nv">installCheck</span> <span class="o">=</span> <span class="nv">runCommand</span> <span class="s2">"bar-installCheck"</span> <span class="p">{</span>
</span><span class="line line-35">			<span class="nv">nativeBuildInputs</span> <span class="o">=</span> <span class="p">[</span> <span class="nv">foo</span> <span class="nv">bar</span> <span class="p">];</span>
</span><span class="line line-36">		<span class="p">}</span> <span class="s2">"foo bar hello &amp;&amp; touch $out"</span><span class="p">;</span>
</span><span class="line line-37">	<span class="p">};</span>
</span><span class="line line-38"><span class="kn">in</span> <span class="nv">bar</span>
</span></code></pre></td></tr></tbody></table>
<p class="no-indent">
To run the test, run
</p>
<table class="rouge-table"><tbody><tr><td class="highlight language-bash"><pre><code><span class="line line-1">nix-build <span class="nt">-E</span> <span class="s1">'((import &lt;nixpkgs&gt; {}).callPackage ./bar.nix {}).tests.installCheck'</span>
</span></code></pre></td></tr></tbody></table>
<p>Now, <code>bar</code> does not need rebuilding when <code>foo</code> updates, but only <code>bar.tests.installCheck</code> needs rebuilding.</p>
</details>
<p>Moving the check phase to <code>passthru.tests</code>, however, is more difficult. The reason is that the configure script will disable the tests in <code>Makefile</code> if it cannot find the test dependencies. This, again, can be solved by using stub libraries for the test dependencies, but there is an easier solution. First, copy everything (configure script, source code, and built binaries) to the environment with test dependencies. Then, run the configure script again to generate a new <code>Makefile</code>. Then we can run the check phase. Hopefully, nowhere in the source code does it use test-related macros.</p>
<p>If we ditch the check phases and the install check phases and move all of them to <code>passthru.tests</code> like this, a lot of rebuilds can be avoided.</p>
<p>Notice that avoiding rebuilds due to test depenencies is also related to content-addressed derivations. In a content-addressed model, if <code>foo</code> is a test dependency of <code>bar</code>, and <code>bar</code> is a build dependency of <code>baz</code>, then <code>baz</code> does not need rebuilding if <code>foo</code> gets an update because it does not change the output of <code>bar</code>. However, <code>bar</code> still needs a rebuild, which is unnecessary if it does not use check phases and install check phases but use <code>passthru.tests</code>.</p>
<h2 data-label="0.5" id="conclusion">Conclusion</h2>
<p>I proposed some ways to reduce mass rebuilds without changing Nix. However, each of them has some downsides and requires major refactoring of Nixpkgs.</p>]]></content><author><name>UlyssesZhan</name><email>ulysseszhan@gmail.com</email></author><category term="programming" /><category term="nix" /><summary type="html"><![CDATA[One serious problem with Nix is the amount of mass rebuilds happening in Nixpkgs every time some package with many dependents gets an update. I thought about three ideas to reduce mass rebuilds: store path replacements, separating compiling and linking, and ditching check phases. Each of them has some degree of impracticality.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ulysseszh.github.io/assets/images/covers/2026-04-16-reduce-nix-rebuild.png" /><media:content medium="image" url="https://ulysseszh.github.io/assets/images/covers/2026-04-16-reduce-nix-rebuild.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html"><![CDATA[Imperative programming in Nix language]]></title><link href="https://ulysseszh.github.io/programming/2025/11/06/nix-imperative.html" rel="alternate" type="text/html" title="Imperative programming in Nix language" /><published>2025-11-06T23:47:51-08:00</published><updated>2025-11-06T23:47:51-08:00</updated><id>https://ulysseszh.github.io/programming/2025/11/06/nix-imperative</id><content type="html" xml:base="https://ulysseszh.github.io/programming/2025/11/06/nix-imperative.html"><![CDATA[<p>Nix, a purely functional language best known for describing reproducible systems, seems to have just enough power to <em>model</em> imperative behavior. This article walks through a small experiment: an embedded DSL in Nix that lets you write programs with imperative features, such as mutable variables, loops, exceptions, functions, and I/O.</p>
<p>This article is an expansion of my earlier <a href="https://discourse.nixos.org/t/interactive-program-written-in-nix-lang/71536" target="_blank" rel="external">forum post</a> about writing an interactive program in Nix.</p>
<h2 data-label="0.1" id="assign-and-print">Assign and print</h2>
<p>Monads are the standard abstraction for mutation of states. A monad wraps values with extra information (such as side effects). A monadic operation can be seen as a function from some <em>state</em> to a new <em>state</em>, possibly with side effects encoded as data rather than real I/O.</p>
<p>This DSL adopts that idea. Each statement is a function of a state that returns an instruction of how to mutate the state. Consider this <code>do</code> function that “executes” a statement:</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="nv">do</span> <span class="o">=</span> <span class="nv">statement</span><span class="p">:</span> <span class="nv">state</span><span class="p">:</span> <span class="nv">state</span> <span class="o">//</span> <span class="nv">statement</span> <span class="nv">state</span> <span class="nv">state</span><span class="p">;</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
Each statement receives the current state and returns a function that mutates the current state into an updated one. It is equivalent to a monadic bind, but the monads are very trivial here because the unit operation is just the identity function.
</p>
<p>Let us define some simple statements such as assignment and printing to the console:</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="c">#!/usr/bin/env nix-build</span>
</span>
            <span class="line line-2">
</span>
            <span class="line line-3"><span class="kd">let</span>
</span>
            <span class="line line-4">	<span class="nv">do</span> <span class="o">=</span> <span class="nv">statement</span><span class="p">:</span> <span class="nv">state</span><span class="p">:</span> <span class="nv">state</span> <span class="o">//</span> <span class="nv">statement</span> <span class="nv">state</span> <span class="nv">state</span><span class="p">;</span>
</span>
            <span class="line line-5">	<span class="nv">assign</span> <span class="o">=</span> <span class="nv">variables</span><span class="p">:</span> <span class="nv">state</span><span class="p">:</span> <span class="nv">state</span> <span class="o">//</span> <span class="nv">variables</span><span class="p">;</span>
</span>
            <span class="line line-6">	<span class="nv">print</span> <span class="o">=</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">trace</span><span class="p">;</span>
</span>
            <span class="line line-7">
</span>
            <span class="line line-8">	<span class="nv">state0</span> <span class="o">=</span> <span class="p">{</span> <span class="p">};</span>
</span>
            <span class="line line-9">	<span class="nv">state1</span> <span class="o">=</span> <span class="nv">do</span> <span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">})</span> <span class="nv">state0</span><span class="p">;</span>
</span>
            <span class="line line-10">	<span class="nv">state2</span> <span class="o">=</span> <span class="nv">do</span> <span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="nv">print</span> <span class="s2">"Current value: </span><span class="si">${</span><span class="kr">toString</span> <span class="nv">state</span><span class="o">.</span><span class="nv">i</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="nv">state1</span><span class="p">;</span>
</span>
            <span class="line line-11">	<span class="nv">state3</span> <span class="o">=</span> <span class="nv">do</span> <span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">i</span> <span class="o">=</span> <span class="nv">state</span><span class="o">.</span><span class="nv">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="p">})</span> <span class="nv">state2</span><span class="p">;</span>
</span>
            <span class="line line-12">	<span class="nv">state4</span> <span class="o">=</span> <span class="nv">do</span> <span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="nv">print</span> <span class="s2">"Current value: </span><span class="si">${</span><span class="kr">toString</span> <span class="nv">state</span><span class="o">.</span><span class="nv">i</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="nv">state3</span><span class="p">;</span>
</span>
            <span class="line line-13">
</span>
            <span class="line line-14"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="nv">state4</span> <span class="p">{</span> <span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
The final <code>builtins.seq</code> call forces the final state to be evaluated so that all the statements will be executed. There is no need for a <code>builtins.deepSeq</code> because any data that we wish to see via <code>print</code> will be force evaluated. The output:
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-plain">
        <pre>
          <code>
            <span class="line line-1">trace: Current value: 1
</span>
            <span class="line line-2">trace: Current value: 2
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
This is nice! We can slightly modify <code>do</code> to make it handle a list of statements instead so that we do not have to call <code>do</code> for each statement. We can also rename <code>state</code> to just <code>_</code> so that the code does not look too verbose.
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="c">#!/usr/bin/env nix-build</span>
</span>
            <span class="line line-2">
</span>
            <span class="line line-3"><span class="kd">let</span>
</span>
            <span class="line line-4">	<span class="nv">do</span> <span class="o">=</span> <span class="nv">statements</span><span class="p">:</span> <span class="nv">_</span><span class="p">:</span>
</span>
            <span class="line line-5">		<span class="k">if</span> <span class="nv">statements</span> <span class="o">==</span> <span class="p">[</span> <span class="p">]</span> <span class="k">then</span> <span class="nv">_</span>
</span>
            <span class="line line-6">		<span class="k">else</span> <span class="nv">do</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">tail</span> <span class="nv">statements</span><span class="p">)</span> <span class="p">(</span><span class="nv">_</span> <span class="o">//</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">head</span> <span class="nv">statements</span> <span class="nv">_</span> <span class="nv">_</span><span class="p">);</span>
</span>
            <span class="line line-7">	<span class="nv">assign</span> <span class="o">=</span> <span class="nv">variables</span><span class="p">:</span> <span class="nv">_</span><span class="p">:</span> <span class="nv">_</span> <span class="o">//</span> <span class="nv">variables</span><span class="p">;</span>
</span>
            <span class="line line-8">	<span class="nv">print</span> <span class="o">=</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">trace</span><span class="p">;</span>
</span>
            <span class="line line-9">
</span>
            <span class="line line-10"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="p">(</span><span class="nv">do</span> <span class="p">[</span>
</span>
            <span class="line line-11">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">})</span>
</span>
            <span class="line line-12">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">print</span> <span class="s2">"Current value: </span><span class="si">${</span><span class="kr">toString</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</span>
            <span class="line line-13">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">i</span> <span class="o">=</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="p">})</span>
</span>
            <span class="line line-14">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">print</span> <span class="s2">"Current value: </span><span class="si">${</span><span class="kr">toString</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</span>
            <span class="line line-15"><span class="p">]</span> <span class="p">{</span> <span class="p">})</span> <span class="p">{</span> <span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
This gives the same output as before. I used a recursion to iterate over the list of statements instead of just using <code>builtins.foldl'</code> because this form is easier to be modified later to incorporate exception handling.
</p>
<h2 data-label="0.2" id="exception-handling">Exception handling</h2>
<p>Next, we add exception handling to the DSL. I introduce it before control flow because I will make loops rely on exceptions to implement <code>break</code> and <code>continue</code>.</p>
<p>For simplicity, I will make throwing an exception just setting a special variable <code>_thrown_</code> in the state:</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="kr">throw</span> <span class="o">=</span> <span class="nv">thrown</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">_thrown_</span> <span class="o">=</span> <span class="nv">thrown</span><span class="p">;</span> <span class="p">};</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
(Notice that this shadows the built-in <code>throw</code> function, so we will refer to the built-in one as <code>builtins.throw</code> if needed.) In <code>do</code>, we need to check whether an exception has been thrown after each statement. If so, we stop executing further statements.
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="nv">do</span> <span class="o">=</span> <span class="nv">statements</span><span class="p">:</span> <span class="nv">_</span><span class="p">:</span>
</span>
            <span class="line line-2">	<span class="k">if</span> <span class="nv">statements</span> <span class="o">==</span> <span class="p">[</span> <span class="p">]</span> <span class="k">then</span> <span class="nv">_</span>
</span>
            <span class="line line-3">	<span class="k">else</span> <span class="kd">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="nv">_</span> <span class="o">//</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">head</span> <span class="nv">statements</span> <span class="nv">_</span> <span class="nv">_</span><span class="p">;</span> <span class="kn">in</span>
</span>
            <span class="line line-4">		<span class="k">if</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">hasAttr</span> <span class="s2">"_thrown_"</span> <span class="nv">result</span> <span class="k">then</span> <span class="nv">result</span>
</span>
            <span class="line line-5">		<span class="k">else</span> <span class="nv">do</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">tail</span> <span class="nv">statements</span><span class="p">)</span> <span class="nv">result</span><span class="p">;</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
Then, in the <code>try</code> statement, we check for <code>_thrown_</code> after executing the <code>try</code> block. If an exception was thrown, we execute the <code>catch</code> block with the exception value passed to it (after unsetting the <code>_thrown_</code> variable).
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="nv">try</span> <span class="o">=</span> <span class="nv">statements</span><span class="p">:</span> <span class="nv">catch</span><span class="p">:</span> <span class="nv">_</span><span class="p">:</span> <span class="kd">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="nv">do</span> <span class="nv">statements</span> <span class="nv">_</span><span class="p">;</span> <span class="kn">in</span>
</span>
            <span class="line line-2">	<span class="k">if</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">hasAttr</span> <span class="s2">"_thrown_"</span> <span class="nv">result</span> <span class="k">then</span>
</span>
            <span class="line line-3">		<span class="nv">do</span> <span class="p">(</span><span class="nv">catch</span> <span class="nv">result</span><span class="o">.</span><span class="nv">_thrown_</span><span class="p">)</span> <span class="p">(</span><span class="kr">removeAttrs</span> <span class="nv">result</span> <span class="p">[</span> <span class="s2">"_thrown_"</span> <span class="p">])</span>
</span>
            <span class="line line-4">	<span class="k">else</span> <span class="nv">result</span><span class="p">;</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
With these definitions, we can now write code that throws and catches exceptions:
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="kd">let</span> <span class="c"># ...</span>
</span>
            <span class="line line-2"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="p">(</span><span class="nv">do</span> <span class="p">[</span>
</span>
            <span class="line line-3">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">try</span> <span class="p">[</span>
</span>
            <span class="line line-4">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="kr">throw</span> <span class="s2">"Some error message."</span><span class="p">)</span>
</span>
            <span class="line line-5">	<span class="p">]</span> <span class="p">(</span><span class="nv">exception</span><span class="p">:</span> <span class="p">[</span>
</span>
            <span class="line line-6">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">print</span> <span class="s2">"Caught exception: </span><span class="si">${</span><span class="nv">exception</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</span>
            <span class="line line-7">	<span class="p">]))</span>
</span>
            <span class="line line-8"><span class="p">]</span> <span class="p">{</span> <span class="p">})</span> <span class="p">{</span> <span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<h2 data-label="0.3" id="control-flow">Control flow</h2>
<p>The control flow statements include <code>if</code> and <code>while</code>, and the latter is accompanied by <code>break</code> and <code>continue</code>. Both of them needs a condition expression that evaluates to a boolean value. The <code>while</code> condition will be evaluated multiple times for different states, so it at least needs to be a function that maps the state to a boolean instead of just a boolean. While the <code>if</code> condition can be a simple boolean expression, I will make it a function as well for consistency.</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="k">if</span><span class="err">'</span> <span class="o">=</span> <span class="nv">condition</span><span class="p">:</span> <span class="kc">true</span><span class="nv">Statements</span><span class="p">:</span> <span class="kc">false</span><span class="nv">Statements</span><span class="p">:</span> <span class="nv">_</span><span class="p">:</span>
</span>
            <span class="line line-2">	<span class="nv">do</span> <span class="p">(</span><span class="k">if</span> <span class="nv">condition</span> <span class="nv">_</span> <span class="k">then</span> <span class="kc">true</span><span class="nv">Statements</span> <span class="k">else</span> <span class="kc">false</span><span class="nv">Statements</span><span class="p">)</span> <span class="nv">_</span><span class="p">;</span>
</span>
            <span class="line line-3"><span class="nv">whileWithoutJump</span> <span class="o">=</span> <span class="nv">condition</span><span class="p">:</span> <span class="nv">body</span><span class="p">:</span>
</span>
            <span class="line line-4">	<span class="k">if</span><span class="err">'</span> <span class="nv">condition</span> <span class="p">[</span> <span class="nv">body</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">whileWithoutJump</span> <span class="nv">condition</span> <span class="nv">body</span><span class="p">)</span> <span class="p">]</span> <span class="p">[</span> <span class="p">];</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
Now, to implement <code>break</code> and <code>continue</code>, we can use exception handling. Define them as throwing special exception values:
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="nv">break</span> <span class="o">=</span> <span class="kr">throw</span> <span class="s2">"_break_"</span><span class="p">;</span>
</span>
            <span class="line line-2"><span class="nv">continue</span> <span class="o">=</span> <span class="kr">throw</span> <span class="s2">"_continue_"</span><span class="p">;</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
Then, define <code>while</code> to use <code>try</code> to catch these exceptions. The <code>"_continue_"</code> exception is caught inside the loop body, and the <code>"_break_"</code> exception is caught outside the loop to terminate the loop.
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="nv">catchOnly</span> <span class="o">=</span> <span class="nv">handled</span><span class="p">:</span> <span class="nv">thrown</span><span class="p">:</span> <span class="k">if</span> <span class="nv">thrown</span> <span class="o">==</span> <span class="nv">handled</span> <span class="k">then</span> <span class="p">[</span> <span class="p">]</span> <span class="k">else</span> <span class="p">[</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="kr">throw</span> <span class="nv">thrown</span><span class="p">)</span> <span class="p">];</span>
</span>
            <span class="line line-2"><span class="nv">while</span> <span class="o">=</span> <span class="nv">condition</span><span class="p">:</span> <span class="nv">statements</span><span class="p">:</span> <span class="nv">try</span> <span class="p">[</span>
</span>
            <span class="line line-3">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">whileWithoutJump</span> <span class="nv">condition</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">try</span> <span class="nv">statements</span> <span class="p">(</span><span class="nv">catchOnly</span> <span class="s2">"_continue_"</span><span class="p">)))</span>
</span>
            <span class="line line-4"><span class="p">]</span> <span class="p">(</span><span class="nv">catchOnly</span> <span class="s2">"_break_"</span><span class="p">);</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p>We can now write some loops.</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="kd">let</span> <span class="c"># ...</span>
</span>
            <span class="line line-2"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="p">(</span><span class="nv">do</span> <span class="p">[</span>
</span>
            <span class="line line-3">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">})</span>
</span>
            <span class="line line-4">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">while</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">)</span> <span class="p">[</span>
</span>
            <span class="line line-5">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="k">if</span><span class="err">'</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="p">[</span>
</span>
            <span class="line line-6">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">break</span><span class="p">)</span>
</span>
            <span class="line line-7">		<span class="p">]</span> <span class="p">[</span> <span class="p">])</span>
</span>
            <span class="line line-8">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="k">if</span><span class="err">'</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="p">[</span>
</span>
            <span class="line line-9">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">i</span> <span class="o">=</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="p">})</span>
</span>
            <span class="line line-10">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">continue</span><span class="p">)</span>
</span>
            <span class="line line-11">		<span class="p">]</span> <span class="p">[</span> <span class="p">])</span>
</span>
            <span class="line line-12">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">print</span> <span class="s2">"Current i: </span><span class="si">${</span><span class="kr">toString</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</span>
            <span class="line line-13">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">i</span> <span class="o">=</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="p">})</span>
</span>
            <span class="line line-14">	<span class="p">])</span>
</span>
            <span class="line line-15"><span class="p">]</span> <span class="p">{</span> <span class="p">})</span> <span class="p">{</span> <span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
This prints:
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-plain">
        <pre>
          <code>
            <span class="line line-1">trace: Current i: 0
</span>
            <span class="line line-2">trace: Current i: 1
</span>
            <span class="line line-3">trace: Current i: 3
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<h2 data-label="0.4" id="functions">Functions</h2>
<p>Defining functions is already possible without any special constructs, but only <code>assign</code> and <code>do</code> is needed:</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="kd">let</span> <span class="c"># ...</span>
</span>
            <span class="line line-2"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="p">(</span><span class="nv">do</span> <span class="p">[</span>
</span>
            <span class="line line-3">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">hello</span> <span class="o">=</span> <span class="nv">name</span><span class="p">:</span> <span class="nv">do</span> <span class="p">[</span>
</span>
            <span class="line line-4">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">print</span> <span class="s2">"Hello, </span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="s2">!"</span><span class="p">)</span>
</span>
            <span class="line line-5">	<span class="p">];</span> <span class="p">})</span>
</span>
            <span class="line line-6">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">hello</span> <span class="s2">"world"</span><span class="p">)</span>
</span>
            <span class="line line-7"><span class="p">]</span> <span class="p">{</span> <span class="p">})</span> <span class="p">{</span> <span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
We can make it nicer by having it support <code>return</code> to exit early from the function. Similar to <code>break</code> and <code>continue</code>, we can implement <code>return</code> using exceptions.
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="nv">return</span> <span class="o">=</span> <span class="kr">throw</span> <span class="s2">"_return_"</span><span class="p">;</span>
</span>
            <span class="line line-2"><span class="nv">function</span> <span class="o">=</span> <span class="nv">functions</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">mapAttrs</span> <span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="nv">fun</span><span class="p">:</span> <span class="nv">arg</span><span class="p">:</span> <span class="nv">try</span> <span class="p">(</span><span class="nv">fun</span> <span class="nv">arg</span><span class="p">)</span> <span class="p">(</span><span class="nv">catchOnly</span> <span class="s2">"_return_"</span><span class="p">))</span> <span class="nv">functions</span><span class="p">);</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p>Then, we can define functions with <code>return</code> support:</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="kd">let</span> <span class="c"># ...</span>
</span>
            <span class="line line-2"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="p">(</span><span class="nv">do</span> <span class="p">[</span>
</span>
            <span class="line line-3">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">function</span> <span class="p">{</span> <span class="nv">hello</span> <span class="o">=</span> <span class="nv">arg</span><span class="p">:</span> <span class="p">[</span>
</span>
            <span class="line line-4">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="k">if</span><span class="err">'</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">arg</span> <span class="o">==</span> <span class="s2">""</span><span class="p">)</span> <span class="p">[</span>
</span>
            <span class="line line-5">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">print</span> <span class="s2">"What's your name?"</span><span class="p">)</span>
</span>
            <span class="line line-6">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">return</span><span class="p">)</span>
</span>
            <span class="line line-7">		<span class="p">]</span> <span class="p">[</span> <span class="p">])</span>
</span>
            <span class="line line-8">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">print</span> <span class="s2">"Hello, </span><span class="si">${</span><span class="nv">arg</span><span class="si">}</span><span class="s2">!"</span><span class="p">)</span>
</span>
            <span class="line line-9">	<span class="p">];</span> <span class="p">})</span>
</span>
            <span class="line line-10">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">hello</span> <span class="s2">""</span><span class="p">)</span>
</span>
            <span class="line line-11">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">hello</span> <span class="s2">"world"</span><span class="p">)</span>
</span>
            <span class="line line-12"><span class="p">]</span> <span class="p">{</span> <span class="p">})</span> <span class="p">{</span> <span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
This prints:
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-plain">
        <pre>
          <code>
            <span class="line line-1">trace: What's your name?
</span>
            <span class="line line-2">trace: Hello, world!
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<h2 data-label="0.5" id="input">Input</h2>
<p>We can use <code>builtins.readFile</code> to read input from files. To read from stdin, we need to use Lix to do that because the vanilla implementation of Nix <a href="https://github.com/NixOS/nix/issues/9330" target="_blank" rel="external">does not</a> support <code>builtins.readFile /dev/stdin</code>.</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="kd">let</span> <span class="c"># ...</span>
</span>
            <span class="line line-2">	<span class="nv">read</span> <span class="o">=</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">readFile</span><span class="p">;</span>
</span>
            <span class="line line-3"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="p">(</span><span class="nv">do</span> <span class="p">[</span>
</span>
            <span class="line line-4">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">input</span> <span class="o">=</span> <span class="nv">read</span> <span class="sx">/dev/stdin</span><span class="p">;</span> <span class="p">})</span>
</span>
            <span class="line line-5">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">print</span> <span class="s2">"You entered: </span><span class="si">${</span><span class="nv">_</span><span class="o">.</span><span class="nv">input</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</span>
            <span class="line line-6"><span class="p">]</span> <span class="p">{</span> <span class="p">})</span> <span class="p">{</span> <span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p>Because <code>builtins.readFile</code> reads the entire file until EOF, when you finish input, you need to send an EOF signal, typically by pressing <kbd>Ctrl</kbd>+<kbd>D</kbd> on an empty line. Notice that you need to press this combination on a empty line, so the input you get in Nix probably ends with a newline character that you want to trim.</p>
<p>If you want to read from stdin multiple times, to ensure that the Nix interpreter tries to reopen <code>/dev/stdin</code> each time, you must not write <code>input = builtins.readFile /dev/stdin;</code> in the first <code>let</code> block and refer to <code>input</code> every time you want to read from stdin. Instead, you should use <code>read /dev/stdin</code> every time, or wrap it in a function and call the function every time.</p>
<h2 data-label="0.6" id="other-minor-improvements">Other minor improvements</h2>
<p>We may want to separate all those things in the first <code>let</code> block into a separate file, which let us call <code>imperative.nix</code>, so that we do not need to write out all those things like <code>assign</code>, <code>print</code> every time we write an imperative program. However, this would still mean that we have to pass a bunch of variables like this:</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="kd">let</span>
</span>
            <span class="line line-2">	<span class="kn">inherit</span> <span class="p">(</span><span class="kr">import</span> <span class="sx">./imperative.nix</span><span class="p">)</span> <span class="nv">do</span> <span class="nv">assign</span> <span class="nv">print</span> <span class="kr">throw</span><span class="p">;</span>
</span>
            <span class="line line-3">	<span class="c"># ...</span>
</span>
            <span class="line line-4"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="p">(</span><span class="nv">do</span> <span class="p">[</span>
</span>
            <span class="line line-5">	<span class="c"># ...</span>
</span>
            <span class="line line-6"><span class="p">]</span> <span class="p">{</span> <span class="p">})</span> <span class="p">{</span> <span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
One way to avoid this is to use <code>builtins.scopedImport</code> in <code>imperative.nix</code>, but in my opinion, a better approach is to simply put all those things in the initial state <code>_</code>. Now, the actually executable Nix file just contain the list of statements, and <code>imperative.nix</code> will import it to run it. All the keywords like <code>while</code> and <code>assign</code> become variables in <code>_</code>, so nothing needs to be imported or scoped in the executable Nix file. However, this would also mean that we need to <code>import</code> the program in <code>imperative.nix</code> instead of the other way around. To make <code>imperative.nix</code> know which file to import, we can provide it with an argument, so it looks like this:
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="p">{</span> <span class="nv">input</span> <span class="p">}:</span> <span class="kd">let</span> <span class="c"># ...</span>
</span>
            <span class="line line-2"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="p">(</span>
</span>
            <span class="line line-3">	<span class="nv">do</span>
</span>
            <span class="line line-4">	<span class="p">(</span><span class="kr">import</span> <span class="p">(</span><span class="k">if</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">match</span> <span class="nv">input</span> <span class="s2">"^/"</span> <span class="o">!=</span> <span class="kc">null</span> <span class="k">then</span> <span class="nv">input</span> <span class="k">else</span> <span class="s2">"</span><span class="si">${</span><span class="kr">toString</span> <span class="sx">./.</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">input</span><span class="si">}</span><span class="s2">"</span><span class="p">))</span>
</span>
            <span class="line line-5">	<span class="p">{</span> <span class="kn">inherit</span> <span class="nv">assign</span> <span class="k">if</span><span class="err">'</span> <span class="nv">while</span> <span class="nv">print</span> <span class="kr">throw</span> <span class="nv">break</span> <span class="nv">continue</span> <span class="nv">return</span> <span class="nv">function</span> <span class="nv">read</span> <span class="nv">try</span><span class="p">;</span> <span class="p">}</span>
</span>
            <span class="line line-6"><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
To run a program in <code>program.nix</code>, you need to run <code>nix-build imperative.nix --argstr input program.nix</code>. You may encapsulate it in a shebang in <code>imperative.nix</code>:
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="c">#!/usr/bin/env nix-shell</span>
</span>
            <span class="line line-2"><span class="c">#!nix-shell --pure -i "nix-build --no-out-link" -p lix</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
If you now make <code>imperative.nix</code> executable, you can run a program using <code>imperative.nix --argstr input program.nix</code>.
</p>
<p>This now enables us to implement an <code>import</code> function that runs programs from other files:</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="kr">import</span> <span class="o">=</span> <span class="nv">file</span><span class="p">:</span> <span class="nv">do</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="kr">import</span> <span class="nv">file</span><span class="p">);</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
(This shadows the built-in <code>import</code> function, so we will refer to the built-in one as <code>builtins.import</code> if needed.) You can now do something like this:
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="c"># program.nix</span>
</span>
            <span class="line line-2"><span class="p">[</span>
</span>
            <span class="line line-3">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="kr">import</span> <span class="sx">./other-program.nix</span><span class="p">)</span>
</span>
            <span class="line line-4"><span class="p">]</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="c"># other-program.nix</span>
</span>
            <span class="line line-2"><span class="p">[</span>
</span>
            <span class="line line-3">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">print</span> <span class="s2">"Hello from other program!"</span><span class="p">)</span>
</span>
            <span class="line line-4"><span class="p">]</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
When you run <code>imperative.nix --argstr input program.nix</code>, it will print:
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-plain">
        <pre>
          <code>
            <span class="line line-1">trace: Hello from other program!
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p>We may improve it further by removing the need of <code>--argstr input</code> and also adding support for passing command line arguments to the program, at the cost of having to write a little bit of shell script and jq in the shebang (so really we cannot call it a “purely Nix” implementation now, but the nix-shell shebang is simply too powerful):</p>
<!-- markdownlint-disable line-length -->
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="c">#!/usr/bin/env nix-shell</span>
</span>
            <span class="line line-2"><span class="c">#!nix-shell --pure -i "sh -c '_1=\$1; _2=\$2; shift 2; exec nix-build --no-out-link \"\$_1\" --argstr input \"\$_2\" --argstr argvJSON \"\$(printf \"%s\\\\0\" \"\$@\" | jq -Rsc \"split(\\\"\\\\u0000\\\")[:-1]\")\"' --" -p lix jq</span>
</span>
            <span class="line line-3">
</span>
            <span class="line line-4"><span class="p">{</span> <span class="nv">input</span><span class="p">,</span> <span class="nv">argvJSON</span> <span class="p">}:</span> <span class="kd">let</span> <span class="c"># ...</span>
</span>
            <span class="line line-5">	<span class="nv">argv</span> <span class="o">=</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">fromJSON</span> <span class="nv">argvJSON</span><span class="p">;</span>
</span>
            <span class="line line-6"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="c"># ...</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<!-- markdownlint-enable line-length -->
<p class="no-indent">
The mechanism is that the shell script in the shebang extracts the first two command line arguments as the Nix file to run and the input file, and the rest of the command line arguments are concatenated with null characters and converted to a JSON array using <code>jq</code>. In <code>imperative.nix</code>, we parse the JSON array back to a Nix list and provide it as the variable <code>argv</code> in the initial state <code>_</code>. You can now simply use a shebang <code>#!/usr/bin/env imperative.nix</code> in <code>program.nix</code>, and then you can run it with <code>./program.nix arg1 arg2</code> if you make it executable, and the command line arguments will be available in the list <code>_.argv</code>.
</p>
<p>Finally, we can use <code>try</code> instead of <code>do</code> in the ultimate <code>builtins.seq</code> call to catch any uncaught exceptions in the program. Here is the final version of <code>imperative.nix</code> and an example <code>program.nix</code> that uses most of the features we introduced:</p>
<!-- markdownlint-disable line-length -->
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="c">#!/usr/bin/env nix-shell</span>
</span>
            <span class="line line-2"><span class="c">#!nix-shell --pure -i "sh -c '_1=\$1; _2=\$2; shift 2; exec nix-build --no-out-link \"\$_1\" --argstr input \"\$_2\" --argstr argvJSON \"\$(printf \"%s\\\\0\" \"\$@\" | jq -Rsc \"split(\\\"\\\\u0000\\\")[:-1]\")\"' --" -p lix jq</span>
</span>
            <span class="line line-3">
</span>
            <span class="line line-4"><span class="p">{</span> <span class="nv">input</span><span class="p">,</span> <span class="nv">argvJSON</span> <span class="p">}:</span> <span class="kd">let</span>
</span>
            <span class="line line-5">	<span class="nv">do</span> <span class="o">=</span> <span class="nv">statements</span><span class="p">:</span> <span class="nv">_</span><span class="p">:</span>
</span>
            <span class="line line-6">		<span class="k">if</span> <span class="nv">statements</span> <span class="o">==</span> <span class="p">[</span> <span class="p">]</span> <span class="k">then</span> <span class="nv">_</span>
</span>
            <span class="line line-7">		<span class="k">else</span> <span class="kd">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="nv">_</span> <span class="o">//</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">head</span> <span class="nv">statements</span> <span class="nv">_</span> <span class="nv">_</span><span class="p">;</span> <span class="kn">in</span>
</span>
            <span class="line line-8">			<span class="k">if</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">hasAttr</span> <span class="s2">"_thrown_"</span> <span class="nv">result</span> <span class="k">then</span> <span class="nv">result</span>
</span>
            <span class="line line-9">			<span class="k">else</span> <span class="nv">do</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">tail</span> <span class="nv">statements</span><span class="p">)</span> <span class="nv">result</span><span class="p">;</span>
</span>
            <span class="line line-10">	<span class="nv">assign</span> <span class="o">=</span> <span class="nv">variables</span><span class="p">:</span> <span class="nv">_</span><span class="p">:</span> <span class="nv">_</span> <span class="o">//</span> <span class="nv">variables</span><span class="p">;</span>
</span>
            <span class="line line-11">	<span class="nv">read</span> <span class="o">=</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">readFile</span><span class="p">;</span>
</span>
            <span class="line line-12">	<span class="nv">print</span> <span class="o">=</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">trace</span><span class="p">;</span>
</span>
            <span class="line line-13">	<span class="k">if</span><span class="err">'</span> <span class="o">=</span> <span class="nv">condition</span><span class="p">:</span> <span class="kc">true</span><span class="nv">Statements</span><span class="p">:</span> <span class="kc">false</span><span class="nv">Statements</span><span class="p">:</span> <span class="nv">_</span><span class="p">:</span>
</span>
            <span class="line line-14">		<span class="nv">do</span> <span class="p">(</span><span class="k">if</span> <span class="nv">condition</span> <span class="nv">_</span> <span class="k">then</span> <span class="kc">true</span><span class="nv">Statements</span> <span class="k">else</span> <span class="kc">false</span><span class="nv">Statements</span><span class="p">)</span> <span class="nv">_</span><span class="p">;</span>
</span>
            <span class="line line-15">	<span class="nv">whileWithoutJump</span> <span class="o">=</span> <span class="nv">condition</span><span class="p">:</span> <span class="nv">body</span><span class="p">:</span>
</span>
            <span class="line line-16">		<span class="k">if</span><span class="err">'</span> <span class="nv">condition</span> <span class="p">[</span> <span class="nv">body</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">whileWithoutJump</span> <span class="nv">condition</span> <span class="nv">body</span><span class="p">)</span> <span class="p">]</span> <span class="p">[</span> <span class="p">];</span>
</span>
            <span class="line line-17">	<span class="nv">catchOnly</span> <span class="o">=</span> <span class="nv">handled</span><span class="p">:</span> <span class="nv">thrown</span><span class="p">:</span> <span class="k">if</span> <span class="nv">thrown</span> <span class="o">==</span> <span class="nv">handled</span> <span class="k">then</span> <span class="p">[</span> <span class="p">]</span> <span class="k">else</span> <span class="p">[</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="kr">throw</span> <span class="nv">thrown</span><span class="p">)</span> <span class="p">];</span>
</span>
            <span class="line line-18">	<span class="nv">while</span> <span class="o">=</span> <span class="nv">condition</span><span class="p">:</span> <span class="nv">statements</span><span class="p">:</span> <span class="nv">try</span> <span class="p">[</span>
</span>
            <span class="line line-19">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">whileWithoutJump</span> <span class="nv">condition</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">try</span> <span class="nv">statements</span> <span class="p">(</span><span class="nv">catchOnly</span> <span class="s2">"_continue_"</span><span class="p">)))</span>
</span>
            <span class="line line-20">	<span class="p">]</span> <span class="p">(</span><span class="nv">catchOnly</span> <span class="s2">"_break_"</span><span class="p">);</span>
</span>
            <span class="line line-21">	<span class="kr">throw</span> <span class="o">=</span> <span class="nv">thrown</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">{</span> <span class="nv">_thrown_</span> <span class="o">=</span> <span class="nv">thrown</span><span class="p">;</span> <span class="p">};</span>
</span>
            <span class="line line-22">	<span class="nv">break</span> <span class="o">=</span> <span class="kr">throw</span> <span class="s2">"_break_"</span><span class="p">;</span>
</span>
            <span class="line line-23">	<span class="nv">continue</span> <span class="o">=</span> <span class="kr">throw</span> <span class="s2">"_continue_"</span><span class="p">;</span>
</span>
            <span class="line line-24">	<span class="nv">return</span> <span class="o">=</span> <span class="kr">throw</span> <span class="s2">"_return_"</span><span class="p">;</span>
</span>
            <span class="line line-25">	<span class="nv">try</span> <span class="o">=</span> <span class="nv">statements</span><span class="p">:</span> <span class="nv">catch</span><span class="p">:</span> <span class="nv">_</span><span class="p">:</span> <span class="kd">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="nv">do</span> <span class="nv">statements</span> <span class="nv">_</span><span class="p">;</span> <span class="kn">in</span>
</span>
            <span class="line line-26">		<span class="k">if</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">hasAttr</span> <span class="s2">"_thrown_"</span> <span class="nv">result</span> <span class="k">then</span>
</span>
            <span class="line line-27">			<span class="nv">do</span> <span class="p">(</span><span class="nv">catch</span> <span class="nv">result</span><span class="o">.</span><span class="nv">_thrown_</span><span class="p">)</span> <span class="p">(</span><span class="kr">removeAttrs</span> <span class="nv">result</span> <span class="p">[</span> <span class="s2">"_thrown_"</span> <span class="p">])</span>
</span>
            <span class="line line-28">		<span class="k">else</span> <span class="nv">result</span><span class="p">;</span>
</span>
            <span class="line line-29">	<span class="nv">function</span> <span class="o">=</span> <span class="nv">functions</span><span class="p">:</span> <span class="nv">assign</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">mapAttrs</span> <span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="nv">fun</span><span class="p">:</span> <span class="nv">arg</span><span class="p">:</span> <span class="nv">try</span> <span class="p">(</span><span class="nv">fun</span> <span class="nv">arg</span><span class="p">)</span> <span class="p">(</span><span class="nv">catchOnly</span> <span class="s2">"_return_"</span><span class="p">))</span> <span class="nv">functions</span><span class="p">);</span>
</span>
            <span class="line line-30">	<span class="kr">import</span> <span class="o">=</span> <span class="nv">file</span><span class="p">:</span> <span class="nv">do</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="kr">import</span> <span class="nv">file</span><span class="p">);</span>
</span>
            <span class="line line-31">	<span class="nv">argv</span> <span class="o">=</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">fromJSON</span> <span class="nv">argvJSON</span><span class="p">;</span>
</span>
            <span class="line line-32"><span class="kn">in</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">seq</span> <span class="p">(</span>
</span>
            <span class="line line-33">	<span class="nv">try</span>
</span>
            <span class="line line-34">	<span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="kr">import</span> <span class="p">(</span><span class="k">if</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">match</span> <span class="nv">input</span> <span class="s2">"^/"</span> <span class="o">!=</span> <span class="kc">null</span> <span class="k">then</span> <span class="nv">input</span> <span class="k">else</span> <span class="s2">"</span><span class="si">${</span><span class="kr">toString</span> <span class="sx">./.</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">input</span><span class="si">}</span><span class="s2">"</span><span class="p">))</span>
</span>
            <span class="line line-35">	<span class="kr">builtins</span><span class="o">.</span><span class="kr">throw</span>
</span>
            <span class="line line-36">	<span class="p">{</span> <span class="kn">inherit</span> <span class="nv">assign</span> <span class="k">if</span><span class="err">'</span> <span class="nv">while</span> <span class="nv">print</span> <span class="kr">throw</span> <span class="nv">break</span> <span class="nv">continue</span> <span class="nv">return</span> <span class="nv">function</span> <span class="nv">read</span> <span class="nv">try</span> <span class="kr">import</span> <span class="kr">builtins</span> <span class="nv">argv</span><span class="p">;</span> <span class="p">}</span>
</span>
            <span class="line line-37"><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<!-- markdownlint-enable line-length -->
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-nix">
        <pre>
          <code>
            <span class="line line-1"><span class="c">#!/usr/bin/env imperative.nix</span>
</span>
            <span class="line line-2">
</span>
            <span class="line line-3"><span class="p">[</span>
</span>
            <span class="line line-4">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="kr">import</span> <span class="p">(</span><span class="nv">_</span><span class="o">.</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">toFile</span> <span class="s2">"other-input.nix"</span> <span class="s2">"[ (_: _.print </span><span class="se">\"</span><span class="s2">Hello from other file!</span><span class="se">\"</span><span class="s2">) ]"</span><span class="p">))</span>
</span>
            <span class="line line-5">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">function</span> <span class="p">{</span> <span class="nv">hello</span> <span class="o">=</span> <span class="nv">arg</span><span class="p">:</span> <span class="p">[</span>
</span>
            <span class="line line-6">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="k">if</span><span class="err">'</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">arg</span> <span class="o">==</span> <span class="s2">""</span><span class="p">)</span> <span class="p">[</span>
</span>
            <span class="line line-7">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">print</span> <span class="s2">"What's your name?"</span><span class="p">)</span>
</span>
            <span class="line line-8">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">return</span><span class="p">)</span>
</span>
            <span class="line line-9">		<span class="p">]</span> <span class="p">[</span> <span class="p">])</span>
</span>
            <span class="line line-10">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">print</span> <span class="s2">"Hello, </span><span class="si">${</span><span class="nv">arg</span><span class="si">}</span><span class="s2">!"</span><span class="p">)</span>
</span>
            <span class="line line-11">	<span class="p">];</span> <span class="p">})</span>
</span>
            <span class="line line-12">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">hello</span> <span class="s2">""</span><span class="p">)</span>
</span>
            <span class="line line-13">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">hello</span> <span class="s2">"World"</span><span class="p">)</span>
</span>
            <span class="line line-14">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">while</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">argv</span> <span class="o">!=</span> <span class="p">[</span> <span class="p">])</span> <span class="p">[</span>
</span>
            <span class="line line-15">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">hello</span> <span class="p">(</span><span class="nv">_</span><span class="o">.</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">head</span> <span class="nv">_</span><span class="o">.</span><span class="nv">argv</span><span class="p">))</span>
</span>
            <span class="line line-16">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">assign</span> <span class="p">{</span> <span class="nv">argv</span> <span class="o">=</span> <span class="nv">_</span><span class="o">.</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">tail</span> <span class="nv">_</span><span class="o">.</span><span class="nv">argv</span><span class="p">;</span> <span class="p">})</span>
</span>
            <span class="line line-17">	<span class="p">])</span>
</span>
            <span class="line line-18">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">assign</span> <span class="p">{</span> <span class="nv">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">})</span>
</span>
            <span class="line line-19">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">while</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">)</span> <span class="p">[</span>
</span>
            <span class="line line-20">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="k">if</span><span class="err">'</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="p">[</span>
</span>
            <span class="line line-21">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">break</span><span class="p">)</span>
</span>
            <span class="line line-22">		<span class="p">]</span> <span class="p">[</span> <span class="p">])</span>
</span>
            <span class="line line-23">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="k">if</span><span class="err">'</span> <span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="p">[</span>
</span>
            <span class="line line-24">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">assign</span> <span class="p">{</span> <span class="nv">i</span> <span class="o">=</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="p">})</span>
</span>
            <span class="line line-25">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">continue</span><span class="p">)</span>
</span>
            <span class="line line-26">		<span class="p">]</span> <span class="p">[</span>
</span>
            <span class="line line-27">			<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">hello</span> <span class="s2">"</span><span class="si">${</span><span class="nv">_</span><span class="o">.</span><span class="kr">builtins</span><span class="o">.</span><span class="kr">toString</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</span>
            <span class="line line-28">		<span class="p">])</span>
</span>
            <span class="line line-29">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">assign</span> <span class="p">{</span> <span class="nv">i</span> <span class="o">=</span> <span class="nv">_</span><span class="o">.</span><span class="nv">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="p">})</span>
</span>
            <span class="line line-30">	<span class="p">])</span>
</span>
            <span class="line line-31">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">try</span> <span class="p">[</span>
</span>
            <span class="line line-32">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="kr">throw</span> <span class="s2">"Some error message."</span><span class="p">)</span>
</span>
            <span class="line line-33">	<span class="p">]</span> <span class="p">(</span><span class="nv">exception</span><span class="p">:</span> <span class="p">[</span>
</span>
            <span class="line line-34">		<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">print</span> <span class="s2">"Caught exception: </span><span class="si">${</span><span class="nv">exception</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</span>
            <span class="line line-35">	<span class="p">]))</span>
</span>
            <span class="line line-36">	<span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="nv">_</span><span class="o">.</span><span class="nv">print</span> <span class="s2">"Goodbye!"</span><span class="p">)</span>
</span>
            <span class="line line-37"><span class="p">]</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
When you run <code>./program.nix Kat "Ulysses Zhan"</code>, it will print:
</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-plain">
        <pre>
          <code>
            <span class="line line-1">trace: Hello from other file!
</span>
            <span class="line line-2">trace: What's your name?
</span>
            <span class="line line-3">trace: Hello, World!
</span>
            <span class="line line-4">trace: Hello, Kat!
</span>
            <span class="line line-5">trace: Hello, Ulysses Zhan!
</span>
            <span class="line line-6">trace: Hello, 0!
</span>
            <span class="line line-7">trace: Hello, 1!
</span>
            <span class="line line-8">trace: Hello, 3!
</span>
            <span class="line line-9">trace: Caught exception: Some error message.
</span>
            <span class="line line-10">trace: Goodbye!
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<h2 data-label="0.7" id="possible-improvements">Possible improvements</h2>
<p>Maybe I can improve it further by adding support for local variables. It would also be nice if functions with side effects can also have return values, but this would be tricky to implement.</p>]]></content><author><name>UlyssesZhan</name><email>ulysseszhan@gmail.com</email></author><category term="programming" /><category term="nix" /><category term="functional programming" /><summary type="html"><![CDATA[Nix is a functional programming language primarily used for package management and system configuration. However, it may be interesting to emulate imperative programming constructs within Nix. This post explores how to achieve imperative-style programming in Nix, ipmlementing control flow, exception handling, and interactive IO.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ulysseszh.github.io/assets/images/covers/2025-11-06-nix-imperative.png" /><media:content medium="image" url="https://ulysseszh.github.io/assets/images/covers/2025-11-06-nix-imperative.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>