<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Linkme on PHP Boy Scout</title><link>https://blog-570662.gitlab.io/tags/linkme/</link><description>Recent content in Linkme on PHP Boy Scout</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><copyright>Matt Cockayne</copyright><lastBuildDate>Fri, 24 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog-570662.gitlab.io/tags/linkme/index.xml" rel="self" type="application/rss+xml"/><item><title>Registering commands without life before main</title><link>https://blog-570662.gitlab.io/registering-commands-without-life-before-main/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog-570662.gitlab.io/registering-commands-without-life-before-main/</guid><description>&lt;img src="https://blog-570662.gitlab.io/registering-commands-without-life-before-main/cover-registering-commands-without-life-before-main.png" alt="Featured image of post Registering commands without life before main" /&gt;&lt;p&gt;I ended the &lt;a class="link" href="https://blog-570662.gitlab.io/what-survives-a-port/" &gt;last post&lt;/a&gt; promising to show how a Rust command registers itself when the language flatly refuses to run any of your code before &lt;code&gt;main()&lt;/code&gt;. This is that post, and it&amp;rsquo;s a lovely example of reaching the same outcome by a completely different road.&lt;/p&gt;
&lt;p&gt;The outcome I wanted to keep is self-registration.&lt;/p&gt;
&lt;h2 id="what-self-registration-buys"&gt;What self-registration buys
&lt;/h2&gt;&lt;p&gt;A command in go-tool-base lives in its own file, and that file puts the command into the framework itself. There&amp;rsquo;s no central list of commands to keep in sync. You add a file, the command appears. You delete the file, it&amp;rsquo;s gone. Nothing else changes.&lt;/p&gt;
&lt;p&gt;That property is worth protecting. The alternative, a hand-maintained registry that every new command has to be threaded into, is exactly the sort of central file that turns into a merge-conflict magnet and quietly falls out of date. So when go-tool-base moved to Rust, self-registration was firmly in the column of things that had to survive.&lt;/p&gt;
&lt;p&gt;The way Go &lt;em&gt;did&lt;/em&gt; it was not.&lt;/p&gt;
&lt;h2 id="how-go-does-it"&gt;How Go does it
&lt;/h2&gt;&lt;p&gt;A Go package can declare an &lt;code&gt;init()&lt;/code&gt; function, and the runtime guarantees every &lt;code&gt;init()&lt;/code&gt; runs before &lt;code&gt;main()&lt;/code&gt; starts. A go-tool-base command file uses this to append itself to a package-level slice:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;DeployCommand&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By the time &lt;code&gt;main()&lt;/code&gt; runs, every command file&amp;rsquo;s &lt;code&gt;init()&lt;/code&gt; has already fired and the registry slice is populated. It&amp;rsquo;s a tidy trick, and it leans entirely on a Go feature: code that executes before &lt;code&gt;main()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="rust-doesnt-have-that"&gt;Rust doesn&amp;rsquo;t have that
&lt;/h2&gt;&lt;p&gt;Rust has no &lt;code&gt;init()&lt;/code&gt;. There&amp;rsquo;s no language-blessed phase that runs your code before &lt;code&gt;main()&lt;/code&gt;. This is a deliberate decision, not an oversight. Code running before &lt;code&gt;main()&lt;/code&gt; across many files has no well-defined order, and a startup phase whose ordering you can&amp;rsquo;t see is a classic source of subtle, miserable bugs. Rust closed that door on purpose.&lt;/p&gt;
&lt;p&gt;Which leaves a real question. If nothing runs before &lt;code&gt;main()&lt;/code&gt;, how does a command file insert itself into a registry without a central list editing it in?&lt;/p&gt;
&lt;h2 id="distributed-slices"&gt;Distributed slices
&lt;/h2&gt;&lt;p&gt;The answer is a crate called &lt;code&gt;linkme&lt;/code&gt;, and the mechanism is the &lt;em&gt;linker&lt;/em&gt; rather than a runtime phase.&lt;/p&gt;
&lt;p&gt;You declare a slice the framework will collect into:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#[distributed_slice]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;BUILTIN_COMMANDS&lt;/span&gt;: &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A command file then contributes one entry to it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Greet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Greet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#[distributed_slice(BUILTIN_COMMANDS)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;register_greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Box&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Greet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s the part that makes it work. The &lt;code&gt;#[distributed_slice]&lt;/code&gt; attribute doesn&amp;rsquo;t generate any code that runs at startup. It places each entry into a dedicated section of the compiled object file. When the linker builds the final binary, it gathers everything in that section and lays it out as one contiguous array. &lt;code&gt;BUILTIN_COMMANDS&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; that array.&lt;/p&gt;
&lt;p&gt;So by the time the program exists as a binary on disk, the registry is already assembled. &lt;code&gt;main()&lt;/code&gt; doesn&amp;rsquo;t build it. No &lt;code&gt;init()&lt;/code&gt; builds it. The linker built it, statically, as part of producing the executable. At runtime the framework iterates a slice that was complete before the process ever started.&lt;/p&gt;
&lt;h2 id="what-you-get-from-it"&gt;What you get from it
&lt;/h2&gt;&lt;p&gt;The outcome is the one Go&amp;rsquo;s &lt;code&gt;init()&lt;/code&gt; gave, and then a bit more.&lt;/p&gt;
&lt;p&gt;A command still lives in one file and still self-registers. Adding a command is still adding a file. There&amp;rsquo;s still no central list.&lt;/p&gt;
&lt;p&gt;But there&amp;rsquo;s no startup phase to reason about, because there isn&amp;rsquo;t one. There&amp;rsquo;s no global mutable slice being appended to as &lt;code&gt;init()&lt;/code&gt;s fire, because nothing is appended at runtime; the slice is immutable and finished. There&amp;rsquo;s no ordering question, because the linker isn&amp;rsquo;t running your code, it&amp;rsquo;s collecting data. And it costs nothing at runtime: assembling the registry happened at link time, so program start just reads it.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s the same idea go-tool-base had, expressed by the tool Rust actually gives you. Go reaches the registry through a controlled phase before &lt;code&gt;main()&lt;/code&gt;. Rust reaches it without any phase at all, because the linker did the assembly while the binary was still being built. Two roads, one destination&amp;hellip; which, if you&amp;rsquo;ve been following along, is becoming the whole theme of the Rust side of this project.&lt;/p&gt;
&lt;h2 id="in-short"&gt;In short
&lt;/h2&gt;&lt;p&gt;Self-registration, where a command file inserts itself into the framework with no central list, is a property worth keeping. go-tool-base achieves it with a package-level &lt;code&gt;init()&lt;/code&gt;, leaning on Go&amp;rsquo;s guarantee that such functions run before &lt;code&gt;main()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Rust has no equivalent and wants none, because code running before &lt;code&gt;main()&lt;/code&gt; has no clear ordering. rust-tool-base uses &lt;code&gt;linkme&lt;/code&gt; distributed slices instead: each command is placed into a dedicated linker section, and the linker assembles them into one contiguous, immutable slice as it builds the binary. The registry is complete before the program runs. Same outcome as Go&amp;rsquo;s &lt;code&gt;init()&lt;/code&gt;, with no life before &lt;code&gt;main&lt;/code&gt; required.&lt;/p&gt;</description></item></channel></rss>