<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Miblog</title><link>https://vyskocil.me/</link><description>Recent content on Miblog</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://vyskocil.me/index.xml" rel="self" type="application/rss+xml"/><item><title>Fearless Dependency Injection for Go</title><link>https://vyskocil.me/blog/fearless-dependency-injection-for-go/</link><pubDate>Tue, 23 Jun 2026 18:58:52 +0200</pubDate><guid>https://vyskocil.me/blog/fearless-dependency-injection-for-go/</guid><description>Dependency Injection is one of those terms that sounds much more dramatic than it really is. In modern frameworks, especially in the Java world, it often comes with containers, annotations, runtime magic, and a lot of ceremony.&amp;#xA;Following my older fearless BDD for Go, where I …</description><content:encoded><![CDATA[<p>Dependency Injection is one of those terms that sounds much more dramatic than
it really is. In modern frameworks, especially in the Java world, it often
comes with containers, annotations, runtime magic, and a lot of ceremony.</p>
<p>Following my older <a href="/blog/fearless-bdd-for-go/">fearless BDD for Go</a>, where I shown how a simple naming convetion
can replace heavy and not idiomatic BDD frameworks, I am going to show that the
idea is way simpler.</p>
<h2 id="what-is-the-dependency-injection">What is the dependency injection?</h2>
<blockquote>
<p>Dependency Injection is a 25-dollar term for a 5-cent concept. – James Shore</p>
</blockquote>
<p>Well, sometimes not.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#a6e22e">@Component</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Controller</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@Autowired</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Spring does the magic here</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> Service myService;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ... if it can find this</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">Service</span> {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@Component</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">ServiceImpl</span> <span style="color:#66d9ef">implements</span> Service {}</span></span></code></pre></div>
<p>In Go. That is the whole thing. No framework. No hidden runtime wiring. No
magic. Just explicit dependencies and small functions.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Service</span> <span style="color:#66d9ef">interface</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Controller</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">service</span> <span style="color:#a6e22e">Service</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewController</span>(<span style="color:#a6e22e">service</span> <span style="color:#a6e22e">Service</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">Controller</span> {
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>The whole principle is that the dependencies, like the service layer are
constructed <em>outside</em> and passed simply as a function arguments. The term
<em>injected</em> makes things confusing. It does not help that a lot of languages and
frameworks tends to hide arguments passing behind a lot of magic.</p>
<h2 id="the-wrong-pattern">The wrong pattern</h2>
<p>This is an opposite of DI - there is a huge piece of code implementing whole
initialization in one function. This is not recommended, but viable approach
for one-shot services still.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewController</span>(<span style="color:#a6e22e">conf</span> <span style="color:#a6e22e">Config</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">Controller</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// connect to database</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// open http server</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// create a service layer</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// start the job queue, ...</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="frameworks">Frameworks</h2>
<p>Go is not immune when it comes to frameworks. A lot of Java, Clojure and other
developers goes to the Go ecosystem and do not like the simplicity. Here
frameworks described in my own words. However mileage may vary.</p>
<ul>
<li><a href="https://github.com/uber-go/fx">Uber/fx</a> - reflection based complex DI system
from Uber. My experience is that this library saves one from a bit of writing
in exchange of a lot of runtime errors. When it works is amazing, but debugging
it before LLMs was a pain.</li>
<li><a href="https://github.com/goforj/wire">goforj/wire</a> - this resolves everything via
go generate. With additions like go tool support I consider this more ergonomic
than it used be. Fork of a google wire, which was archived in 2025. Errors can
be tricky, but all are revealed in a compile time.</li>
<li><a href="https://github.com/samber/do">samber/do</a> - this uses a generics, however I had personally
found this to be even more confusing and as the dependencies are resolved using
indirection via <code>do.Injector</code> interface in runtime. This gives again an amazing
possibility of having a runtime errors, which are as readable as Java exceptions.</li>
</ul>
<h2 id="no-framework">No framework</h2>
<blockquote>
<p>Clear is better than clever; Reflection is never clear.</p>
</blockquote>
<p>The greatest part of the <code>wire</code> is that all it does is that it generates a lot
of Go functions. All the magic behind <code>do</code>, <code>fx</code> or Spring Boot committed to
git repository. Can be inspected, can be debugged - only a single <code>go generate</code>
needed.</p>
<p>When writing an <a href="https://codeberg.org/gonix/mcp">MCP server</a> for
<a href="https://codeberg.org/gonix">gonix</a> project I wanted to use benefits of the
<em>approach</em> but to not commit to any framework. One of the gonix principle is to
stay light on dependencies. It turns out that with a simple conventions the whole initialization is much easier than one would say</p>
<p><code>internal/app</code> is the package which contains all infrastructure code. The name
<code>app</code> is short and clear enough in the source code. I saw code bases using names
like <code>infrastructure</code>, <code>dependencies</code> or has all this code scattered in various
packages.</p>
<ul>
<li><code>conf.go</code>: code dealing with a configuration</li>
<li><code>di.go</code>: dependency injection code</li>
<li><code>server.go</code>: wrapper on top of go-sdk MCP server</li>
</ul>
<h2 id="dependencies">Dependencies</h2>
<p>This is an example of a Provider - which is super confusing concept again. Lets stop bashing Java
and check what angular documentation says.</p>
<blockquote>
<p>Using <code>providedIn</code> in the <code>@Injectable</code> decorator, the <code>@Service</code> decorator, or by
providing a factory in the <code>InjectionToken</code> configuration</p>
</blockquote>
<p>I must admit that I do not understand a single word. In a simpler language
like Go, the provider is a simple piece of code returning an input for other
function. The example below returns a path to the cache directory, where
<code>wazero</code> can store compilation artifacts.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">CacheDir</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">GuessCacheDir</span>() <span style="color:#a6e22e">CacheDir</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">cacheDir</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">UserCacheDir</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">CacheDir</span>(<span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">appCacheDir</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Join</span>(<span style="color:#a6e22e">cacheDir</span>, <span style="color:#e6db74">&#34;gonix-mcp&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">MkdirAll</span>(<span style="color:#a6e22e">appCacheDir</span>, <span style="color:#ae81ff">0</span><span style="color:#a6e22e">o755</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">CacheDir</span>(<span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">CacheDir</span>(<span style="color:#a6e22e">cacheDir</span>)
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>And instead of a runtime magic, the type definition is all its needed to connect
the provider with its counterpart. Types, what a concept! Can&rsquo;t be really much easier
than this.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WasmRuntime</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">root</span> <span style="color:#a6e22e">Root</span>, <span style="color:#a6e22e">cache</span> <span style="color:#a6e22e">CacheDir</span>)
</span></span><span style="display:flex;"><span>(<span style="color:#f92672">*</span><span style="color:#a6e22e">wasm</span>.<span style="color:#a6e22e">Runtime</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span>, <span style="color:#66d9ef">error</span>)</span></span></code></pre></div>
<p>So the whole code chain is again, driven by types, visible in IDE or editor,
checked by the compiler.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// dependency 1</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ctx</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>()
</span></span><span style="display:flex;"><span><span style="color:#75715e">// dependency 2</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">root</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">OpenRoot</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">config</span>, <span style="color:#a6e22e">flags</span>.<span style="color:#a6e22e">Root</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">// dependency 3</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">cacheDir</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">GuessCacheDir</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">runtime</span>, <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">WasmRuntime</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ctx</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">root</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">cacheDir</span>,
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>And this all is the simple straightforward code all the way down.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// tool: read_text_file</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">rtf</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">BuildReadTextFileTool</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">config</span>.<span style="color:#a6e22e">Tools</span>.<span style="color:#a6e22e">ReadTextFile</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">root</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">coreutils</span>,
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">registrars</span> []<span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">Registrar</span>{<span style="color:#a6e22e">rtf</span>, <span style="color:#a6e22e">sht</span>, <span style="color:#a6e22e">lst</span>, <span style="color:#a6e22e">lsd</span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">NewServer</span>(<span style="color:#e6db74">&#34;gonix-mcp&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">tool</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">registrars</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">AddTool</span>(<span style="color:#a6e22e">tool</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">mcp</span>.<span style="color:#a6e22e">StdioTransport</span>{})
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="use-case-flags">Use case: flags</h2>
<p>One thing I&rsquo;ve been struggling with is the command line flags parsing - the
default style in Go relies on global variables (or variables with a too huge
scope), popular frameworks like cobra uses a combination of <code>init()</code> and global
variables.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">cfgFile</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">rootCmd</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">cobra</span>.<span style="color:#a6e22e">Command</span>{}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">init</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">rootCmd</span>.<span style="color:#a6e22e">PersistentFlags</span>().<span style="color:#a6e22e">StringVar</span>(<span style="color:#960050;background-color:#1e0010">\</span><span style="color:#f92672">&amp;</span><span style="color:#a6e22e">cfgFile</span>, <span style="color:#e6db74">&#34;config&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;config file (default locations: ., $HOME/.myapp/)&#34;</span>)
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>It turn out the command line flags are the <em>dependency</em> too as they change the
way how&rsquo;s program supposed to be initialized. The <code>Flag</code> declaration tells the
code if the flag was <em>present</em> on a command line. The other struct simply lists
all the possible options. The type definitions are again for a further type
safety.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Flag</span>[<span style="color:#a6e22e">T</span> <span style="color:#66d9ef">any</span>] <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Value</span>   <span style="color:#a6e22e">T</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Present</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">ConfigFlag</span> <span style="color:#a6e22e">Flag</span>[<span style="color:#66d9ef">string</span>]
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">RootFlag</span> <span style="color:#a6e22e">Flag</span>[<span style="color:#66d9ef">string</span>]
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Flags</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Config</span> <span style="color:#a6e22e">ConfigFlag</span> <span style="color:#75715e">// -config</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Root</span>   <span style="color:#a6e22e">RootFlag</span> <span style="color:#75715e">// -root</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Following this principle lead to the most pleasant command line flags parsing I
ever saw in a Go program. So if anyone was in doubt why to use it, this is the
reason.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">ParseFlags</span>(<span style="color:#a6e22e">args</span> []<span style="color:#66d9ef">string</span>) (<span style="color:#a6e22e">Flags</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fset</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">flag</span>.<span style="color:#a6e22e">NewFlagSet</span>(<span style="color:#e6db74">&#34;gonix-mcp&#34;</span>, <span style="color:#a6e22e">flag</span>.<span style="color:#a6e22e">ExitOnError</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">config</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fset</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;config&#34;</span>, <span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">root</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fset</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;root&#34;</span>, <span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fset</span>.<span style="color:#a6e22e">Parse</span>(<span style="color:#a6e22e">args</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Flags</span>{}, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ret</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">Flags</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Config</span>: <span style="color:#a6e22e">ConfigFlag</span>{
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Value</span>: <span style="color:#f92672">*</span><span style="color:#a6e22e">config</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Root</span>: <span style="color:#a6e22e">RootFlag</span>{
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Value</span>: <span style="color:#f92672">*</span><span style="color:#a6e22e">root</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fset</span>.<span style="color:#a6e22e">Visit</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">f</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">flag</span>.<span style="color:#a6e22e">Flag</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">Name</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;config&#34;</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">ret</span>.<span style="color:#a6e22e">Config</span>.<span style="color:#a6e22e">Present</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;root&#34;</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">ret</span>.<span style="color:#a6e22e">Root</span>.<span style="color:#a6e22e">Present</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">ret</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>And a code which needs to react on a <code>-root</code> flag?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">OpenRoot</span>(<span style="color:#a6e22e">config</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Config</span>, <span style="color:#a6e22e">root</span> <span style="color:#a6e22e">RootFlag</span>) (<span style="color:#a6e22e">Root</span>, <span style="color:#66d9ef">error</span>)</span></span></code></pre></div>
<h1 id="conclusion">Conclusion</h1>
<p>The real lesson here is not to avoid frameworks at all costs. The important lessons are</p>
<ul>
<li>do not be afraid to make the wiring explicit and visible</li>
<li>express it in code that is easy to follow and safe</li>
<li>following the dependency injection <em>pattern</em> lead to more elegant code</li>
</ul>
<h2 id="logo">Logo</h2>
<p><a href="https://raw.githubusercontent.com/egonelbre/gophers/refs/heads/master/vector/science/soldering.svg">soldering.svg</a></p>
]]></content:encoded></item><item><title>Gonix: Go All the Way Down</title><link>https://vyskocil.me/blog/gonix-go-all-the-way-down/</link><pubDate>Wed, 17 Jun 2026 12:22:46 +0200</pubDate><guid>https://vyskocil.me/blog/gonix-go-all-the-way-down/</guid><description>In gonix: unix from userspace and gonix: the sixth element, I introduced the full user space Unix scripting system implemented in Go.&amp;#xA;However, shell scripting is not only about running compiled binaries. It is also about writing new functions that can be called from the scripts. …</description><content:encoded><![CDATA[<p>In <a href="/blog/gonix-unix-from-userspace/">gonix: unix from userspace</a> and <a href="/blog/gonix-the-sixth-element/">gonix: the sixth element</a>, I introduced the full user space Unix
scripting system implemented in Go.</p>
<p>However, shell scripting is not only about running compiled binaries. It is also
about writing new functions that can be called from the scripts. Writing code in
a shell itself is clunky as it was never supposed to be a real
programming language. Gonix addresses this by the fact all builtins are (or
can be) trivially implementable in Go.</p>
<p>However if everything needs to be compiled, is is still a scripting?</p>
<h2 id="writing-script-commands-in-go">Writing script commands in Go</h2>
<p>I was thrilled, surprised and amazed when I discovered <a href="https://mvm.sh">mvm.sh</a> - a
fast virtual machine for Go and beyond. Why? Well it allows one to write
scripts in Go. The real scripts and not a <code>go run</code> that needs to be cimpiled first.
This is a% scripting, yet powered by Go and its standard library.</p>
<p>So <a href="codeverg.org/gonix/mvm">https://codeberg.org/gonix/mvm</a> integrates with it.
As a simple example is worth lines of text. So here is one.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// &#34;codeberg.org/gonix/mvm/core&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// &#34;codeberg.org/gonix/gio/unix&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">code</span> = <span style="color:#e6db74">`
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">package main
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">import &#34;fmt&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">import &#34;os&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">func main() {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  fmt.Printf(&#34;hello %s\n&#34;, os.Args[1])
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">`</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">b</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">core</span>.<span style="color:#a6e22e">NewBuilder</span>(<span style="color:#a6e22e">code</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">f</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Build</span>(<span style="color:#e6db74">&#34;test&#34;</span>, []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;mvm&#34;</span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">stdout</span> <span style="color:#a6e22e">bytes</span>.<span style="color:#a6e22e">Buffer</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">NewStdio</span>(<span style="color:#66d9ef">nil</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">stdout</span>, <span style="color:#66d9ef">nil</span>))
</span></span><span style="display:flex;"><span><span style="color:#75715e">// stdout.String == &#34;hello mvm\n&#34;</span></span></span></code></pre></div>
<p>The Go script itself if simple. It prints the first argument. The <code>fmt.Printf</code>
is a normal Go standard library function, which is called. The <code>mvm</code>
integration passes the arguments as <code>os.Args</code> into the script. Which looks like
any normal go binary. How cool is that? Additionally as <code>Build</code> method
implements a <a href="https://codeberg.org/vyskocilm/gio/src/branch/main/docs/adrs/GIO-001-unix-filter-interface.md#builder-pattern">Builder
pattern</a>
other things like environment variables or working dir can be passed to the
script too.</p>
<p>An another really cool feature is the fact <code>mvm.sh</code> itself provides two flavors
of a standard library. Those are</p>
<ul>
<li><a href="https://pkg.go.dev/github.com/mvm-sh/mvm@v0.4.2/stdlib/core">core</a></li>
<li><a href="https://pkg.go.dev/github.com/mvm-sh/mvm@v0.4.2/stdlib/all">all</a></li>
</ul>
<p>The same split is in the wrapper. The <code>core.Filter</code> type imports the subset of
library that dooes not allow code to open any other communication channel
besides passes stdio. No writing to files, no network connections, nothing is
allowed. This makes it a <em>perfect</em> match for a typical Unix command. And
untrusted code.</p>
<blockquote>
<p>With the unsafe package there are no guarantees.</p>
</blockquote>
<p>Additionally <code>core.Filter</code> type does not enable <code>unsafe</code> and <code>sync/atomic</code>
packages to be imported. The typical unix command does not need it, so this
keeps the type even more secure.</p>
<p>The other flavor, <code>all.Filter</code>, has an access to the full standard library and
can bridge additional symbols from 3rd packages, as long as if they&rsquo;re imported
from the main binary. All external dependencies, which would require
donwloading from <code>GOPROXY</code> or reading a go module cache are not available
though.</p>
<p>While <code>mvm.sh</code> supports that, this is out of scope of what a scripting
should do.</p>
<h2 id="integration-shims">Integration shims</h2>
<p>Lets briefly discuss about the integration. As <code>mvm</code> allows to shim every Go
symbol, <code>gonix</code> integration do exactly that. Following things are always
available and can&rsquo;t be excluded or changed. The <code>unix.FilterBuilderFunc</code>
implemented by <code>Builder</code> has all necessary data via arguments and
<code>unix.ShellContextConfig</code>. So shims available to the scripts are</p>
<ul>
<li><code>os.Args</code>:    for receiving command-line arguments</li>
<li><code>os.LookupEnv</code>, <code>os.Getenv</code> and <code>os.Environ</code>: for accessing environment variables</li>
<li><code>os.Getwd</code>:   for access the working directory</li>
<li><code>os.Exit</code>:    virtualized by mvm.sh (filters cannot terminate the host
process directly)</li>
</ul>
<p>Additionally I wanted to ensure the called script has an access to passed
context of <code>Run</code> method. Initially I wanted to shim <code>context.Background()</code>,
however the <a href="https://pkg.go.dev/context#Background">documentation</a> is pretty clear here.</p>
<blockquote>
<p>It is never canceled, has no values, and has no deadline</p>
</blockquote>
<p>And passing non-empty context is a violation. An another Go proverb says <em>Clear is better
than clever</em>. The calling context is available as <code>gonix.Context()</code>. So the
script has a full access to the proper context and can act accordingly.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// &#34;codeberg.org/gonix/mvm/core&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// &#34;codeberg.org/gonix/gio/unix&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">code</span> = <span style="color:#e6db74">`
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">package main
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">import &#34;fmt&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">import &#34;gonix&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">func main() {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">	ctx := gonix.Context()
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">	x := ctx.Value(&#34;key&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">	fmt.Println(x.(string))
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">	&lt;-ctx.Done()
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">	fmt.Println(ctx.Err())
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">}`</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">b</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">core</span>.<span style="color:#a6e22e">NewBuilder</span>(<span style="color:#a6e22e">code</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">f</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Build</span>(<span style="color:#e6db74">&#34;test&#34;</span>, []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;mvm&#34;</span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">stdout</span> <span style="color:#a6e22e">bytes</span>.<span style="color:#a6e22e">Buffer</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ctx</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithValue</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#e6db74">&#34;key&#34;</span>, <span style="color:#e6db74">&#34;value&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithDeadline</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#ae81ff">100</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">NewStdio</span>(<span style="color:#66d9ef">nil</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">stdout</span>, <span style="color:#66d9ef">nil</span>))
</span></span><span style="display:flex;"><span><span style="color:#75715e">// stdout.String == &#34;value\ncontext deadline exceeded\n&#34;</span></span></span></code></pre></div>
<h2 id="shell-functions">Shell functions</h2>
<p>There is only one problem with such integration. While it replaces an
individual commands, is not a replacement for shell <em>functions</em>. This is pretty
interesting topic on its own I want to explore further.</p>
<p>The idea I have is roughly</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// funcs.go - go functions available in a shell</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">HelloWorld</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;hello, world&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">HelloArgs</span>(<span style="color:#a6e22e">args</span> <span style="color:#f92672">...</span><span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%+v&#34;</span>, <span style="color:#a6e22e">args</span><span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">HelloContext</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">args</span> <span style="color:#f92672">...</span><span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">lookupEnv</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gonix</span>.<span style="color:#a6e22e">LookupEnvFunc</span>(<span style="color:#a6e22e">ctx</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">args</span>) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">v</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">lookupEnv</span>(<span style="color:#a6e22e">args</span>[<span style="color:#ae81ff">1</span>])
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;%s=(%s, %t)\n&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">args</span>[<span style="color:#ae81ff">1</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">v</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ok</span>,
</span></span><span style="display:flex;"><span>  )
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>And with an appropriate magic those can be made available for <code>sh</code> and used</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>source-go ./funcs.go
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>hello_world
</span></span><span style="display:flex;"><span>hello_args <span style="color:#e6db74">&#34;Hi&#34;</span> <span style="color:#e6db74">&#34;shell&#34;</span> <span style="color:#e6db74">&#34;in&#34;</span> <span style="color:#e6db74">&#34;go&#34;</span>
</span></span><span style="display:flex;"><span>FOO<span style="color:#f92672">=</span>BAR hello_context <span style="color:#e6db74">&#34;FOO&#34;</span></span></span></code></pre></div>
<h2 id="on-txtar">On txtar</h2>
<p>One advantage of shell scripts is those are a single file. This is broken by
having parts written in Go. Fortunately Go itself has a solution <a href="https://pkg.go.dev/golang.org/x/tools/txtar">txtar
format</a>. This is trivial to write
and to parse format which can contain two different languages in a single file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>#!/bin/gonix-sh
</span></span><span style="display:flex;"><span># A showcase of gonix and shell integration
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>hello &#34;mvm&#34;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>-- hello --
</span></span><span style="display:flex;"><span>package main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>import &#34;fmt&#34;
</span></span><span style="display:flex;"><span>import &#34;os&#34;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>func main() {
</span></span><span style="display:flex;"><span>  fmt.Println(&#34;hello &#34; + os.Args[1])
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Which will output <code>hello mvm</code>.</p>
<h1 id="conclusion">Conclusion</h1>
<p>This addition goes the full circle - all the layers like compiled utilities,
shell, monitoring thing, the wasm integration and now the scripted builtins -
it is Go all the way round. I am curious how well the shell function
integration and txtar format support will go.</p>
]]></content:encoded></item><item><title>Gonix: The Sixth Element</title><link>https://vyskocil.me/blog/gonix-the-sixth-element/</link><pubDate>Sun, 31 May 2026 20:43:09 +0200</pubDate><guid>https://vyskocil.me/blog/gonix-the-sixth-element/</guid><description>Unix is not just pipes and text streams. This is why I introduced a kernel like element into gonix: unix from userspace. To allow the system become close to the real Unix shell.&amp;#xA;Beyond running cat | wc -l user will naturally ask&amp;#xA;How many processes are running? What are their name …</description><content:encoded><![CDATA[<p>Unix is not just pipes and text streams. This is why I introduced a kernel like
element into <a href="/blog/gonix-unix-from-userspace/">gonix: unix from userspace</a>. To allow the system become close to
the real Unix shell.</p>
<p>Beyond running <code>cat | wc -l</code> user will naturally ask</p>
<ol>
<li>How many processes are running?</li>
<li>What are their name and arguments?</li>
<li>Can I stop a process when running?</li>
<li>Can I pause it?</li>
<li>And more.</li>
</ol>
<h2 id="a-monitor">A Monitor</h2>
<p>The real Unix is an operating system. This means it has a kernel and it
has superpowers that normal user-space code does not and cannot have. At
the same time it is the kernel which is able to answer all the question above.</p>
<p>Welcome the fift^W sixth element of a <a href="https://codeberg.org/gonix">gonix</a>
project. The <a href="https://codeberg.org/gonix/monitor">monitor</a>. While it can&rsquo;t run
or interrupt anything, it can track filters or it can facilitate sending the
information to the filter. It can even stop or pause and resume well
<em>cooperating</em> filters. So I&rsquo;ve chosen the name <code>monitor</code>.</p>
<h2 id="how-to-terminate-or-pause-go-code">How to terminate or pause Go code?</h2>
<p>The real kernel like unix can setup a signal handler of a process and on a
signal delivery simply change the program counter of the processor to the
address of the handler. The userspace has no say here and code of an interrupt
handler is executed.</p>
<p>However Go itself has two concepts, which can map to the unix signals just well.</p>
<ol>
<li><strong>Context cancellation</strong> - This tells the well behaving Go code to stop whatever
is doing and return early. Which can emulate <code>SIGKILL</code> and <code>SIGTERM</code>.</li>
<li><code>sync.Cond</code> type which is good enough to emulate <code>SIGSTOP</code> and <code>SIGCONT</code>.</li>
</ol>
<p>Both concepts are implemented via wrappers like
<a href="https://codeberg.org/gonix/gio/src/commit/da7df72abfb89be401001db89c47b4f075a5832d/context.go#L28">gio.NewContextReader</a>
and once filter&rsquo;s standard io is wrapped, then filter supports the semantics of
those signals well. Because most Unix filters are designed around I/O, this
simple trick usually works in most situations. You don&rsquo;t have to add this
support to each filter code.</p>
<p>That code is surprisingly short and easy to understand. The only thing to keep
in mind is that the function <code>wait()</code> must return once the <code>r.ctx</code> has been
canceled. However, it is up to the caller of this wrapper.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Read implements Reader[T any] interface</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#a6e22e">ContextReader</span>[<span style="color:#a6e22e">T</span>]) <span style="color:#a6e22e">Read</span>(<span style="color:#a6e22e">p</span> []<span style="color:#a6e22e">T</span>) (<span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">wait</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">wait</span>()
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ctxErr</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">ctx</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Read</span>(<span style="color:#a6e22e">p</span>)
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="signal-delivery">Signal delivery</h2>
<p>As it was mentioned in previous paragraph, Go code can <em>emulate</em>
the behavior of a Unix signals almost perfectly. However the Go solution will use <code>channel</code>,
<code>select</code>, and <code>goroutines</code>. Even if it can implement the semantics of Unix
signals, the delivery mechanism will be different.</p>
<p>This is the reason the solution is called like this.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Pulser</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Pulse</span>() <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>The <em>pulse</em> is a concept from a <a href="https://www.qnx.com/developers/docs/8.0/com.qnx.doc.neutrino.sys_arch/topic/ipc_Pulses.html">QNX
Neutrino</a>
that involves an asynchronous message passing between userspace and a kernel.
And asynchronous message passing in Go is idiomatic and native, making this a
perfect fit.</p>
<p>With than in mind defining a Unix signal equivalent is easy.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">sigkill</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">signal</span>{<span style="color:#a6e22e">s</span>: <span style="color:#e6db74">&#34;killed&#34;</span>}
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">sigstop</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">signal</span>{<span style="color:#a6e22e">s</span>: <span style="color:#e6db74">&#34;stopped&#34;</span>}
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">sigcont</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">signal</span>{<span style="color:#a6e22e">s</span>: <span style="color:#e6db74">&#34;continued&#34;</span>}
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">sigterm</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">signal</span>{<span style="color:#a6e22e">s</span>: <span style="color:#e6db74">&#34;terminated&#34;</span>}
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">signal</span> <span style="color:#66d9ef">struct</span> {<span style="color:#a6e22e">s</span> <span style="color:#66d9ef">string</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">signal</span>) <span style="color:#a6e22e">Pulse</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">s</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Each wrapper of a <code>unix.Filter</code> has an internal channel for pulses, so all of
them can receive them. This method guarantee that he pulse is sent to filter if
it is running. Technically this spawns a goroutine for a delivery if the
underlying channel is full. So it is not a good idea to send a lot of pulses or
write the code which handles them slowly.</p>
<p>This is good enough for mechanism like signals. If you need a strong way to
communicate, it&rsquo;s better to use different Go mechanisms. It&rsquo;s all just a user
space anyway.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Send sends a Pulser to the filter channel. The delivery is guaranteed and if</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// the filter code is slow in a consuming, new goroutines are spawned to ensure</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// the delivery. It is never a good idea to send too many pulses or have a slow</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// pulse handling.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">f</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">filter</span>) <span style="color:#a6e22e">Send</span>(<span style="color:#a6e22e">signal</span> <span style="color:#a6e22e">Pulser</span>)</span></span></code></pre></div>
<h3 id="unmaskable-signals">Unmaskable signals</h3>
<p>Unix defines three important signals <code>SIGKILL</code>, <code>SIGSTOP</code> and <code>SIGCONT</code>. Those
are never send to the process so it can never handle them. In the Unix terminology
it can never <em>mask</em> them.</p>
<p><code>monitor</code> defines their equivalents:</p>
<ol>
<li><code>Sigkill()</code> is an equivalent of <code>SIGKILL</code> - means hard stop, never forwarded.</li>
<li><code>Sigstop()</code> is an equivalent of <code>SIGSTOP</code> - this uses <code>sync.Cond</code> to stop the execution until &hellip;</li>
<li><code>Sigcont()</code> pulse is delivered, this is an equivalent of <code>SIGCONT</code> and causes the code to resume</li>
</ol>
<p>Once those pulses are delivered, they are handled inside the wrapper.</p>
<h2 id="maskable-signals-and-mailbox">Maskable signals and mailbox</h2>
<p>The <code>Sigterm()</code> pulse is an equivalent to <code>SIGTERM</code>. It causes the filter&rsquo;s
context to be canceled. However the filter code can opt-in to the delivery. In
this case <code>monitor</code> will forward it to <strong>mailbox</strong>. Which is the term borrowed
from Erlang and acts as an asynchronous message queue.</p>
<p>In a case of <code>gonix</code> this is a <code>channel</code>, which makes the pulse-aware filter code looking like</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">handle</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">monitor</span>.<span style="color:#a6e22e">Self</span>(<span style="color:#a6e22e">ctx</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">mbox</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">handle</span>.<span style="color:#a6e22e">Mailbox</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Err</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">pulse</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">mbox</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">pulse</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">monitor</span>.<span style="color:#a6e22e">Sigterm</span>():
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">stdio</span>.<span style="color:#a6e22e">Stderr</span>(), <span style="color:#e6db74">&#34;%s\n&#34;</span>, <span style="color:#a6e22e">pulse</span>.<span style="color:#a6e22e">Pulse</span>())
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">stdio</span>.<span style="color:#a6e22e">Stdout</span>(), <span style="color:#e6db74">&#34;%s\n&#34;</span>, <span style="color:#a6e22e">pulse</span>.<span style="color:#a6e22e">Pulse</span>())
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="os-level-signals">OS Level signals</h2>
<p>One cannot expect everything to be available as a native Go code. So <code>gio</code> has a <a href="https://codeberg.org/gonix/gio/src/commit/da7df72abfb89be401001db89c47b4f075a5832d/unix/exec.go#L27">support</a> for a real
<code>execve</code> and real OS processes via a <a href="https://codeberg.org/gonix/gio/src/commit/da7df72abfb89be401001db89c47b4f075a5832d/unix/exec.go#L14">Cmd
wrapper</a>.
This integrates real processes into the project, except for pulses. Those are <code>gonix</code>&rsquo;s own userspace
abstractions, which has nothing to do with the operating system.</p>
<p>In order to solve this, the wrapper expose the fact that it can receive the OS level
signals via
<a href="https://codeberg.org/gonix/gio/src/commit/da7df72abfb89be401001db89c47b4f075a5832d/unix/exec.go#L80">Signal</a>
method. This allows the <code>monitor</code> to translate the pulses into real signals. Each
pulse can define a method called
<a href="https://codeberg.org/gonix/monitor/src/commit/d043a8bdd01e0c1373f17538e8c998f3eb2aa651/os.go#L18">Signal</a>
which translates the pulse into OS level counterpart if possible. The design
is flexible enough that user can define own pulses and their rules how to
translate them into to real OS signals.</p>
<h2 id="spawn">Spawn</h2>
<p>Spawning a filter in a <code>gonix</code> project may seem like a convoluted three-step
process at first. There is no equivalent of <code>CreateProcess</code> or <code>fork+execve</code>,
and the process may be a bit tedious at first. However, this
complex^Wflexibility will soon pay off.</p>
<ol>
<li>The <a href="https://codeberg.org/gonix/gio/src/commit/da7df72abfb89be401001db89c47b4f075a5832d/unix/builder.go#L36">Lookup</a> operation maps the command name in a script to the &hellip;</li>
<li>the <a href="https://codeberg.org/gonix/gio/src/commit/da7df72abfb89be401001db89c47b4f075a5832d/unix/builder.go#L33">Builder</a> which injects the command name and arguments and shell option to the &hellip;</li>
<li>correct <a href="https://codeberg.org/gonix/gio/src/commit/da7df72abfb89be401001db89c47b4f075a5832d/unix/unix.go#L57">Filter</a></li>
</ol>
<p>This design allows the caller to precisely determine and control the state of the
sub-filter. Most importantly, it enables a seamless integration
between the various components of the <code>gonix</code> stack.</p>
<p>For a monitor to perform its monitoring function, it must run a dedicated
wrapper that provides all the needed functionality. The exposed <code>Lookup</code> method
does exactly this. It ensures what that caller get the builder function which
will run the properly wrapped and tracked filter.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// codeberg.org/gonix/utils/xargs</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// codeberg.org/gonix/sh</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// codeberg.org/gonix/monitor</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// unix.FilterLookupMap provides compatible FilterLookupFunc</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// wrapping the go map - this is handy for examples or tests</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">binaries</span> <span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">FilterLookupMap</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">m</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">monitor</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">binaries</span>.<span style="color:#a6e22e">Lookup</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// integrating with xargs-like tool</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">XargsNew</span>(<span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">FilterLookupFunction</span>) <span style="color:#a6e22e">Xargs</span> {}
</span></span><span style="display:flex;"><span><span style="color:#75715e">// integration with a sh</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewFilters</span>(<span style="color:#a6e22e">filters</span> <span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">FilterLookupFunc</span>, 
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">opts</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">ShellContextOption</span>) <span style="color:#a6e22e">Filters</span> {}</span></span></code></pre></div>
<p>The beauty of this design is that the <code>utils</code>, <code>sh</code>, and <code>monitor</code> do not
depend on each other. The only one dependency is on <code>gio</code> itself. Combined
together, they provide a fully user-space Unix scripting with signals and native
<code>xargs</code> for <code>gonix</code>.</p>
<p>Additionally the caller has a full control over how it will execute the children.</p>
<ol>
<li><code>Run</code> and exit? This is not a problem as <code>gonix</code> never reassigns the parent id,
does not expect parent to call <code>waitpid</code>.</li>
<li><code>Run</code> and <code>wait</code>? No problem.</li>
<li><code>Run</code> concurrently? No problem. Use <code>sync.Waitgroup</code>, Luke.</li>
<li><code>Run</code> concurrently and cancel on the first failure? No problem. Use <code>errgroup.Group</code>, Anakin.</li>
</ol>
<h2 id="final-thoughts">Final thoughts</h2>
<p>This is a huge milestone for me. It&rsquo;s been almost four years since I&rsquo;ve been
watching the <a href="https://youtu.be/XvDZLjaCJuw?t=786">Dennis Ritchie</a> and
experimenting with Unix pipeline and fully userspace Unix tools. I&rsquo;ve never had
a <em>side</em> project like this. While the original idea was simple, it led to
one or more new ideas each time.</p>
<ul>
<li>How hard would it be to write a unix tool (<code>utils</code> repository)?</li>
<li>There is <code>goawk</code>, how hard it would be write a <code>cat</code> on top of it?</li>
<li>Having a unified base would be nice (<code>gio</code>).</li>
<li>There is <code>sh</code> in pure Go, let&rsquo;s integrate it.</li>
<li>There are Go native unix tools, let&rsquo;s integrate the <code>u-root</code> project.</li>
<li>Rust coreutils has WebAssembly build, let&rsquo;s integrate them.</li>
<li>The tools needs a working directory and env variables, figure out.</li>
<li>I would want to stop or pause the filter, welcome <code>monitor</code> and pulses.</li>
<li>I would want to know how many were started, how many are in progress and so on.</li>
</ul>
<p>I can&rsquo;t believe that the entire project has less than 10,000 (9657 accroding
<code>tokei</code>) lines of code. Yet, by integrating excellent third party packages, it
can do so much. I am convinced this stance is important especially in days of
agentic coding - everyone with an agent would be able to generate 10000, 50,000
or 100,000 lines in no time. We, the humans, can compete only on a metric of
less and better designed code.</p>
<p>This minimalism is reflected in the project layout itself. Each repository does
one thing. <code>gio</code> itself is so fundamental base of the project <a href="https://codeberg.org/gonix/gio/commit/fec06d1e8ffae431edc9f48223d7f2e1adbe533a">I dropped even
testify from its
dependencies</a>
from it. Only standard library is allowed here. Similarly, the dependency
chains are kept minimal. Integration tests, like the one integrating <code>sh</code>,
<code>utils/xargs</code> and <code>monitor</code>, are in their specific go packages with own
<code>go.mod</code>. This way the test dependencies do not pollute the main module. At the
same time I refused to better error library to <code>monitor</code>. All popular packages
felt extra heavy given the little of a functionality I really needed.</p>
<h2 id="to-ai-or-not-to-ai">To AI or not to AI?</h2>
<p>It is 2026! This topic must be made clear. I wrote every production line of
code by myself. At the same time, I must admit that LLMs were useful when
working on this project. Given the little time I had, I would never been able
to finish it so fast. So I took this as a good opportunity to <em>learn</em> how to
use the LLMs. So far, I have found the chats and agents good at</p>
<ol>
<li>In providing code samples. For example getting code examples for <code>wazero</code>.</li>
<li>In generating unit test cases, especially bug reproducers.</li>
<li>In research and helping me to shape my vague ideas - similarly to rubber
duck debugging.</li>
<li>In suggesting me the Pulse and QNX naming, as well as Mailbox and Erlang, or
discovering <code>uutils</code> has web assembly build.</li>
<li>In generating a lot of plausible sounding overly complicated designs I was
able to either reject or simplify.</li>
<li>In helping me to debug bugs, crashes and deadlocks I had.</li>
<li>In keeping the track about the state of the project.</li>
</ol>
<p>I haven&rsquo;t found it a good tool for <em>writing</em> a code, though. Using it on this
project has actually made me more skeptical. If you <em>care</em> about the code, you
absolutely must write it yourself. LLMs, especially agents, <em>can</em> help you with
everything else. But a machine cannot replace the human understanding. The more
you know, the better the LLMs become as tools for you.</p>
<p>Writing the code is the best way how to develop a deep understanding.</p>
<h2 id="logo">Logo</h2>
<p>Legs, beak and colors from <a href="https://freesvg.org/confused-tux-vector-image">Confused Tux (Public Domain)</a>
mixed into
<a href="https://vyskocil.me/blog/gonix-unix-from-userspace/gonix.svg">gonix.svg</a>.</p>
]]></content:encoded></item><item><title>Gonix: Unix From Userspace</title><link>https://vyskocil.me/blog/gonix-unix-from-userspace/</link><pubDate>Fri, 24 Apr 2026 14:21:36 +0200</pubDate><guid>https://vyskocil.me/blog/gonix-unix-from-userspace/</guid><description>A while back, I watched the video where Dennis Ritchie was showing Unix command line. And I though to myself.&amp;#xA;How hard would it be to replicate the unix pipe in Go?&amp;#xA;Given the fact I began experimenting 4 years ago, it might be tempting to say yes. It is hard! However, …</description><content:encoded><![CDATA[<p>A while back, I watched the <a href="https://youtu.be/XvDZLjaCJuw?t=786">video where Dennis Ritchie was showing Unix command
line</a>. And I though to myself.</p>
<blockquote>
<p>How hard would it be to replicate the unix pipe in Go?</p>
</blockquote>
<p>Given the fact I began experimenting 4 years ago, it might be tempting to say
yes. It is hard!  However, implementing a Unix command and a pipe itself is
trivial in Go. All the necessary ingredients are already present in a standard
library.</p>
<ul>
<li><code>pipe</code> is <code>io.Pipe</code></li>
<li>the producer and consumer runs concurrently and Go has <code>goroutines</code></li>
</ul>
<h2 id="tldr">TL;DR;</h2>
<p>Let me introduce the project quicky.
<a href="https://codeberg.org/gonix">https://codeberg.org/gonix</a> provides a Unix
utilities and scripts as a (Go) programming library. Since the utilities and
shell interpreter itself themselves are Go - there&rsquo;s no <code>fork+execve</code>, no extra
syscalls, it is all standard programming in userspace. And thanks to people
working on reimplementation of <a href="https://www.gnu.org/software/coreutils/">GNU core
utilities</a> to
<a href="https://github.com/uutils/coreutils">Rust</a> GNU compatible coreutils are
available right now.</p>
<p>Consider this trivial example of the script.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>&gt; gonix/cat /etc/passwd | rust/wc -l
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">42</span></span></span></code></pre></div>
<p>While it does look like one and behave like a script, it is all pure Go all the way down.</p>
<ol>
<li><a href="https://github.com/mvdan/sh">https://github.com/mvdan/sh</a> implements the <code>shell</code> itself.</li>
<li><code>cat</code> is implemented by a gonix project itself.
<a href="https://codeberg.org/gonix/gonix/src/branch/main/cat">https://codeberg.org/gonix/gonix/src/branch/main/cat</a></li>
<li><code>wc</code> uses WebAssembly build of <a href="https://github.com/uutils/coreutils">https://github.com/uutils/coreutils</a> project.</li>
</ol>
<p>The names <code>gonix/cat</code> and <code>rust/wc</code> are <em>arbitrary</em> - scripts can be called by any name and
these names has been chosen in order to showcase the fact this is not a script in a traditional sense.</p>
<h2 id="motivation">Motivation</h2>
<p>Despite using Linux for almost two decades, I have never liked the strict separation
between scripting and a programming. There is no <code>libgrep</code> or <code>libsed</code> or <code>libawk</code> that combines
the higher level tools with a proper programming environment. Projects like <code>libcurl/curl</code>
do exist though. Even then - if a script solves the problem by calling a <code>curl</code>, the solution
must be written again using interfaces tailored to <code>C</code> or other real programming language.</p>
<p>So while command line tools and scripting languages themselves are written in C
(or Go or Rust or Python or Node). Consuming those back as a software package
is awkward (see <code>gpgme</code> as an infamous example). This project is an attempt to
merge both worlds together and keep scripting useful part and integrated part
of a programming.</p>
<p>The <code>cat | wc</code> example above can be implemented using a few lines of Go and
all runs <em>in userspace</em> inside the <em>same</em> process without a single additional <code>fork+exec</code> in sight.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// register commands</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">cmds</span> = <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">FilterBuilderFunc</span>{
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;gonix/cat&#34;</span>: <span style="color:#a6e22e">cat</span>.<span style="color:#a6e22e">FromArgs</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;rust/wc&#34;</span>:  <span style="color:#a6e22e">coreutils</span>.<span style="color:#a6e22e">Wc</span>(),
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">// parse the script</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">file</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">syntax</span>.<span style="color:#a6e22e">NewParser</span>().
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Parse</span>(<span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">NewReader</span>(<span style="color:#a6e22e">script</span>), <span style="color:#e6db74">&#34;gonix/cat ${FILE} | rust/wc&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">runner</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">interp</span>.<span style="color:#a6e22e">New</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">interp</span>.<span style="color:#a6e22e">Env</span>(<span style="color:#a6e22e">expand</span>.<span style="color:#a6e22e">ListEnviron</span>(<span style="color:#e6db74">&#34;FILE=test.me&#34;</span>)),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">interp</span>.<span style="color:#a6e22e">StdIO</span>(<span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdout</span>, <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdout</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">interp</span>.<span style="color:#a6e22e">ExecHandlers</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">ExecHandler</span>),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>) = <span style="color:#a6e22e">runner</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#a6e22e">file</span>)</span></span></code></pre></div>
<h2 id="shoulders-of-giants">Shoulders of giants</h2>
<p>The most fascinating aspect of this project is how little code I needed to
write. It is also impressive how much I can rely on work of others. The whole
entire project contains less <strong>10,500 lines</strong> of a code. This is a cool metric,
especially in this AI agentic era.</p>
<p>This project would not be possible without the creators of Unix and Go and</p>
<ul>
<li><a href="https://github.com/mvdan/sh">https://github.com/mvdan/sh</a> for an excellent implementation of a shell for Go</li>
<li><a href="https://github.com/benhoyt/goawk">https://github.com/benhoyt/goawk</a> for amazing implementation of awk. The <a href="https://codeberg.org/gonix/gonix/src/branch/main/cat">gonix implementation of cat</a> is implemented as awk scripts.</li>
<li><a href="https://pkg.go.dev/github.com/tetratelabs/wazero">https://pkg.go.dev/github.com/tetratelabs/wazero</a> which allowed me to use</li>
<li><a href="https://github.com/uutils/coreutils">https://github.com/uutils/coreutils</a> despite being written in Rust</li>
<li><a href="https://github.com/u-root/u-root">https://github.com/u-root/u-root</a> project for having a native implementation of some utilites too.</li>
</ul>
<h2 id="the-unix-architecture">The unix architecture</h2>
<p>Consider this very simple Unix shell pipeline.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>&gt; cat /etc/passwd | wc -l
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">42</span></span></span></code></pre></div>
<p>In a traditional unix architecture there are many components, which must be there for this to work.</p>
<ol>
<li>unix kernel</li>
<li><code>sh</code>, <code>cat</code> and <code>wc</code> binaries installed</li>
<li>kernel starts the <code>sh</code> process</li>
<li>sh must <code>open(2)</code> and <code>read(2)</code> the script</li>
<li>sh must call <code>pipe(2)</code> to create a pipe</li>
<li>sh must call <code>fork(2)</code> for each sub-process</li>
<li>sh must call <code>dup(2)</code> connecting the <code>stdout</code> of <code>cat</code> with <code>stdin</code> of <code>wc</code></li>
<li>sh must call <code>execve(2)</code> for both subprocesses</li>
<li>sh calls <code>waitpid(2)</code> to get a report from kernel when and how tools
ended</li>
<li>sh can finally <code>exit(2)</code> and report the status</li>
</ol>
<p>Do not forget this is simplified version of all the work computer does. It
ignores stuff like signal handling and all other stuff, dynamic linking,
locales, memory calls like <code>brk</code> and <code>mmap</code>, opening locale and other files and
so on. The GNU userspace executed as <code>sh test.sh</code> ran <code>567</code> syscalls.</p>
<p>Additionally, a script will only work if these are installed on the target
system. This can become a problem if</p>
<ul>
<li>You cannot modify the system.</li>
<li>The required tool is not easily available.</li>
<li>Your system don&rsquo;t have latest GNU utilities and all it has are outdated unix tools.</li>
<li>your system is alien enough, that even if a Unix environment has been ported, this
will never feel native.</li>
</ul>
<p>With gonix shell scripting becomes 100% portable.</p>
<h2 id="gonix-architecture">Gonix architecture</h2>
<p>The core of unix command is the standard io. The C abstraction is called a
<em>file descriptor</em> and is represented by a type <code>int</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">/* #include&lt;unistd.h&gt; */</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/* File number of stdin; 0. */</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#define STDIN_FILENO 0
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">/* File number of stdout; 1. */</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#define STDOUT_FILENO 1
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">/* File number of stderr; 2. */</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#define STDERR_FILENO 2</span></span></span></code></pre></div>
<p>This concept can be easily expressed in Go.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">StandardIO</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Stdin</span>() <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">Reader</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Stdout</span>() <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">Writer</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Stderr</span>() <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">Writer</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Which is exactly what
<a href="https://codeberg.org/gonix/gio/src/commit/5177420949c5aba0f4aa08fb98c4eaf9dd6f389d/unix/unix.go#L17-L22">gio/unix</a>
does.</p>
<p>And while each C unix tool starts with a <code>main</code> function</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;unistd.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">write</span>(STDOUT_FILENO, <span style="color:#e6db74">&#34;stdout</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>, <span style="color:#a6e22e">strlen</span>(<span style="color:#e6db74">&#34;stdout</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>));
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">write</span>(STDERR_FILENO, <span style="color:#e6db74">&#34;stderr</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>, <span style="color:#a6e22e">strlen</span>(<span style="color:#e6db74">&#34;stderr</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>));
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>For our purpose its better to define <a href="https://codeberg.org/gonix/gio/src/commit/5177420949c5aba0f4aa08fb98c4eaf9dd6f389d/unix/unix.go#L25-L27">an
interface</a></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Filter</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">StandardIO</span>) <span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>and implement it</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Demo</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">Demo</span>) <span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">_</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">stdio</span> <span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">StandardIO</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">stdio</span>.<span style="color:#a6e22e">Stdout</span>(), <span style="color:#e6db74">&#34;stdout\n&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">stdio</span>.<span style="color:#a6e22e">Stderr</span>(), <span style="color:#e6db74">&#34;stderr\n&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="example-cat">Example: cat</h2>
<p>The <code>cat</code> without any arguments is literary an one-liner with <code>io.Copy</code> doing all the work.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// import codeberg.org/gonix/gio/unix</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">Cat</span>) <span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">_</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">stdio</span> <span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">StandardIO</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">Copy</span>(<span style="color:#a6e22e">stdio</span>.<span style="color:#a6e22e">Stdout</span>(), <span style="color:#a6e22e">stdio</span>.<span style="color:#a6e22e">Stdin</span>())
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="example-wc--l">Example: wc -l</h2>
<p>The <code>wc -l</code> is a bit more lines, but still not very complicated.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// import codeberg.org/gonix/gio/unix</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">Lines</span>) <span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">_</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">stdio</span> <span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">StandardIO</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">count</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">scanner</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bufio</span>.<span style="color:#a6e22e">NewScanner</span>(<span style="color:#a6e22e">stdio</span>.<span style="color:#a6e22e">Stdin</span>())
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">scanner</span>.<span style="color:#a6e22e">Scan</span>() {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">count</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">scanner</span>.<span style="color:#a6e22e">Err</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">stdio</span>.<span style="color:#a6e22e">Stdout</span>(), <span style="color:#e6db74">&#34;%d\n&#34;</span>, <span style="color:#a6e22e">count</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="command-line-arguments">Command line arguments</h2>
<p><code>Run</code> and <code>unix.Filter</code> interface can satisfy the easiest tools without a
command line arguments or any notion of an outer context like working
directory. The typical Unix tool expects and receives command line arguments
when executed. This is solved by <a href="https://codeberg.org/gonix/gio/src/commit/252204530e2b3156df2818cb036dea7b9a533c23/unix/builder.go#L31-L33">filter builder
pattern</a></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">command</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">args</span> []<span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">opts</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">ShellContextOption</span>) (<span style="color:#a6e22e">Filter</span>, <span style="color:#66d9ef">error</span>)</span></span></code></pre></div>
<p>What way implementation can get more context than context and standard io only.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">CatWithArgs</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">command</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">args</span> []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// import codeberg.org/gonix/gio/unix</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">CatWithArgs</span>) <span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">_</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">stdio</span> <span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">StandardIO</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">parse</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">args</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;%s: %w&#34;</span>, <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">command</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">Copy</span>(<span style="color:#a6e22e">stdio</span>.<span style="color:#a6e22e">Stdout</span>(), <span style="color:#a6e22e">stdio</span>.<span style="color:#a6e22e">Stdin</span>())
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="the-shell-context">The shell context</h2>
<p>Let&rsquo;s consider this example</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>FOO<span style="color:#f92672">=</span><span style="color:#ae81ff">42</span> printenv</span></span></code></pre></div>
<p>Unix tools expects environment variables too. This is achieved by <code>opts ...ShellContextOption</code> of a builder. Every tool can then get proper functions
which can be used to get an access. The key design concern here is to decouple
the interface for such functionality with an actual implementation</p>
<ol>
<li>interface is defined in <code>gio/unix</code></li>
<li>injection is a part of <code>unix.FilterBuilderFunc</code></li>
<li>actual implementation if done in <code>sh</code> package for <code>mvdan.cc/sh/v3</code>.</li>
</ol>
<p>Tools can register those helpers to obtain enough from the context as seen in a following example.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">b</span> <span style="color:#a6e22e">uroot</span>.<span style="color:#a6e22e">Builder</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">builders</span> = <span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">FilterLookupMap</span>{
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;mktemp&#34;</span>: <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Mktemp</span>(),
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">filters</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">sh</span>.<span style="color:#a6e22e">NewFilters</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">builders</span>.<span style="color:#a6e22e">Lookup</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">unix</span>.<span style="color:#a6e22e">WithLookupEnvFunc</span>(<span style="color:#a6e22e">sh</span>.<span style="color:#a6e22e">LookupEnvFunc</span>),
</span></span><span style="display:flex;"><span>  )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">source</span> = <span style="color:#e6db74">&#34;TMPDIR=/tmp42 mktemp -d t.XXXXXX&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">parsed</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">syntax</span>.<span style="color:#a6e22e">NewParser</span>().
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Parse</span>(<span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">NewReader</span>(<span style="color:#a6e22e">source</span>), <span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">require</span>.<span style="color:#a6e22e">NoError</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">got</span> <span style="color:#a6e22e">bytes</span>.<span style="color:#a6e22e">Buffer</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">runner</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">interp</span>.<span style="color:#a6e22e">New</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">interp</span>.<span style="color:#a6e22e">StdIO</span>(<span style="color:#66d9ef">nil</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">got</span>, <span style="color:#66d9ef">nil</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">interp</span>.<span style="color:#a6e22e">ExecHandlers</span>(<span style="color:#a6e22e">filters</span>.<span style="color:#a6e22e">ExecHandler</span>),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// expect error as /tmp42 does not exists</span></span></span></code></pre></div>
<p>Available options are</p>
<ol>
<li>get working directory via <code>unix.GetDirFunc</code></li>
<li>lookup environment variable via <code>unix.LookupEnvFunc</code></li>
<li>get all environment via <code>unix.GetEnvFunc</code></li>
</ol>
<h2 id="what-did-the-gonix-did-for-us">What did the gonix did for us?</h2>
<p>Nothing at the moment. However I think this can improve an existing container tooling for example.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">scratch</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># I need to create a directory</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># but can&#39;t as mkdir is not there</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">RUN</span> mkdir /app</span></span></code></pre></div>
<p>or the debugging sessions</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># go away: cannot execute /bin/ls: file not found</span>
</span></span><span style="display:flex;"><span>docker exec scratch ls -lh /app</span></span></code></pre></div>
<p>At this moment all the tools are supposed to be installed inside the system
itself. And consider a hypothetical improved tooling</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> <span style="color:#e6db74">scratch</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># mkdir is available as a part of Go runtime</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># no need to install a thing</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>GONIX rust/mkdir /app</span></span></code></pre></div>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># ls is a part of runtime, so rust/ls will work</span>
</span></span><span style="display:flex;"><span>better-docker exec --gonix <span style="color:#e6db74">&#39;rust/ls -lh /app&#39;</span> scratch</span></span></code></pre></div>
<p>The other possible use cases are</p>
<ul>
<li>And monitoring probes</li>
<li>Tools which setups systems using scripts like <code>distrobox</code></li>
<li>Experimenting with more exotic tools like those written for Plan9</li>
<li>AI companions writing and executing scripts</li>
<li>MCP server</li>
<li>And many more</li>
</ul>
<h2 id="roadmap">Roadmap?</h2>
<p>There is any as this is a project of love. However the interesting areas to consider are</p>
<ol>
<li>context aware io - at this moment individual filters much implement context
checks individually - idea here is to wrap context and io together, so Read/Write methods
will stop working once context is canceled</li>
<li>busybox-like tool which packs as much other tools as possible</li>
<li>interactive shell - being in Go it can have nice things like native fuzzy search</li>
</ol>
<h2 id="limitations">Limitations</h2>
<p>There are more aspects to a real Unix system than just pipes and environment
variables. Some are fundamental limits of how the kernel works, while others
may be implemented later. These limitations include things like</p>
<h3 id="kernel-properties">Kernel properties</h3>
<p>The project is an <em>emulation</em> of a real Unix system completely bypassing kernel
and all its features. So process isolation, process tracking, signals, control
groups, security context, SELinux labels, user and groups, extended attributes
and so on - nothing does work inside gonix.</p>
<p>Unless the <code>fork+exec</code> is involved -
<a href="unix/exec.go">https://codeberg.org/gonix/gio/src/branch/main/unix/exec.go#L30</a>
is available.</p>
<p>In other words <code>gonix</code> is <strong>not</strong> a security layer. Additionally running inside
the same OS process, you will never get SUID binaries like <code>sudo</code> working in
userspace.</p>
<h3 id="cant-execve-by-default">Can&rsquo;t execve by default</h3>
<p>Other part are unit tools calling <code>fork+exec</code> themselves like <code>xargs</code>. There is
no good solution for a <em>reverse</em> wrapper which will hijack <code>exec.Command</code> and
covert it to <code>unix.Filter</code>. So there is GNU compatible implementation of
<a href="https://codeberg.org/gonix/gonix/tree/main/xargs/">https://codeberg.org/gonix/gonix/tree/main/xargs/</a>
which gets the <code>unix.FilterBuilderFunc</code> as a parameter and can execute other
userspace tools.</p>
<p>This design allows setups like the main shell having an access to
different filters than <code>xargs</code> itself. This is how it is unit tested by the way.</p>
<h3 id="signals-and-process-tracking">Signals and process tracking</h3>
<p>While this one can be emulated, at the moment individual utilities don&rsquo;t
respect <code>context.Context</code> by default unless specified elsewhere. The design of
Go context will most likely allow emulating the signals, but this is not yet
done. The relevant part is a process tracking, this is not implemented - if
something get stuck, you must kill a whole process.</p>
<h3 id="interactivity">Interactivity</h3>
<p>While interesting, neither <code>mvdan.cc/sh/v3</code> neither the utilities are developed
and tested with interactivity in mind.</p>
<h2 id="are-there-any-other-similar-projects">Are there any other similar projects?</h2>
<p>I haven&rsquo;t searched thoroughly and those are two examples I am aware of.</p>
<p><a href="https://github.com/ewhauser/gbash">https://github.com/ewhauser/gbash</a> seems to
be the closest. However it seems to be more AI driven and I expect that most of code there is
generated.</p>
<p>The <code>gbash</code> project itself references
<a href="https://github.com/vercel-labs/just-bash">https://github.com/vercel-labs/just-bash</a>
which seems to be the same, only in Typescript.</p>
<h2 id="ai">AI</h2>
<p>While code in the project was written by me, I must admit I would not be able
to progress so quickly without a help of LLMs.</p>
<ol>
<li>Proof of concept code including wazero integration has been provided by
various AI tools as well as a few test cases.</li>
<li>The ideas behind <code>unix.ShellContextOption</code> has been investigated by Claude. It was
nice to be able to see and reject all the <em>wrong</em> solutions it produced, which helped
me to figure out the proper design.</li>
<li>Experimental <code>sbase</code> web assembly port has been vibe-coded by Claude.</li>
<li>deepl.com/write reviewed this text as usual.</li>
<li>Google AI explained me how to use Inkscape to convert the png logo into svg.</li>
</ol>
<h2 id="logo">Logo</h2>
<p>Gopher by Rene French CC license <a href="https://upload.wikimedia.org/wikipedia/commons/2/2d/Go_gopher_favicon.svg">https://upload.wikimedia.org/wikipedia/commons/2/2d/Go_gopher_favicon.svg</a> and <a href="https://www.deviantart.com/dollarakshay/art/Unix-Terminal-Logo-735161243">Unix Terminal Logo</a> <a href="https://www.deviantart.com/dollarakshay/gallery">Dollar Akshay</a></p>
]]></content:encoded></item><item><title>Going Dark</title><link>https://vyskocil.me/blog/going-dark/</link><pubDate>Sun, 22 Mar 2026 13:48:37 +0100</pubDate><guid>https://vyskocil.me/blog/going-dark/</guid><description>While writing about the responsive design of ByteKit,I wanted to demonstrate the fact it has both light and a dark themes. At this exact moment I realised that this blog itself has no dark theme. When I setup to setup Hugo for it I simply used an existing theme named hestia-pure. …</description><content:encoded><![CDATA[<p>While writing about the <a href="https://vyskocil.me/blog/bytekit-responsive/">responsive design of ByteKit</a>,I wanted to demonstrate the fact it has both <em>light</em> and a <em>dark</em>
themes. At this exact moment I realised that this blog itself has no dark theme. When I setup to setup Hugo for it I simply used an existing theme named
<a href="https://github.com/diwao/hestia-pure">hestia-pure</a>. As it didn&rsquo;t support the dark theme, neither did my blog.</p>
<p>Meanwhile, it was necessary to
<a href="https://codeberg.org/vyskocilm/miblog-hestia-pure">fork it</a> it due growing needs. Most notably it rely on SCSS, where I want CSS written by hand without any Javascript post-processing. With modernish CSS features like variables supporting the color scheme is easy and this blog is based on <a href="https://github.com/morhetz/gruvbox">gruvbox</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#75715e">/* https://github.com/morhetz/gruvbox?tab=readme-ov-file#light-mode-1 */</span>
</span></span><span style="display:flex;"><span>:<span style="color:#a6e22e">root</span> {
</span></span><span style="display:flex;"><span>  --bg: <span style="color:#ae81ff">#fbf1c7</span>;
</span></span><span style="display:flex;"><span>  --red: <span style="color:#ae81ff">#cc241d</span>;
</span></span><span style="display:flex;"><span>  --green: <span style="color:#ae81ff">#98971a</span>;
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="going-dark">Going dark</h2>
<p>With CSS variables in place, the dark theme simply redefines variables using
<code>prefers-color-scheme: dark</code> <code>@media</code> query.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-css" data-lang="css"><span style="display:flex;"><span>@<span style="color:#66d9ef">media</span> <span style="color:#f92672">(</span><span style="color:#f92672">prefers-color-scheme</span><span style="color:#f92672">:</span> <span style="color:#f92672">dark</span><span style="color:#f92672">)</span> {
</span></span><span style="display:flex;"><span>  :<span style="color:#a6e22e">root</span> {
</span></span><span style="display:flex;"><span>    --bg: <span style="color:#ae81ff">#282828</span>;
</span></span><span style="display:flex;"><span>    --red: <span style="color:#ae81ff">#cc241d</span>;
</span></span><span style="display:flex;"><span>    --green: <span style="color:#ae81ff">#98971a</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>And the page itself can advertise supported schemes and its priority.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;color-scheme&#34;</span> <span style="color:#a6e22e">content</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;light dark&#34;</span> /&gt;</span></span></code></pre></div>
<h2 id="fixing-mastodon-comments">Fixing Mastodon comments</h2>
<p>Taking an inspiration from
<a href="https://carlschwan.eu/2020/12/29/adding-comments-to-your-static-blog-with-mastodon/">https://carlswchwan.eu</a>,
I added support for Mastodon comments. The problem was that I originally reused a CSS from a different blog and didn&rsquo;t actually used site&rsquo;s CSS variables. This has been fixed and styling for comments gor merged to main <code>style.css</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#75715e">/* Mastodon comments */</span>
</span></span><span style="display:flex;"><span>#load-comment {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">background-color</span>: <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span><span style="color:#66d9ef">blue</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">color</span>: <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>bg);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">border</span>: <span style="color:#ae81ff">1</span><span style="color:#66d9ef">px</span> <span style="color:#66d9ef">solid</span> <span style="color:#a6e22e">var</span>(<span style="color:#f92672">--</span>blue12);
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="color-scheme-based-screenshots">Color scheme based screenshots</h2>
<p>One of the coolest things about this exercise is that HTML itself supports <code>@media</code> queries, making it easy to render different images based on a preferred color scheme. This means that page with a light style will display light screenshots. And vice versa.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">picture</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">source</span> <span style="color:#a6e22e">srcset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;screenshot-dark.png&#34;</span> <span style="color:#a6e22e">media</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;(prefers-color-scheme: dark)&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">source</span> <span style="color:#a6e22e">srcset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;screenshot-light.png&#34;</span> <span style="color:#a6e22e">media</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;(prefers-color-scheme: light)&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">img</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;screenshot-light.png&#34;</span> <span style="color:#a6e22e">alt</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Miblog screenshot&#34;</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">picture</span>&gt;</span></span></code></pre></div>
<picture>
  <source srcset="screenshot-dark.png" media="(prefers-color-scheme: dark)">
  <source srcset="screenshot-light.png" media="(prefers-color-scheme: light)">
  <img src="screenshot-light.png" alt="Miblog screenshot">
</picture>
<h2 id="color-scheme-thumbnails">Color scheme thumbnails</h2>
<p>There is another problem on the main index page. Unfortunately, most of article
thumbnails were not created with dark theme support in mind. The <a href="https://vyskocil.me/blog/bytekit-responsive/">responsive
ByteKit</a> is the first article to
feature the support for dark themed thumbnails and dark themed screenshots.</p>
]]></content:encoded></item><item><title>Bytekit Responsive</title><link>https://vyskocil.me/blog/bytekit-responsive/</link><pubDate>Wed, 18 Mar 2026 12:25:57 +0100</pubDate><guid>https://vyskocil.me/blog/bytekit-responsive/</guid><description>Bytekit: jq, awk and a responsive design I released version 0.3.0 of a bytekit.app, which is a privacy oriented AGPLv3 Web application for developers that I had been working on.&amp;#xA;There are a few new features to highlight:&amp;#xA;AWK, which is based on the impressive goawk package from …</description><content:encoded><![CDATA[<h1 id="bytekit-jq-awk-and-a-responsive-design">Bytekit: jq, awk and a responsive design</h1>
<p>I released version <a href="https://quay.io/repository/vyskocilm/bytekit?tab=tags">0.3.0</a> of a <a href="https://bytekit.app">bytekit.app</a>, which is a
privacy oriented AGPLv3 <a href="https://vyskocil.me/blog/introducing-bytekit/">Web application for developers</a> that I had been working on.</p>
<p>There are a few new features to highlight:</p>
<ul>
<li><a href="https://bytekit.app/goawk">AWK</a>, which is based on the impressive <a href="https://benhoyt.com/writings/goawk/">goawk</a> package from Ben Hoyt.</li>
<li><a href="https://bytekit.app/gojq">jq</a>, which is based on the <a href="github.com/itchyny/gojq">pure Go version of jq</a></li>
<li>new design, logo and responsive layout and a footer</li>
</ul>
<p>I must admit that ChatGPT helped me generate ideas for the logo and its color
scheme. My minuscule Inkscape skills handled the rest then.</p>
<img src="bytekit-stronger.svg" alt="Logo" style="width: 40%; max-width: 350px; height: auto;">
<h2 id="responsive-design">Responsive design</h2>
<p>However much bigger change than visual was a responsiveness. Bytekit is
intentionally light on elements. I wanted to have something, which just works
and to not overwhelm users or myself with UI options. Additionally writing UI
elements is not a funniest or most creative part of the work. So once I made
elements like working. Well then every other problem can be solved by adding a
new input box, right?</p>
<p>However, the most significant change was the responsiveness. Not the visuals. Bytekit intentionally uses only a few elements. I wanted something that just works and to not overwhelm users or myself with UI options. And I must admit that writing UI elements is not the most funnies part of the work. Quite the opposite. So once I made elements like the <a href="https://codeberg.org/vyskocilm/bytekit/src/branch/main/internal/kit/input_box.go">InputBox</a> work. Then every problem became a nail. Or something input box can solve.</p>
<p>Let&rsquo;s say the <a href="https://bytekit.app/goawk">awk page</a>.</p>
<ol>
<li>Command line parameters? InputBox!</li>
<li>Program itself? Well InputBox, of course.</li>
<li>Input?</li>
<li>Output?</li>
</ol>
<p>You get it. The Input Box has a consistent layout, the copy and paste buttons,
can display an error like a wrong AWK program being passed and can be bind to
<em>on text changed</em> event providing an interactivity. And as most of the pages
are based on this element, this helps a lot with a consistent user experience.</p>
<picture>
  <source srcset="bytekit-goawk-dark.png" media="(prefers-color-scheme: dark)">
  <source srcset="bytekit-goawk-light.png" media="(prefers-color-scheme: light)">
  <img src="bytekit-goawk-light.png" alt="ByteKit theme">
</picture>
<p>Despite using light HTML or using a CSS framework, the design was not well-suited
to the smaller screens. The biggest culprit was the <code>&lt;aside&gt;</code> menu, which got
rendered at the top of the screen every time, making the site next to useless on
mobile. Fortunately Bulma.css, the CSS framework behind the page, supports a
<a href="https://bulma.io/documentation/start/responsiveness/">responsiveness</a>.</p>
<h2 id="menu-is-twice">Menu is twice</h2>
<p>The problem is how to ensure that mobile users gets no menu and can show one,
while desktop users has their menu always displayed as they do have much free
space. The solution was to render the menu twice and use CSS to hide/show it
depending on a screen size.</p>
<p>Bigger screens are now defined with <code>is-hidden-touch</code> CSS class, which ensures
it gets hidden on a smaller screens.</p>
<p>The menu for smaller screens is inside the navbar and has the class
<code>is-hidden-desktop</code>. This means, that by default, the <code>aside</code> and <code>navbar</code>
menus are hidden. In order to display it, there is a button which on a click
pass the <code>is-active</code> class to the proper element. This is a few lines of Go and
this is basically what drives a lot of interactivity of the bytekit itself.</p>
<picture>
  <source srcset="bytekit-mobile-menu-dark.png" media="(prefers-color-scheme: dark)">
  <source srcset="bytekit-mobile-menu-light.png" media="(prefers-color-scheme: light)">
  <img src="bytekit-mobile-menu-light.png" alt="ByteKit mobile menu">
</picture>
<p>There are still areas to improve, such as the aforementioned InputBox which has
too much padding on a mobile devices. In overall the site looks much better now.</p>
<h2 id="better-footer">Better footer</h2>
<p>Last but not least change is the new footer. Previously, it displayed a nerdy
sha256 hash to everyone, making it unpleasant and especially on a mobile. This
has been changed, it got a name, em dash, short description, links to source
code and hashes are hidden for those interested.</p>
<picture>
  <source srcset="bytekit-footer-dark.png" media="(prefers-color-scheme: dark)">
  <source srcset="bytekit-footer-light.png" media="(prefers-color-scheme: light)">
  <img src="bytekit-footer-light.png" alt="ByteKit footer">
</picture>
<p>Logo for the article is remixed <a href="https://github.com/egonelbre/gophers/blob/master/vector/science/power-to-the-linux.svg">https://github.com/egonelbre/gophers/blob/master/vector/science/power-to-the-linux.svg</a> with <a href="https://upload.wikimedia.org/wikipedia/commons/c/cd/IPhone_size_comparison.svg">https://upload.wikimedia.org/wikipedia/commons/c/cd/IPhone_size_comparison.svg</a>.</p>
]]></content:encoded></item><item><title>Bytekit: Generating Passwords</title><link>https://vyskocil.me/blog/bytekit-generating-passwords/</link><pubDate>Mon, 09 Feb 2026 23:41:03 +0100</pubDate><guid>https://vyskocil.me/blog/bytekit-generating-passwords/</guid><description>In my recent post introducing bytekit, I described a tiny little PWA designed to help me and other developers. Here I am going to introduce a new feature: generating passwords and secrets.&amp;#xA;It may sound odd to use an online tool for something as sensitive as password. The good …</description><content:encoded><![CDATA[<p>In my recent <a href="https://vyskocil.me/blog/introducing-bytekit/">post introducing bytekit</a>, I described a tiny little PWA designed to help me and other developers.
Here I am going to introduce a new feature: generating passwords and
secrets.</p>
<p>It may sound odd to use an <em>online</em> tool for something as sensitive as
password. The good thing is that <a href="https://bytekit.app/password-generator">https://bytekit.app/password-generator</a> has the necessary properties to make it trustworthy.</p>
<ol>
<li>It runs inside browser.</li>
<li>It does not send data anywhere - this can be verified.</li>
<li>It is <a href="https://codeberg.org/vyskocilm/bytekit">https://codeberg.org/vyskocilm/bytekit</a> Free Software, so the source code can be inspected.</li>
<li>Anyone can deploy the static binary or a container <a href="https://quay.io/repository/vyskocilm/bytekit">quay.io/vyskocilm/bytekit</a> himself.</li>
</ol>
<h1 id="how-to-generate-a-secure-password">How to generate a secure password</h1>
<p>In a not-so-good days of enterprise systems a few rules were made mandatory.</p>
<ol>
<li>Passwords must have a minimum length</li>
<li>Passwords must contains D digits and S special characters</li>
<li>One must change your password every M months</li>
<li>The system remembers all previous passwords, so it&rsquo;ll reject too similar new passwords. This is very secure, indeed.</li>
</ol>
<p>Xkcd made them obsolete.</p>
<p><a href="https://xkcd.com/936/"><img src="https://imgs.xkcd.com/comics/password_strength.png" alt="Password Strength"></a></p>
<p>In fact the sane rules are</p>
<ol>
<li>Passwords must be stored in a password manager</li>
<li>It prevents a phishing attempts among others</li>
<li>Passwords must be randomly generated (ie no <code>admin</code>, <code>12345</code>, or a <code>password</code> or a <code>football</code>)</li>
<li>Passwords must not be reused</li>
<li>The password manager is typically protected by a master password and this is where xkcd comes into a place.</li>
<li>Use <a href="https://haveibeenpwned.com/">https://haveibeenpwned.com/</a> and <a href="https://haveibeenpwned.com/Passwords">https://haveibeenpwned.com/Passwords</a> to verify your email and a password.</li>
</ol>
<h1 id="algorithms">Algorithms</h1>
<p>So to make the traditional enterprise systems happy as well as readers of xkcd and to enjoy a bit of coding,
<a href="https://bytekit.app/password-generator">https://bytekit.app/password-generator</a>
generates 4 types of passwords at once.</p>
<ol>
<li><strong>random</strong> is password which has uppercases, lowercases, digits and symbols.
Password rules are respected.</li>
<li><strong>wovel-consontant-wovel</strong> is a simplified version of <em>A Random Word Generator
For Pronounceable Passwords National Technical Information Service (NTIS)
AD-A-017676</em>, which aims to generate memorable passwords. Now is deprecated.
The wovel consonant tries to generate readable passwords using a simplified
algorithm. In a reality they&rsquo;re not if a password is long enough. Rules are
respected by placing digits and special characters to random places in a
generated string.</li>
<li><strong>koremutake</strong> generated passwords based on <a href="https://shorl.com/koremutake.php">algorithm from shorl</a> and <a href="https://github.com/wneessen/apg-go/blob/main/koremutake.go">apg-go</a> and its predefined syllables. Bytekit
randomly switches between all lowecase, all uppercase or first uppercase for each syllable. Rules are respected by placing digits and special characters to random places in a
generated string.</li>
<li><strong>xkcd</strong> - the recommended algorithm for passwords intended to be memorized. The
<code>CorrectHorseBatteryStaple</code> from <a href="https://xkcd.com/936/">xkcd: Password
Strength</a> is an example of great and safe password.
Except not <em>this</em> one, because it has been breached 150 times already (lower
case variant more than 4000 times) and today it is very likely be a part of a dictionary.
Rules related to digits and specials are ignored in this case.</li>
</ol>
<p>When in doubt use a <strong>random</strong> option or <strong>xkcd</strong> if special characters or digits does not matter.</p>
<h1 id="password-rules">Password Rules</h1>
<p>Many sites require passwords to follow specific rules. To avoid implementing
many HTML controls, I use a simple algorithm that tries to identify rules in
plain English and apply them. The best results are when each rule is on its own
line, like the placeholder.</p>
<p>It works by searching for numbers or well known word numbers like zero and then
within three words long window some keyword is searched. The &ldquo;Password must
have three digits&rdquo; will find a number 3 and a keyword <code>digits</code>, so minimal
number of digits is set to 3.</p>
<p>The <a href="https://codeberg.org/vyskocilm/bytekit/src/commit/58330d16c30f26e288e61940cdf897673c89ff7eb9f95e278fbc54a5e68e86dd/internal/app/apg/generate.go#L368">parseRules</a> and <a href="https://codeberg.org/vyskocilm/bytekit/src/commit/58330d16c30f26e288e61940cdf897673c89ff7eb9f95e278fbc54a5e68e86dd/internal/app/apg/generate_test.go#L94">TestParse</a> can shed more light to anyone interested.</p>
]]></content:encoded></item><item><title>Neovim as a Java Ide with Lombok support</title><link>https://vyskocil.me/blog/neovim-as-java-ide-lombok/</link><pubDate>Mon, 26 Jan 2026 09:12:43 +0100</pubDate><guid>https://vyskocil.me/blog/neovim-as-java-ide-lombok/</guid><description>I got recently switched to working on a Java codebase. This is a tutorial on how to setup neovim as a proper Java IDE, including a lombok support.&amp;#xA;Introduction After working with computers for almost two decades, vim&amp;amp;rsquo;s modal editing is wired into my brain and a muscle …</description><content:encoded><![CDATA[<p>I got recently switched to working on a Java codebase. This is a tutorial on
how to setup neovim as a proper Java IDE, including a
<a href="https://projectlombok.org/">lombok</a> support.</p>
<h1 id="introduction">Introduction</h1>
<p>After working with computers for almost two decades, vim&rsquo;s modal editing is
wired into my brain and a muscle memory. I do not consider myself as an
advanced user, but yanking/pasting, <code>%</code>, splits, textobjects, macros and a few
movements that I use are enough for me to stick with it. Not being forced to
use mouse is a nice bonus too.</p>
<p>Enter <a href="https://neovim.io">neovim</a>. As a fork of vim, it inherits all good parts
and adds dozens of advantages making it a plausible IDE. Among other is brings
tree-sitter, native LSP client and Lua as a scripting language.</p>
<p>Unlike int my favorite language Go, where vim/neovim sits proudly <a href="https://go.dev/blog/survey2025">in a 3rd
place</a> with 19% of respondents claiming use is.
The situation in Java world is most likely different. I could not find
any specific numbers for it. However, since its inception, the Java world has been
dominated by IDEs. Starting with
<a href="https://netbeans.apache.org/front/main/index.html">Netbeans</a> - which to my
surprise is still an active project under Apache Foundation - through <a href="https://eclipseide.org/">Eclipse
IDE</a>, and, most recently <a href="https://www.jetbrains.com/idea/">Intellij</a>, probably the most popular IDE today. Then there are Spring Tools Suite,
JBoss Developer studio as well as a Visual Studio Code. Java has never been short of IDEs.</p>
<h1 id="neovim-plugins">Neovim plugin(s)</h1>
<p>When I&rsquo;m checking out anew programming language, I check out the
<a href="https://github.com/neovim/nvim-lspconfig">nvim-lspconfig</a> first. However, in the case of
Java, this is the wrong place to look.</p>
<p><a href="https://codeberg.org/mfussenegger/nvim-jdtls">mfussenegger/nvim-jdtls</a> is the plugin I used. It integrates neovim with <a href="https://projects.eclipse.org/projects/eclipse.jdt.ls">Eclipse JDT
LS</a>. Yes, that is is the
<em>same</em> Eclipse, that produces the IDE mentioned earlier. There is also
<a href="https://github.com/nvim-java/nvim-java">nvim-java/nvim-java</a> too, which I
haven&rsquo;t tested. The <code>nvim-java</code> project itself says</p>
<blockquote>
<p>nvim-jdtls is a plugin that follows &ldquo;Keep it simple, stupid!&rdquo; approach.
If you love customizing things by yourself, then give nvim-jdtls a try.</p>
</blockquote>
<p>Which, to be honest, haven&rsquo;t shed any lights whenever I want to use this or that plugin.</p>
<h2 id="installation">Installation</h2>
<p>The installation string is a bit longer, because project is hosted on codeberg.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;mfussenegger/nvim-jdtls&#34;</span>,
</span></span><span style="display:flex;"><span>  url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;https://codeberg.org/mfussenegger/nvim-jdtls&#34;</span>,
</span></span><span style="display:flex;"><span>  ft <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;java&#34;</span>,
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Then configure it and enable.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span>vim.lsp.config(<span style="color:#e6db74">&#34;jdtls&#34;</span>, {capabilities <span style="color:#f92672">=</span> capabilities,})
</span></span><span style="display:flex;"><span>vim.lsp.enable(<span style="color:#e6db74">&#34;jdtls&#34;</span>)</span></span></code></pre></div>
<p>And that&rsquo;s all folks. Or isn&rsquo;t?</p>
<h1 id="project-lombok">Project Lombok</h1>
<p>A typical Java class has a few things: the private attributes, constructor and the infamous getter and setter methods. Unlike in Go, making the class attributes public is considered a bad style. However as both languages emphasizes interfaces, you can&rsquo;t place an attribute to interface defintion - ignoring <code>final static</code> constants in Java. So having getter setters makes a sense.</p>
<p>So here is the typical Java code</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Foo</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">int</span> bar;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">Foo</span>(<span style="color:#66d9ef">int</span> bar) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">bar</span> <span style="color:#f92672">=</span> bar;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">int</span> <span style="color:#a6e22e">getBar</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> bar;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">void</span> <span style="color:#a6e22e">setBar</span>(<span style="color:#66d9ef">int</span> bar) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">bar</span> <span style="color:#f92672">=</span> bar;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>The getter/setter code is very repetitive. The original Java solution was to use IDE to generate the getter and setter code. <a href="https://projectlombok.org/">Project lombok</a> automates the whole thing via plugging into JVM and via special feature called annotation it generates needed methods in a background.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#f92672">import</span> lombok.AllArgsConstructor;
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> lombok.Data;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@Data</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@AllArgsConstructor</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Foo</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">int</span> bar;
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>It turns out the <code>nvim-jdtls</code> does not support this out of the box, so LSP compaints all the time about missing methods.</p>
<h1 id="the-howto">The HOWTO</h1>
<p>The TL;DR answer is to check <a href="https://codeberg.org/vyskocilm/dotfiles/src/branch/main/.config/nvim/lua/plugins/jdtls.lua">jdtls.lua in my
dotfiles</a>. It contains complete setup for it.</p>
<p>I do not want to use Mason, which can automate a lot of LSP related downloads.
So the process is a bit manual.</p>
<ol>
<li><a href="https://projectlombok.org/download">download the lombok.jar</a> and place it somewhere</li>
<li>define <code>lombok_path</code> variable pointing to the lombok.jar</li>
<li>pass it as <code>&quot;-javaagent:&quot; .. lombok_path</code> to the configuration.</li>
</ol>
<p>And that&rsquo;s it.</p>
<p>I used the Lua variable <code>lombok_path</code> to debug issues I
was facing while configuring neovim. Using the variable made it easier to write a proper notification. So now, when I open a Java project, the confirmation about properly found <code>lombok.jar</code> properly appears at the top of the messages.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#66d9ef">local</span> notify <span style="color:#f92672">=</span> vim.notify
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">local</span> ok, fidget <span style="color:#f92672">=</span> pcall(require, <span style="color:#e6db74">&#34;fidget&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> ok <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>  notify <span style="color:#f92672">=</span> fidget.notify
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">-- Verify lombok exists</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> vim.fn.filereadable(lombok_path) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>  notify(<span style="color:#e6db74">&#34;Lombok jar not found at: &#34;</span> <span style="color:#f92672">..</span> lombok_path, vim.log.levels.ERROR)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>  notify(<span style="color:#e6db74">&#34;Lombok jar found at: &#34;</span> <span style="color:#f92672">..</span> lombok_path, vim.log.levels.INFO)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">end</span></span></span></code></pre></div>
<h1 id="acknowledgments">Acknowledgments</h1>
<p>The following images has been hurt during the logo creation process. And ChatGPT
has been used to sketch the original idea.</p>
<ul>
<li><a href="https://cdn.jsdelivr.net/gh/neovim/neovim@f05a289/cmake.packaging/neovim.svg">neovim.svg</a></li>
<li><a href="https://www.vectorlogo.zone/logos/java/java-icon.svg">java-icon.svg</a></li>
<li><a href="https://avatars.githubusercontent.com/u/45949248?s=200&amp;v=4">project-lombook-avatar</a></li>
</ul>
]]></content:encoded></item><item><title>Introducing Bytekit.app: Privacy Oriented Web App for developers</title><link>https://vyskocil.me/blog/introducing-bytekit/</link><pubDate>Tue, 13 Jan 2026 17:31:39 +0100</pubDate><guid>https://vyskocil.me/blog/introducing-bytekit/</guid><description>I recently deployed my tiny Web application bytekit.app. This is tool for developers doing stuff like JWT, base64, hashes and so on. I wanted something easy to use, privacy-oriented, and fast.&amp;#xA;Bytekit is written in Go. That might sound an odd choice for a browser app, but Go …</description><content:encoded><![CDATA[<p>I recently deployed my tiny Web application <a href="https://bytekit.app">bytekit.app</a>.
This is tool for developers doing stuff like JWT, base64, hashes and so on. I
wanted something easy to use, privacy-oriented, and fast.</p>
<p>Bytekit is written in <a href="https://go.dev">Go</a>. That might sound an odd choice for a browser
app, but Go itself targets WebAssembly. Using
<a href="https://go.dev/wiki/WebAssembly">GOARCH=wasm</a> together with
<a href="https://go-app.dev/">go-app</a> let me run Go in the browser.</p>
<h1 id="the-motivation">The motivation</h1>
<p>I mostly use the command line, but I still rely on online tools from time to time. Many are available, yet most have annoying issues:</p>
<ul>
<li>no privacy - &ldquo;we and 635 trustworthy partners swear we won&rsquo;t share your data&rdquo;</li>
<li>dubious security - is data processing done client-side or server-side? Are
results shared with third parties?</li>
<li>no consistency - each tool uses a different UI, and some need extra clicks for simple actions</li>
<li>no source code</li>
</ul>
<p>My main motivation was a better JWT decoder. I personally find the UI of
<a href="https://jwt.io">https://jwt.iot</a> as confusing with regards to verifying of
signatures. So I wanted to have a cleaner and simpler interface. As a GNOME
user, I like the Adwaita and GNOME HIG in general. So the goal was to have
something similar to <a href="https://flathub.org/en/apps/me.iepure.devtoolbox">Dev
Toolbox</a> only on web.</p>
<h2 id="1-security-first">1. Security first</h2>
<p>Developer tools often handle sensitive data like auth tokens and API keys. Sending
that data through a web app can be risky. Bytekit runs entirely in the browser</p>
<ul>
<li>nothing is ever sent outside your computer. You can verify this easily with your
browser&rsquo;s developer tools.</li>
</ul>
<h2 id="2-privacy">2. Privacy</h2>
<p>The Privacy policy for <a href="https://bytekit.app">https://bytekit.app</a> is simple. I
run the service alone - there are no partners, no trackers, no ads and no
analytics. The HTTP requests are sent to <code>journald</code> and are placed to 100MB big
in-memory ring buffer together with all other services. I inspect them manually
from time to time for a debugging purposes.</p>
<p>The site links to its source code hosted on <a href="https://codeberg.org/vyskocilm/bytekit/">Codeberg</a> and the it runs from <a href="https://quay.io/repository/vyskocilm/bytekit?tab=tags&amp;tag=latest">quay.io/vyskocilm/bytekit</a> container image. So self-hosting is always an option.</p>
<h2 id="3-open-source--free-software">3. Open source / Free software</h2>
<p>I chose AGPLv3 for the main app because I want to encourage people to
self-host, reuse, or improve the project. At the same time, the license asks
that any changes to be shared back with the community.</p>
<h2 id="4-easy-to-use">4. Easy to use</h2>
<p>I avoided forcing clicks for simple tasks. The app runs locally in your browser
and processes data as you type. The UI is meant to be clear and minimal — no
unnecessary bells and whistles. One example is the hash/checksum page. Instead of making you choose an algorithm, it computes them all at once.</p>
<h1 id="using-go-on-a-frontend">Using Go on a frontend?</h1>
<p>Let&rsquo;s start with an elephant in the room first. Which is the artifacts size.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ ls -lh public/web/*.wasm*
</span></span><span style="display:flex;"><span>-rw-r--r--. <span style="color:#ae81ff">1</span> michal michal 9.1M Jan  <span style="color:#ae81ff">7</span> 16:14 public/web/app.wasm
</span></span><span style="display:flex;"><span>-rw-r--r--. <span style="color:#ae81ff">1</span> michal michal 2.4M Jan  <span style="color:#ae81ff">7</span> 16:14 public/web/app.wasm.gz
</span></span><span style="display:flex;"><span>-rw-r--r--. <span style="color:#ae81ff">1</span> michal michal 2.1M Jan  <span style="color:#ae81ff">7</span> 16:14 public/web/app.wasm.zst</span></span></code></pre></div>
<p>For a perspective, a main Javascript file from YouTube says 1,835kB (resource
size 9,446 kB). So given the fact this comes with full Go runtime, I consider
this as pretty small and acceptable. The wasm binary and other assets are
compressed to save bandwidth. Clients request the compressed version via
Content Negotiation (<code>Accept-Encoding</code>). Browsers do this by default, so users
get the smaller transfer automatically.</p>
<p>The server actually refuses to serve an uncompressed wasm file.</p>
<p>The application itself is just a directory of a static assets inside a
<code>public/</code> folder. I also included optional Go <code>net/http</code> server to serve those
files. That server has a few advantages over serving static files:</p>
<ul>
<li>it is compatible with OCI containers and can be placed behind a reverse proxy</li>
<li>use of <code>embed</code> means the app is a single executable which works from memory
<ul>
<li>it doesn&rsquo;t need external services, neither a filesystem and can be deployed anywhere</li>
</ul>
</li>
<li>it handles content negotiation for compressed assets and refuses to serve a plain wasm</li>
<li>it makes app cache friendly by handling Etags, <code>If-None-Match</code> and <code>Vary</code> HTTP headers</li>
<li>it ensures a correct <code>Content-Type</code> for all requests</li>
<li>it has a rewrite rules included - so paths never ends with <code>.html</code></li>
<li>it takes a care about <code>Content-Security-Policy</code> headers</li>
</ul>
<h1 id="how-the-frontend-go-look-like">How the frontend Go look like?</h1>
<p>This is not a tutorial, only a short showcase of a frontent Go. The example is
a base64 encoding/decoding page. All UI components are plain Go structs which
must embed the framework&rsquo;s <code>app.Compo</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">base64</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">bk</span> <span style="color:#e6db74">&#34;codeberg.org/vyskocilm/bytekit/internal/kit&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;github.com/maxence-charriere/go-app/v10/pkg/app&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Content</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">Compo</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">showError</span> <span style="color:#a6e22e">bk</span>.<span style="color:#a6e22e">ToastFunc</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">encoded</span>   <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">decoded</span>   <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">encoding</span>  <span style="color:#f92672">*</span><span style="color:#a6e22e">base64</span>.<span style="color:#a6e22e">Encoding</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>To render a content as HTML one must implement the <code>Render</code> method from
<a href="https://pkg.go.dev/github.com/maxence-charriere/go-app@v1.0.1/pkg/app#Composer">app.Composer</a>.
Those components are HTML components defined by <code>go-app</code> or those can be other
structs implementing <code>app.Composer</code> interface. The app defines a few reusable
UI components this way, like and <code>InputBox</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Content</span>) <span style="color:#a6e22e">Render</span>() <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">UI</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">Div</span>().<span style="color:#a6e22e">Body</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">title</span>{
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">showError</span>:  <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">showError</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">onSelected</span>: <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">onEncodingChanged</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">bk</span>.<span style="color:#a6e22e">InputBox</span>().
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">SetText</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">encoded</span>).
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">ShowError</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">showError</span>).
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Label</span>(<span style="color:#e6db74">&#34;Base64 encoded content&#34;</span>).
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">OnChangedText</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">onEncodedChange</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">bk</span>.<span style="color:#a6e22e">InputBox</span>().
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">SetText</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">decoded</span>).
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">ShowError</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">showError</span>).
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Label</span>(<span style="color:#e6db74">&#34;Decoded text&#34;</span>).
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">OnChangedText</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">onDecodedChange</span>),
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>The interactivity is done via callbacks - the framework calls a defined
callback a particular event.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Content</span>) <span style="color:#a6e22e">onDecodedChange</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">decoded</span> <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Async</span>((<span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">encoded</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">encoding</span>.<span style="color:#a6e22e">EncodeToString</span>([]byte(<span style="color:#a6e22e">decoded</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Dispatch</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">encoded</span> = <span style="color:#a6e22e">encoded</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">decoded</span> = <span style="color:#a6e22e">decoded</span>
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>  }))
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>In the browser, JavaScript runs on a single main thread. Heavy computations
block that thread and freeze the UI because no other JavaScript can run at the
same time. There is a concept of a
<a href="https://developer.mozilla.org/en-US/docs/WebAssembly/API/Worker">Worker</a> which
lets you run code off the main thread.</p>
<p>In <a href="https://go-app.dev/concurrency">Concurrency</a> model of <code>go-app</code> all things runs in a single UI goroutine which updates the UI and a state of the components. <code>Async</code> then runs the code inside another goroutine, which do not block the main UI. When code is finished running, the <code>Dispatch</code> call passes results safely back to the UI goroutine.</p>
<h2 id="javascript-interop">Javascript interop</h2>
<p>No frontend code can live without an ability to call Javascript. This is the only way for accessing browser APIs. The <code>app</code> itself exposes some of the DOM API.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>  <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Async</span>(<span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">Window</span>().<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;navigator&#34;</span>).
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;clipboard&#34;</span>).
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Call</span>(<span style="color:#e6db74">&#34;writeText&#34;</span>, <span style="color:#a6e22e">text</span>)
</span></span><span style="display:flex;"><span>  })</span></span></code></pre></div>
<p>An access to elements triggering an event is possible too.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>  <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Async</span>(<span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">title</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">JSSrc</span>().<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;innerText&#34;</span>).<span style="color:#a6e22e">String</span>()
</span></span><span style="display:flex;"><span>  })</span></span></code></pre></div>
<p>As well as a support for
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a>,
which is the only way of reading from a clipboard.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">clipboard</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">Window</span>().<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;navigator&#34;</span>).
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;clipboard&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">promise</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">clipboard</span>.<span style="color:#a6e22e">Call</span>(<span style="color:#e6db74">&#34;readText&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">thenFunc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">FuncOf</span>(<span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">catchFunc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">FuncOf</span>(<span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">promise</span>.<span style="color:#a6e22e">Call</span>(<span style="color:#e6db74">&#34;then&#34;</span>, <span style="color:#a6e22e">thenFunc</span>).<span style="color:#a6e22e">Call</span>(<span style="color:#e6db74">&#34;catch&#34;</span>, <span style="color:#a6e22e">catchFunc</span>)</span></span></code></pre></div>
<p>The frontend itself is generated by <a href="https://go-app.dev">https://go-app.dev</a> via</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ GOARCH<span style="color:#f92672">=</span>wasm GOOS<span style="color:#f92672">=</span>js go build -o <span style="color:#e6db74">&#34;public/web/app.wasm&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>codeberg.org/vyskocilm/bytekit/cmd/bytekit</span></span></code></pre></div>
<p>This populates the content of a <code>public</code> folder where all web assets are placed. The <code>go-app</code> framework is the one doing all the magic behind the scenes.</p>
<h1 id="so-hows-dev-experience-without-npm">So how&rsquo;s dev experience without npm?</h1>
<p>The <code>npm</code> (<code>pnpm</code>, <code>yarn</code>, &hellip;) is pervasive in a FE development world. Not
using the standard frontend toolbox means you often ends up solving problems
yourself. That said, using npm for parts of the workflow can be reasonable. For
example, bytekit uses <code>purgecss</code> to minify bulma.css by removing unused
declarations, making assets as lean as possible.</p>
<p>On the other hand sometimes writing a part of a build chain is easy and very
convenient. As an example code compressing assets with gzip and zstd turned out
to be about 100 lines of straightforward, idiomatic Go code. This is a tradeoff
worth making. Especially the build system used,
<a href="https://magefile.org/">https://magefile.org/</a>, is Go code itself.</p>
<p>So there are gophers all the way down.</p>
<p>Having complete codebase in a single language including the build system makes
the developer experience very pleasant. There is a little magic and build logic
is only a code inside <code>internal/mage</code> and can be inspected, changed or tested
exactly like the rest of the other code. The top level <code>mage.go</code> merely defines
a build targets.</p>
<p>And as go supports <code>go tool</code> the build system is a dev dependency listed in <code>go.mod</code>.</p>
<pre tabindex="0"><code class="language-gomod" data-lang="gomod">tool github.com/magefile/mage</code></pre>
<p>And a CI is a buch of calls of a form <code>go tool mage $target</code></p>
<ul>
<li> smaller ecosystem to pull from</li>
<li> coherent codebase - everything is written in one language</li>
<li> speed - once is built, it runs faster than npm tooling</li>
</ul>
<h2 id="closing-thoughts">Closing thoughts</h2>
<p>Bytekit started as a small experiment in running Go on the web. It stays small and focused on a few useful tools. It’s privacy-first, open source, and easy to self‑host if you prefer.</p>
<p>Try it at [https://bytekit.app]. The source is on Codeberg and the container image is on <code>quay.io</code>, so running your own copy is straightforward. Issues, ideas, and pull requests are welcome.</p>
<p>If you like the approach or find a rough edge, tell me - especially about the UI and privacy parts. Thanks for reading and for trying Bytekit.</p>
<p>Logo is remixed [https://github.com/egonelbre/gophers/blob/master/vector/science/power-to-the-linux.svg].</p>
]]></content:encoded></item><item><title>UnmarshalText in Go</title><link>https://vyskocil.me/blog/unmarshal-text-in-go/</link><pubDate>Wed, 12 Nov 2025 10:10:45 +0100</pubDate><guid>https://vyskocil.me/blog/unmarshal-text-in-go/</guid><description>I must admit that I tend to overlook the interfaces in the encoding package. Since I typically work with JSON or Yaml, so I tend to care more about methods like UnmarshalJSON. However, today I learned the trick of using TextUnmarshaler when I needed to use a custom type inside …</description><content:encoded><![CDATA[<p>I must admit that I tend to overlook the interfaces in the
<a href="https://pkg.go.dev/encoding#TextUnmarshaler">encoding</a> package. Since I typically
work with JSON or Yaml, so I tend to care more about methods like
<code>UnmarshalJSON</code>. However, today I learned the trick of using <code>TextUnmarshaler</code>
when I needed to use a custom type inside the struct.</p>
<h1 id="problem">Problem</h1>
<p>Consider this code</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;encoding/json&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;net/url&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Foo</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">BaseURL</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">url</span>.<span style="color:#a6e22e">URL</span> <span style="color:#e6db74">`json:&#34;base_url&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">src</span> = <span style="color:#e6db74">`{&#34;base_url&#34;: &#34;https://example.com&#34;}`</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">aFoo</span> <span style="color:#a6e22e">Foo</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">src</span>), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">aFoo</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    panic(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%#+v\n&#34;</span>, <span style="color:#a6e22e">aFoo</span>.<span style="color:#a6e22e">BaseURL</span>.<span style="color:#a6e22e">String</span>())
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>The standard <code>*url.URL</code> can&rsquo;t do that. There&rsquo;s
<a href="https://pkg.go.dev/net/url#URL.UnmarshalBinary">UnmarshalBinary</a>, but no other
method available.</p>
<pre><code>panic: json: cannot unmarshal string into Go struct field Foo.base_url of type url.URL
</code></pre>
<h1 id="unmarshaltext">UnmarshalText</h1>
<p><a href="https://pkg.go.dev/encoding/json#Unmarshal">json.Unmarshal</a> documents how it works</p>
<blockquote>
<p>To unmarshal JSON into a value implementing [Unmarshaler],
Unmarshal calls that value&rsquo;s [Unmarshaler.UnmarshalJSON] method, including
when the input is a JSON null.
Otherwise, if the value implements [encoding.TextUnmarshaler]
and the input is a JSON quoted string, Unmarshal calls
[encoding.TextUnmarshaler.UnmarshalText] with the unquoted form of the string.</p>
</blockquote>
<p>The only thing needed to be implemented is <code>TextUnmarshaler</code> interface. Because URL is
a string in JSON.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">URL</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">*</span><span style="color:#a6e22e">url</span>.<span style="color:#a6e22e">URL</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">u</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">URL</span>) <span style="color:#a6e22e">UnmarshalText</span>(<span style="color:#a6e22e">text</span> []<span style="color:#66d9ef">byte</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">parsed</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">url</span>.<span style="color:#a6e22e">Parse</span>(string(<span style="color:#a6e22e">text</span>))
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">URL</span> = <span style="color:#a6e22e">parsed</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>And the output is <code>&quot;https://example.com&quot;</code>.</p>
<h1 id="marshal-it-back">Marshal it back</h1>
<p>When marshaled back to JSON, the URL looks like this: No one wants to write
URLs like this.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;u&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Scheme&#34;</span>: <span style="color:#e6db74">&#34;https&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Opaque&#34;</span>: <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;User&#34;</span>: <span style="color:#66d9ef">null</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Host&#34;</span>: <span style="color:#e6db74">&#34;example.com&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Path&#34;</span>: <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;RawPath&#34;</span>: <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;OmitHost&#34;</span>: <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;ForceQuery&#34;</span>: <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;RawQuery&#34;</span>: <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Fragment&#34;</span>: <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;RawFragment&#34;</span>: <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Obviously there is an interface for that</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">u</span> <span style="color:#a6e22e">URL</span>) <span style="color:#a6e22e">MarshalText</span>() ([]<span style="color:#66d9ef">byte</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">URL</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> []<span style="color:#66d9ef">byte</span>{}, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> []byte(<span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">String</span>()), <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{<span style="color:#f92672">&#34;base_url&#34;</span>:<span style="color:#e6db74">&#34;https://example.com&#34;</span>}</span></span></code></pre></div>
<h1 id="conclusion">Conclusion</h1>
<p>While the text encoding methods are somewhat less popular than their
<code>encoding/json</code> counterparts, they can be still useful.</p>
<p>See complete example <a href="https://go.dev/play/p/BKX9j-OOprk">on Go playground</a>.</p>
<p>Logo is remixed <a href="https://github.com/egonelbre/gophers/blob/master/vector/superhero/standing.svg">github.com/egonelbre/gophers</a> and is under <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0 license</a> like an original.</p>
]]></content:encoded></item><item><title>Ci Setup Which Never Worked</title><link>https://vyskocil.me/blog/ci-setup-which-never-worked/</link><pubDate>Thu, 23 Oct 2025 19:20:50 +0200</pubDate><guid>https://vyskocil.me/blog/ci-setup-which-never-worked/</guid><description>In an earlier post about giving up Kubernetes, I wrote that my server is a plain old virtual machine pet. While this works pretty well for a static web server use case, I wanted to explore ways how to automate this blog.&amp;#xA;This is a story about how I failed. I am writing this …</description><content:encoded><![CDATA[<p>In an <a href="/blog/back-to-vm/">earlier post about giving up Kubernetes</a>, I
wrote that my server is a plain old virtual machine pet. While this works
pretty well for a static web server use case, I wanted to explore ways
how to automate this blog.</p>
<p>This is a story about how I failed. I am writing this because I did my best to
make it work, but it turned out its impossible</p>
<p>At the same time it has a some good hints about user systemd, quadlets, rootless podman and what I managed to get working.</p>
<h1 id="the-problem">The problem</h1>
<p>When it comes to your own stuff, overengineering is always the preferred
option, isn&rsquo;t it? My problem was fairly simple. After finishing a Markdown
document in <a href="https://neovim.io/">neovim</a>, I have to run
<a href="https://gohugo.io/">hugo</a>, install it, or find a
(distrobox)[https://distrobox.it/] container with it installed. Then, I need to
push the source and built content to the
<a href="https://codeberg.org/vyskocilm/miblog/">Codeberg</a> repository. After that, I
need to log into the server, switch to the user, and run <code>git pull</code> to finally
<em>publish</em> the changes.</p>
<p>Except for the <code>git</code>, this is a pretty 90s approach. And as a popular saying goes</p>
<blockquote>
<p>Developer will rather spent 3 days writing script* rather than doing the
same 3 minutes of manual work twice.</p>
</blockquote>
<p>Which is again pretty 90s, isn&rsquo;t it? Nowadays developers would spent 3 months
deploying a multi-cloud multi-region k8s cluster with Terraform, Helm, ArgoCD
and dozens of microservices than writing a script.</p>
<h1 id="the-idea">The idea</h1>
<p>The solution exists and is called <a href="https://en.wikipedia.org/wiki/CI/CD">CI/CD</a>.
Because Codeberg itself is a public instance of a software called
<a href="https://forgejo.org/">Forgejo</a>, the most natural way to do it is with
<a href="https://forgejo.org/docs/latest/user/actions/reference/">actions</a> powered by
<a href="https://forgejo.org/docs/latest/admin/actions/#forgejo-runner">forgejo-runner</a>.
There are
<a href="https://jan.wildeboer.net/2024/08/Running-a-runner-codeberg/">others</a> who
successfully did that.</p>
<blockquote>
<p>Here I wanted to mention that both forgejo-runner and codeberg are wonderful
tools, which has nothing to do with why I failed.</p>
</blockquote>
<p>So the idea is to build roughly this.</p>
<ol>
<li>On every push, Hugo Build translates the content.</li>
<li>Things will be packaged into an OCI container and pushed to an internal repository.</li>
<li>The blog itself will be an OCI container running behind Caddy.</li>
<li>The whole server will be behind the front-end Caddy, which will be in charge of logging, TLS, and certificate management.</li>
</ol>
<p>It&rsquo;s pretty standard, isn&rsquo;t it? Given the state of the industry in 2025, it
doesn&rsquo;t sound too complicated.</p>
<h1 id="the-container-curse">The container curse</h1>
<p>I must say that I like containers. When used properly, they&rsquo;re amazing. For
example the <a href="https://github.com/openSUSE/cnf">openSUSE cnf</a>. This is a Rust
tool built inside
<a href="https://github.com/openSUSE/cnf/blob/main/.github/workflows/pr_check.yml#L13">ubuntu-latest</a>
image. In order to test that it works on openSUSE itself, the resulting binary
is <a href="https://github.com/openSUSE/cnf/blob/main/test/root.sh#L23">mounted</a> to a
tested openSUSE Tumbleweed container. This allows for <a href="https://github.com/openSUSE/cnf/actions/runs/17488932710/usage">a
fast</a>
integration test using a real system as well as testing of all supported
scenarios like <code>zypper</code>, <code>dnf5</code> or <code>dnf</code>.</p>
<p>What I don&rsquo;t like is Docker, specifically <code>dockerd</code>, and the fact that it runs
as <code>root</code>. While I understand why it is like that. And after this saga I
understand it more than ever. Its simplicity is what made it so popular in the
first place. Docker like <code>dockerd</code> is a great solution for developers. Not for
my server. This brings me to the real <em>curse</em> of containers: <em>rootless</em> ones.</p>
<p>Specifically, I dislike <code>dockerd</code> because it runs as root. I understand why it
is like that, though. After this saga, I understand it more than ever. Its
simplicity is what made it popular in the first place. Docker, the all might
<code>dockerd</code>, is a great solution for developers. But not for my server. This
brings me to the real curse of containers: rootless ones.</p>
<p>It&rsquo;s 2025, and I must say, you can really go far with them. Most of their
limitations are in the form of <code>/etc/subuid</code> allocations, and sometimes one
encounters a strange permission error. However, it&rsquo;s much better than it was in
2020 when I was experimenting with them.</p>
<h1 id="how-to-setup-a-rootless-container">How to setup a rootless container</h1>
<h2 id="1-create-dedicated-user">1. create dedicated user</h2>
<p>Create a new dedicated system user; <code>systemd-sysuser</code> is probably the best
option nowadays.</p>
<pre><code class="language-sh">&gt; cat /etc/sysusers.d/forgejo.conf
#Type Name    ID   GECOS               Home directory  Shell
u     actions -    &quot;forgejo actions&quot;   /home/actions   /sbin/nologin
r     -       1000-1999
m     actions systemd-journal
</code></pre>
<p>For rootless Podman containers, it is best to start an user instance via
systemd so that the containers will be managed by it like a regular services.
Note that <code>systemd-sysuser</code> does not create a home directory.</p>
<pre><code class="language-sh"># Create new user
&gt; systemd-sysusers /etc/sysusers.d/forgejo.conf
# Run systemd's user instance for a newly user
&gt; systemctl start user@1999.service
</code></pre>
<p><a href="https://www.redhat.com/en/blog/quadlet-podman">Quadlet</a> is a Red Hat technology
that integrates Podman with a systemd. By writing a <code>.container</code> file
that describes all of its properties, quadlet generates an
appropriate systemd unit.</p>
<p>This means that forgejo runner <em>itself</em> runs as a container. To do its job, it
needs a &ldquo;docker&rdquo; socket. In this case it is podman socket of a <code>actions</code> user
mounted to the proper place. This allows runner to pull and execute other
containers without having a chance to obtain root privileges.</p>
<p>This setup mounts <code>/home/actions/runner</code> into the runner container, ensuring cache is
saved between runs.</p>
<pre><code class="language-ini">[Unit]
Description=Forgejo runner

[Container]
Image=code.forgejo.org/forgejo/runner:11
ContainerName=forgejo-runner
User=0
Group=0
Environment=DOCKER_HOST=unix:///var/run/docker.sock
Exec=forgejo-runner --config /data/config/config.yml daemon
Volume=%h/runner:/data:Z
Volume=/run/user/%U/podman/podman.sock:/var/run/docker.sock:rw
Volume=/etc/localtime:/etc/localtime:ro
AutoUpdate=registry

[Service]
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=default.target
</code></pre>
<pre><code class="language-sh"># Check actions user services
&gt; systemctl --machine actions@.host --user status forgejo-runner
● forgejo-runner.service - Forgejo runner
     Loaded: loaded (/home/actions/.config/containers/systemd/forgejo-runner.container; generated)
     Active: active (running) since Thu 2025-10-23 18:37:46 CEST; 1h 35min ago
</code></pre>
<h2 id="selinux-intermezzo">SElinux intermezzo</h2>
<p>In the grand scheme of things, this was really a minor annoyance. I got a
&ldquo;permission denied&rdquo; on <code>/var/run/docker.sock</code> inside the container. The problem
was that openSUSE Leap default policy don&rsquo;t allow for <code>container_t</code> to use
<code>connectto</code>.</p>
<pre><code class="language-sh">$ ausearch -m AVC -ts recent

type=AVC msg=audit(1761167553.886:1210): avc:  denied  { connectto } for
pid=34382 comm=&quot;forgejo-runner&quot; path=&quot;/run/user/1999/podman/podman.sock&quot;
scontext=system_u:system_r:container_t:s0:c167,c401
tcontext=unconfined_u:unconfined_r:container_runtime_t:s0-s0:c0.c1023
tclass=unix_stream_socket permissive=0
</code></pre>
<pre><code class="language-sh">&gt; ausearch -m AVC -ts recent | audit2allow -M forgejorunner
&gt; semodule -i forgejorunner.pp
</code></pre>
<pre><code>module forgejorunner 1.0;

require {
        type container_t;
        type container_runtime_t;
        class unix_stream_socket connectto;
}

#============= container_t ==============
allow container_t container_runtime_t:unix_stream_socket connectto;
</code></pre>
<h2 id="running-own-registry">Running own registry</h2>
<p>So far I was making a good progress. The runner was registered against Codeberg
and was running a <code>printf</code> just fine. The second problem was decifing to store
the containers with blog. I did not want to store them in any external
registry. They are private artifact of the server itself, so why expose them?</p>
<p>As I got confident with systemd, quadlets and container, I deployed another
container file under <code>registry</code> user.</p>
<pre><code class="language-ini">[Unit]
Description=Zot containers registry

[Container]
Image=ghcr.io/project-zot/zot-linux-amd64:latest
ContainerName=zotregistry
User=0
Group=0
Exec=serve /data/config/config.json
Volume=%h/registry:/data:Z
Volume=/etc/localtime:/etc/localtime:ro
PublishPort=127.0.0.1:2999:2999
AutoUpdate=registry

[Service]
Restart=on-failure
RestartSec=30s

[Install]
WantedBy=default.target
</code></pre>
<p>Long story short. It is a good idea that does not work. In a hindsight, it
makes a sense. You can&rsquo;t connect two rootless Podmans running under a different
user accounts. Not without doing their networking equal to <code>host</code>. Which kinda
makes no sense. Why go through all the troubles isolating things and then let
everything bind everywhere?</p>
<p>One simple alternative would be to expose the registry under a public name, such as
<code>registry.vyskocil.me</code>. The container was exposed to the (localhost)host. However, I
never wanted to deal with yet another public service. And the intent was always to keep the registry internal to the VM.</p>
<p>The easiest solution was to
move <code>registry</code> under <code>actions</code> user and to define a Podman network.</p>
<pre><code class="language-sh">&gt; cat /home/actions/.config/containers/systemd/ci-net.network
[Network]
Driver=bridge
</code></pre>
<p>Add <code>Network=ci-net.network</code> to both container files. And a started containers must have an access to the same network. As this is managed by <code>systemd</code> it has a prefix.</p>
<pre><code class="language-yaml">container:
  network: &quot;systemd-ci-net&quot;
</code></pre>
<h1 id="the-dead-end">The dead end</h1>
<p>I must admit that, during this phase, I thought all the difficult problems had been solved. Permissions, SELinux, and networking were all configured and set up. However, item 3 of my plan turned out to be a show stopper.</p>
<p>You cannot build a container. Inside a container. Period! Altough there seems to
be workarounds like <code>gcr.io/kaniko-project/executor:latest</code> they always require
elevated privileges like access to <code>/dev/fuse</code> for overlayfs and possibly the
<code>--priviledged</code> flag to gain more capabilities. I was not
comfortable doing this. The purpose of using separate user accounts
and a rootless Podman containers was to make the system more secure and less susceptible to breaches.</p>
<h1 id="sync-like-its-1996">Sync like its 1996</h1>
<p>So, how did I solve the problem? <code>rsync</code>! Since runner runs on my own host, I can mount the host&rsquo;s file system write the results there.</p>
<pre><code class="language-yaml">container:
  image: docker.io/library/node:22-bookworm
  options: &gt;-
    -v /home/actions/runner/public:/public:Z

  steps:
  - name: Trigger a sync
    run: touch /public/.ready-for-sync
</code></pre>
<h1 id="or-rather-2010">Or rather 2010?</h1>
<p>Then how are we going to sync files? <code>systemd</code> comes to the rescue. The easiect way is to have unit watching <code>.ready-for-sync</code> and run the <code>rsync</code> script. It runs as a root as it needs to adapt the privileges once more. However <code>systemd</code> itself is great because t provides a lot of tunables that limit the permissions to an absolute minimum.</p>
<pre><code class="language-ini">[Service]
# Hardening
ProtectSystem=strict
ProtectHome=read-only
BindReadOnlyPaths=/home/actions/runner/public
ReadWritePaths=/run/lock/ /srv/www...
InaccessiblePaths=/root
PrivateTmp=yes
PrivateMounts=yes
NoNewPrivileges=yes
ProtectControlGroups=yes
...
</code></pre>
<h1 id="conclusion">Conclusion</h1>
<p>Altough I didn&rsquo;t reach my original goal, I can still call this journey as a
success. At least I can uninstall <code>git</code> from VM and <code>hugo</code> from laptop and can
enjoy at least a partially modern, fully automated setup.</p>
<p>Codeberg/Forgejo and Forgejo-runner are amazing pieces of a software. I was
surprised that I could deploy and run the software successfully, even with rather
unusual setup.</p>
<p>And rootless pPdman containers in 2025? They are super cool. The
integration to <code>systemd</code> via quadlets is amazing. I feel good knowing they are limited by real Unix privileges and not by namespace/container magic.</p>
<p>However as you give up all the privileges, you can&rsquo;t build a new OCI container.</p>
<h1 id="last-remark">Last remark</h1>
<p>This is dedicated to all the script kiddies bombing my <code>sshd</code> with <code>ubuntu</code>, <code>admin</code>,
<code>wordpress</code> and other non sense accounts. Good luck with <code>actions</code>!</p>
<pre><code class="language-sh">&gt; getent passwd actions
actions:x:1999:1999:forgejo actions:/home/actions:/sbin/nologin
</code></pre>
]]></content:encoded></item><item><title>Oneoflint: Exhaustive Lint for Proto Files</title><link>https://vyskocil.me/blog/oneoflint-exhaustive-lint-for-proto-files/</link><pubDate>Sat, 18 Oct 2025 12:17:56 +0200</pubDate><guid>https://vyskocil.me/blog/oneoflint-exhaustive-lint-for-proto-files/</guid><description>In an earlier post about sumlint I wrote about a linter that helps with exhaustive checks of type switches for interfaces with a particular naming pattern.&amp;#xA;After some time, I realized that this is essentially how protobuf&amp;amp;rsquo;s oneof is implemented by protoc and protoc-gen-go. …</description><content:encoded><![CDATA[<p>In an <a href="/blog/sumlint-having-sum-types-in-go/">earlier post about sumlint</a> I wrote about a linter that helps
with exhaustive checks of type switches for interfaces with a particular naming pattern.</p>
<p>After some time, I realized that this is essentially how <a href="https://protobuf.dev/programming-guides/proto3/#oneof">protobuf&rsquo;s
oneof</a> is implemented by
<code>protoc</code> and <code>protoc-gen-go</code>. Surprisingly, the naming convention it uses is
<em>almost</em> identical to mine. This protobuf definition</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-proto" data-lang="proto"><span style="display:flex;"><span><span style="color:#66d9ef">message</span> <span style="color:#a6e22e">Msg</span> {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">oneof</span> payload {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    A a <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    B b <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>  }<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>is implemented using nearly the same naming convention.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">isMsg_Payload</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">isMsg_Payload</span>()
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="oneoflint">oneoflint</h2>
<p>Welcome <a href="https://gihub.com/gomoni/sumlint/cmd/oneoflint">oneoflint</a>. This
linter matches protobuf&rsquo;s <code>oneof</code> pattern and provides an exhaustive checks for code implementing the protobuf.</p>
<p>Because the naming convention is almost the same, the two linters
shares the same code (and bugs). They are exposed as two binaries, so everyone can be opinionated and there is not configuration. It is exactly how a proper Go tooling should be.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">Sum</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">analysis</span>.<span style="color:#a6e22e">Analyzer</span>{
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Name</span>: <span style="color:#e6db74">&#34;sumlint&#34;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Run</span>:  <span style="color:#a6e22e">analyzer</span>{<span style="color:#a6e22e">prefix</span>: <span style="color:#e6db74">&#34;Sum&#34;</span>}.<span style="color:#a6e22e">run</span>,
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">Oneof</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">analysis</span>.<span style="color:#a6e22e">Analyzer</span>{
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Name</span>: <span style="color:#e6db74">&#34;oneoflint&#34;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Run</span>:  <span style="color:#a6e22e">analyzer</span>{<span style="color:#a6e22e">prefix</span>: <span style="color:#e6db74">&#34;is&#34;</span>}.<span style="color:#a6e22e">run</span>,</span></span></code></pre></div>
<h2 id="be-honest">Be honest</h2>
<blockquote>
<p>There is no configuration at all.
Be honest!
I am being honest!
So what about CLI arguments?
Okay, there is a verbose flag.</p>
</blockquote>
<p>When developing a linter, there&rsquo;s nothing worse than starting with an empty output when it was supposed to work. The worst part is that, due to the clever caching of the Go toolchain, you may see your tool doing something, but then nothing at the subsequent step. The cache simply assumes that linters are bug-free, but that&rsquo;s not the case during development. After some back-and-forth with Copilot and the package documentation, I figured out how to properly add a flag.</p>
<p>This defines the <code>flag.FlagSet</code>. Each one has a proper name so that each linter can have its own configuration.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">init</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">verboseFlag</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fs1</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">flag</span>.<span style="color:#a6e22e">NewFlagSet</span>(<span style="color:#a6e22e">Sum</span>.<span style="color:#a6e22e">Name</span>, <span style="color:#a6e22e">flag</span>.<span style="color:#a6e22e">ExitOnError</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fs1</span>.<span style="color:#a6e22e">BoolVar</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">verboseFlag</span>, <span style="color:#e6db74">&#34;verbose&#34;</span>, <span style="color:#66d9ef">false</span>, <span style="color:#e6db74">&#34;enable verbose output&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Sum</span>.<span style="color:#a6e22e">Flags</span> = <span style="color:#f92672">*</span><span style="color:#a6e22e">fs1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fs2</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">flag</span>.<span style="color:#a6e22e">NewFlagSet</span>(<span style="color:#a6e22e">Oneof</span>.<span style="color:#a6e22e">Name</span>, <span style="color:#a6e22e">flag</span>.<span style="color:#a6e22e">ExitOnError</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fs2</span>.<span style="color:#a6e22e">BoolVar</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">verboseFlag</span>, <span style="color:#e6db74">&#34;verbose&#34;</span>, <span style="color:#66d9ef">false</span>, <span style="color:#e6db74">&#34;enable verbose output&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Oneof</span>.<span style="color:#a6e22e">Flags</span> = <span style="color:#f92672">*</span><span style="color:#a6e22e">fs2</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<blockquote>
<p>Detected shared state anomaly: both FlagSet instances bind to identical bool storage (verboseFlag)</p>
</blockquote>
<p>This is a valid comment from our AI overlord. In fact I asked it to output it in less human form for an effect. In this case the value is accessed using <a href="https://pkg.go.dev/flag#FlagSet.Lookup"><code>Lookup</code></a> method and not by reading the variable.</p>
<p>Usage on a command line is simple. The FlagSet name is a prefix of an argument.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ go vet -vettool<span style="color:#f92672">=</span>/path/to/sumlint -sumlint.verbose .</span></span></code></pre></div>
<h2 id="unit-tests">Unit tests</h2>
<p>Unit testing the code with <code>golang.org/x/tools/go/analysis/analysisTest</code> proved challenging. To use it, you need to put the Go source code into <code>testdata/src/$package</code> and let the analyzer compare the results with the expected data.</p>
<p>This is great as long as it works. However, if the data does not match, <code>analysistest</code> becomes very difficult to understand.</p>
<p>First, you need to assert the list of exported <a href="https://pkg.go.dev/golang.org/x/tools/go/analysis#Fact">Facts</a>. In the case of <code>oneoflint</code>, this is a list of structs that implement the isMsg_Payload interface.</p>
<p>The second assertion is the error message that is supposed to be printed by the <code>linter</code>. Which gave this test case.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">one_of</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">isMsg_Payload</span> <span style="color:#66d9ef">interface</span> { <span style="color:#75715e">//want isMsg_Payload:`one_of\.Msg_A,one_of\.Msg_B`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">isMsg_Payload</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Msg_A</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">A</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">A</span> <span style="color:#e6db74">`protobuf:&#34;bytes,1,opt,name=a,proto3,oneof&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Msg_B</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">B</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">B</span> <span style="color:#e6db74">`protobuf:&#34;bytes,2,opt,name=b,proto3,oneof&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#f92672">*</span><span style="color:#a6e22e">Msg_A</span>) <span style="color:#a6e22e">isMsg_Payload</span>() {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#f92672">*</span><span style="color:#a6e22e">Msg_B</span>) <span style="color:#a6e22e">isMsg_Payload</span>() {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">process</span>(<span style="color:#a6e22e">msg</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Msg</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">msg</span>.<span style="color:#a6e22e">GetPayload</span>().(<span style="color:#66d9ef">type</span>) { <span style="color:#75715e">// want `non-exhaustive type switch on isMsg_Payload: missing cases for: one_of.Msg_B`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Msg_A</span>:
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>	}</span></span></code></pre></div>
<h2 id="integration-testing">Integration testing</h2>
<p>To my surprise unit testing was not enough. While <code>sumlint</code> worked like a charm, the <code>oneoflint</code> failed and printed nothing. This is when the <code>verbose</code> flag goes handy. It allows to print just enough state of a linter to be able to diagnose a problem.</p>
<p>Much to my surprise, unit testing was not enough. <code>sumlint</code> worked like a charm, but <code>oneoflint</code> failed and printed nothing. This is when the <code>verbose</code> flag comes in handy. It is supposed to print enough information about the linter to diagnose a problem.</p>
<p>With enough logs, Copilot was able to suggest a <a href="https://github.com/gomoni/sumlint/commit/ab0f702c8b17c513fb8fde00a08854b2ed0da142#diff-c925c91196efe3f3ff9e0a21a2766cd48bab478f47e1cb0affee2dc3286a2f76R305">fix</a>. The issue was related to the fact that the proto interface was not exported, and Copilot suggested adding a new pass for this case.</p>
<p>However, my trust in unit tests was shaken. The first problem I had with this approach was that the analyzer test could not load the external package. The source code for the unit test was generated by <code>protoc</code>, but due to its inability to import non-standard library packages, it had to be manually changed. Of course, not being able to check a real world code is not good.</p>
<p>The <a href="https://github.com/gomoni/sumlint/tree/main/test">github.com/gomoni/sumlint/test</a> package contains realistic test data with a file generated by <code>protoc</code>. The test file then imports real Go code, tests calls to <code>go vet</code> via <code>os/exec</code>. And compares the results.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">test</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;github.com/gomoni/sumlint/test/one_of&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">oneofTest</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">oneofTest</span>) <span style="color:#a6e22e">good</span>(<span style="color:#a6e22e">msg</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">one_of</span>.<span style="color:#a6e22e">Msg</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">msg</span>.<span style="color:#a6e22e">GetPayload</span>().(<span style="color:#66d9ef">type</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">one_of</span>.<span style="color:#a6e22e">Msg_A</span>:
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">one_of</span>.<span style="color:#a6e22e">Msg_B</span>:
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">oneofTest</span>) <span style="color:#a6e22e">noDefault</span>(<span style="color:#a6e22e">msg</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">one_of</span>.<span style="color:#a6e22e">Msg</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">msg</span>.<span style="color:#a6e22e">GetPayload</span>().(<span style="color:#66d9ef">type</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">one_of</span>.<span style="color:#a6e22e">Msg_A</span>:
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">one_of</span>.<span style="color:#a6e22e">Msg_B</span>:
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">oneofTest</span>) <span style="color:#a6e22e">noB</span>(<span style="color:#a6e22e">msg</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">one_of</span>.<span style="color:#a6e22e">Msg</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">payload</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">msg</span>.<span style="color:#a6e22e">GetPayload</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">payload</span>.(<span style="color:#66d9ef">type</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">one_of</span>.<span style="color:#a6e22e">Msg_A</span>:
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>In other words, integration test</p>
<ol>
<li>build linters</li>
<li><code>cd test</code></li>
<li>run <code>go vet -vettool=/path/to/oneoflint .</code></li>
<li>compare the stderr with expected output</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ <span style="color:#f92672">(</span>cd test; go vet -vettool<span style="color:#f92672">=</span>/path/to/oneoflint .<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># github.com/gomoni/sumlint/test</span>
</span></span><span style="display:flex;"><span>./oneof.go:16:2: missing default <span style="color:#66d9ef">case</span> on isMsg_Payload: code cannot handle nil interface
</span></span><span style="display:flex;"><span>./oneof.go:24:2: non-exhaustive type switch on isMsg_Payload: missing cases <span style="color:#66d9ef">for</span>: github.com/gomoni/sumlint/test/one_of.Msg_B</span></span></code></pre></div>
]]></content:encoded></item><item><title>Sumlint: Having sum types in Go</title><link>https://vyskocil.me/blog/sumlint-having-sum-types-in-go/</link><pubDate>Mon, 13 Oct 2025 15:51:33 +0200</pubDate><guid>https://vyskocil.me/blog/sumlint-having-sum-types-in-go/</guid><description>A sum type (also known as enum / tagged union / one of / disjoint union) allows a type to be exactly one of several fixed alternatives. Functional languages treat this as a first-class concept with pattern matching and exhaustive checking. Go offers almost everything, but does …</description><content:encoded><![CDATA[<p>A sum type (also known as enum / tagged union / one of / disjoint union) allows a type to be
exactly one of several fixed alternatives. Functional languages treat this as a
first-class concept with pattern matching and exhaustive checking. Go offers <em>almost</em> everything, but does not. My little linting tool
<a href="https://github.com/gomoni/sumlint">sumlint</a> aims to bridge part of that gap
for Go.</p>
<h1 id="introduction">Introduction</h1>
<p>This concept exists in many functional languages. Take <a href="https://elm-lang.org/try">Elm</a>, for example</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elm" data-lang="elm"><span style="display:flex;"><span><span style="color:#f92672">import </span><span style="color:#a6e22e">Html</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#66d9ef">Shape</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">=</span> <span style="color:#66d9ef">Circle</span> <span style="color:#66d9ef">Float</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">|</span> <span style="color:#66d9ef">Point</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>area <span style="color:#a6e22e">:</span> <span style="color:#66d9ef">Shape</span> <span style="color:#a6e22e">-&gt;</span> <span style="color:#66d9ef">Float</span>
</span></span><span style="display:flex;"><span>area shape <span style="color:#a6e22e">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> shape <span style="color:#66d9ef">of</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">Circle</span> r <span style="color:#a6e22e">-&gt;</span>
</span></span><span style="display:flex;"><span>            pi <span style="color:#a6e22e">*</span> r <span style="color:#a6e22e">*</span> r
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">Point</span> <span style="color:#a6e22e">-&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">main </span><span style="color:#a6e22e">=</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">let</span> shape <span style="color:#a6e22e">=</span> <span style="color:#66d9ef">Point</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">in</span>
</span></span><span style="display:flex;"><span>  area shape
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">|&gt;</span> <span style="color:#66d9ef">Debug</span><span style="color:#a6e22e">.</span>toString
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">|&gt;</span> <span style="color:#66d9ef">Html</span><span style="color:#a6e22e">.</span>text</span></span></code></pre></div>
<p>In this case, an idiomatic Go implementation would be straightforward.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Areaer</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Area</span>() <span style="color:#66d9ef">float64</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Point</span> <span style="color:#66d9ef">struct</span> {}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">p</span> <span style="color:#a6e22e">Point</span>) <span style="color:#a6e22e">Area</span>() <span style="color:#66d9ef">float64</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Consider creating a new case in Elm.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elm" data-lang="elm"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#66d9ef">Shape</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">=</span> <span style="color:#66d9ef">Circle</span> <span style="color:#66d9ef">Float</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">|</span> <span style="color:#66d9ef">Point</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">|</span> <span style="color:#66d9ef">Rectangle</span> <span style="color:#66d9ef">Float</span> <span style="color:#66d9ef">Float</span></span></span></code></pre></div>
<p>This results in a compile error that leads the developer to all places where this case must be handled. Additionally, using interfaces can be impractical when the underlying structs have different layouts.</p>
<p>This <code>case</code> does not account for all possibilities.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>10|&gt;    case shape of
</span></span><span style="display:flex;"><span>11|&gt;        Circle r -&gt;
</span></span><span style="display:flex;"><span>12|&gt;            pi * r * r
</span></span><span style="display:flex;"><span>13|&gt;        Point -&gt;
</span></span><span style="display:flex;"><span>14|&gt;            0
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Missing possibilities include:</span></span></code></pre></div>
<h2 id="what-have-go-ever-done-for-us">What have Go ever done for us?</h2>
<blockquote>
<p>Interfaces? Generics? Type switches?</p>
<p>All right, but apart of interfaces, generics and type switches?</p>
<p>Iterators?</p>
<p>Iterators? Oh shut up!</p>
</blockquote>
<p>Go itself seems to be very close to having sum types. The following code is valid Go code that looks almost exactly like Elm examples.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Shape</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Circle</span> | <span style="color:#a6e22e">Point</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<blockquote>
<p>Excellent proposal, sir! There are only two problems.
The first is this is a generic type constraint only.
And the second is this is only a generic type constraint.</p>
<p>Technically this is a single problem, but I considered it to be
so important, that I mentioned it twice!</p>
</blockquote>
<p>In other words, if you declare a variable of type Shape, the Go compiler will complain.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>cannot use type Shape outside a type constraint: interface contains type constraints</span></span></code></pre></div>
<p>However, since Go 1, there has been a nearly perfect construct in Go: a type switch. However, as this is Go, there is no way to perform advanced pattern matching; a switch can be based on types or values, but not in a single statement.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">shape</span> <span style="color:#a6e22e">Shape</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">area</span> <span style="color:#66d9ef">float64</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">x</span>.(<span style="color:#a6e22e">Shape</span>) {
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Point</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">area</span> = <span style="color:#ae81ff">0.0</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Circle</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">area</span> = <span style="color:#a6e22e">math</span>.<span style="color:#a6e22e">Pi</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">x</span>.<span style="color:#a6e22e">R</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">x</span>.<span style="color:#a6e22e">R</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Except for exhaustiveness. The Go compiler is unaware of which structs implement which interfaces, so adding a new case has no impact. The code will compile and crash at runtime. While possible, type switches are not idiomatic Go.</p>
<h2 id="burntsushigo-sumtype">BurntSushi/go-sumtype</h2>
<p>I am not the first person to try to solve this problem.Take
<a href="https://github.com/BurntSushi/go-sumtype">https://github.com/BurntSushi/go-sumtype</a>
as an example. BurntSushi is a Rust developer and the author of <code>ripgrep</code>, among other projects. So it&rsquo;s not surprising that he&rsquo;s interested in having at least one type of sum type available in Go.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">//go-sumtype:decl MySumType</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MySumType</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">sealed</span>()
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ go-sumtype mysumtype.go
</span></span><span style="display:flex;"><span>mysumtype.go:18:2: exhaustiveness check failed <span style="color:#66d9ef">for</span> sum type <span style="color:#e6db74">&#39;MySumType&#39;</span>: missing cases f</span></span></code></pre></div>
<h2 id="can-we-do-it-better">Can we do it better?</h2>
<p>The Rust code looks like this, and this approach is considered normal and idiomatic.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#75715e">#[derive(Debug)]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">struct</span> <span style="color:#a6e22e">Rectangle</span> {
</span></span><span style="display:flex;"><span>    width: <span style="color:#66d9ef">u32</span>,
</span></span><span style="display:flex;"><span>    height: <span style="color:#66d9ef">u32</span>,
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Not in Go. The magic comments commonly seen in Go source code are directives of the compiler itself. Such as <code>go:embed</code>, <code>go:build</code>, <code>go:tool</code>. The only exception is <code>nolint</code>, which is used to turn off false positives of a linter.</p>
<p>In Go world, there is actually a great precedent. The Test, Bench, and Fuzz prefixes are merely conventions that have an actual meaning for testing code. So let&rsquo;s have a linter that implements the following simple convention</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// SumFoo declares a sum type, which is recognized by sumlint</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">SumFoo</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">sumFoo</span>()
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>The rules are simple.</p>
<blockquote>
<p>The Sum interface must be public and have <code>Sum</code> as a prefix.</p>
<p>It must implement a private method with the same name and the <code>sum</code> prefix.</p>
</blockquote>
<p>Each definition of this type is considered a sum type. Sumlint detects all structs that implement the interface and enforces exhaustiveness in type switches. No magic comments are needed, as the special status is visible from the type name itself.</p>
<h2 id="an-example">An example</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// SumFoo declares a sum type, which is recognized by sumlint</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">SumFoo</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">sumFoo</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// A is an implementation of a SumFoo</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">A</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">A</span>) <span style="color:#a6e22e">sumFoo</span>() {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// B is an implementation of a SumFoo</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">B</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">B</span>) <span style="color:#a6e22e">sumFoo</span>() {}</span></span></code></pre></div>
<p>There are three cases. The first one is ideal because all cases are covered. Unlike in other languages, Go requires a default case to handle the nil interface case. This particular property may clash with a nilness or nilaway analyzers.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">good</span>(<span style="color:#a6e22e">x</span> <span style="color:#a6e22e">SumFoo</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">x</span>.(<span style="color:#66d9ef">type</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">A</span>, <span style="color:#a6e22e">B</span>:
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Removing <code>B</code> case leads to an error.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// missing B</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">noB</span>(<span style="color:#a6e22e">x</span> <span style="color:#a6e22e">SumFoo</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">x</span>.(<span style="color:#66d9ef">type</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">A</span>:
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>go vet -vettool<span style="color:#f92672">=</span><span style="color:#e6db74">${</span>HOME<span style="color:#e6db74">}</span>/go/bin/sumlint .
</span></span><span style="display:flex;"><span>./src.go:29:2: non-exhaustive type switch on SumFoo: missing cases <span style="color:#66d9ef">for</span>: github.com/gomoni/sumlint/tests.B</span></span></code></pre></div>
<p>As well as a missing default. This case is intended to handle the <code>nil</code> interface value, which can’t be easily work-arounded in a Go.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// missing default, is reported</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">noDefault</span>(<span style="color:#a6e22e">x</span> <span style="color:#a6e22e">SumFoo</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">x</span>.(<span style="color:#66d9ef">type</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">A</span>, <span style="color:#a6e22e">B</span>:
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>go vet -vettool<span style="color:#f92672">=</span><span style="color:#e6db74">${</span>HOME<span style="color:#e6db74">}</span>/go/bin/sumlint .
</span></span><span style="display:flex;"><span>./src.go:23:2: missing default <span style="color:#66d9ef">case</span> on SumFoo: code cannot handle nil interface</span></span></code></pre></div>
<p>Edited on 2021-10-18: fix the grammar, improved wording.</p>
]]></content:encoded></item><item><title>Updating Hugo Templates</title><link>https://vyskocil.me/blog/updating-hugo-templates/</link><pubDate>Mon, 23 Jun 2025 15:43:42 +0200</pubDate><guid>https://vyskocil.me/blog/updating-hugo-templates/</guid><description>This is my personal log detailing how I updated the blog to support the latest version of Hugo.&amp;#xA;I rarely write content here. Perhaps I should work on making writing a habit. The blog itself does uses hugo to generate the HTML from Markdown input. This works well. However. Hugo is …</description><content:encoded><![CDATA[<p>This is my personal log detailing how I updated the blog to support the latest
version of Hugo.</p>
<p>I rarely write content here. Perhaps I should work on making
writing a habit. The blog itself does uses <a href="https://gohugo.io">hugo</a> to
generate the HTML from Markdown input. This works well. However. Hugo is
<em>huge</em> big, super flexible and makes incompatible changes from time to time.</p>
<pre><code>ERROR deprecated: .Site.Author was deprecated in Hugo v0.124.0 and subsequently
removed. Implement taxonomy 'author' or use .Site.Params.Author instead.
</code></pre>
<p>As I no expert on Hugo and only use it occasionally, I must admit that such
errors do not help me much. However, it turns out that this is a problem that
can be solver with AI. And in fact ChatGPT provided me with some useful answers
regaring taxonomy and <code>.Site.Params.Author</code>.</p>
<h2 id="siteauthor">.Site.Author</h2>
<p>I fixed an error in
<a href="https://codeberg.org/vyskocilm/miblog/commit/f5354a33037ffc9773ff15521b1f90cef83eebe0#diff-c2996dfd59d9c152da4b8148714e209d60f21ffa">config.toml</a>
by simply moving data around, because <em>taxonomy</em> is not needed for a single
user blog.</p>
<pre><code class="language-diff">- [author]
-   author = &quot;Michal Vyskocil&quot;
-   profile = &quot;I am software developer and a former field manager with 12+ years of experience, Prague, Czech Republic&quot;

[Params]
+   author = &quot;Michal Vyskocil&quot;
+   profile = &quot;I am software developer and a former field manager with 12+ years of experience, Prague, Czech Republic&quot;
</code></pre>
<p>Modifying the <code>config.toml</code> is only a half of the story. The way the Markdown is rendered is the responsibility
of the used template. This also needs to be updated. This blog uses
(miblog-hestia-pure)[https://codeberg.org/vyskocilm/miblog-hestia-pure], which
is a fork of
<a href="https://github.com/diwao/hestia-pure">https://github.com/diwao/hestia-pure</a>. Therefore with the change above, updating the template was <a href="https://codeberg.org/vyskocilm/miblog-hestia-pure/commit/2e0ddcb89ea6c2dd317064b810c99d8869c825c2">straightforward</a>.</p>
<pre><code class="language-diff">  &lt;div class=&quot;author__info pure-u-4-5 pure-u-md-7-8&quot;&gt;
-   {{ if and (.Site.Author.author) (ne .Site.Author.author &quot;&quot;) }}
-   &lt;h3&gt;{{ .Site.Author.author }}&lt;/h3&gt;
+   {{ if and (.Site.Params.author) (ne .Site.Params.author &quot;&quot;) }}
+   &lt;h3&gt;{{ .Site.Params.author }}&lt;/h3&gt;
    {{ else }}
</code></pre>
<h2 id="truncate-output">Truncate output</h2>
<p>Hugo added a <a href="https://gohugo.io/content-management/summaries/">new divider for
summaries</a>, which is HTML
comment `</p>
<p>`. This broke the index.html and the RSS output.
I only realised this once I put the blog online 🤦. As I had no intention of adding the marker to all the articles I had already written, the <a href="https://codeberg.org/vyskocilm/miblog-hestia-pure/commit/6cdab1202c885e322820720e59e185a4fad5eb19">solution</a> was to truncate each article to 280 characters.</p>
<pre><code class="language-diff">-   &lt;p &gt;{{ if .IsPage }}{{ .Summary }}...{{ else }}{{ .Site.Params.description }}{{ end }}&lt;/p&gt;
+   &lt;p &gt;{{ if .IsPage }}{{ .Summary | plainify | truncate 280 }}...{{ else }}{{ .Site.Params.description }}{{ end }}&lt;/p&gt;
</code></pre>
<h2 id="fixing-rss">Fixing RSS</h2>
<p>Changing the output of RSS was more work. This was because I basically had to fork the <a href="https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/rss.xml">default RSS template</a>. And then I had to adapt it to <a href="https://codeberg.org/vyskocilm/miblog-hestia-pure/commit/9644b00fb47bd7401999860b5058a06372472eeb">my needs</a>.</p>]]></content:encoded></item><item><title>Blink.cmp</title><link>https://vyskocil.me/blog/blink.cmp/</link><pubDate>Thu, 08 May 2025 21:31:50 +0200</pubDate><guid>https://vyskocil.me/blog/blink.cmp/</guid><description>In the Lazy Neovim Configuration I wrote a bit about the neovim configuration I use. While most of my configuration has not changed, I learned about a new completion plugin blink.cmp. It got recommended by kulala-ls and got recently introduced in a kickstart.nvim.&amp;#xA;The …</description><content:encoded><![CDATA[<p>In the <a href="/blog/lazy-neovim-configuration.html">Lazy Neovim Configuration</a> I
wrote a bit about the neovim configuration I use. While most of my
configuration has not changed, I learned about a new completion plugin
<a href="https://cmp.saghen.dev/">blink.cmp</a>. It got recommended by
<a href="https://github.com/mistweaverco/kulala-ls?tab=readme-ov-file#configuration">kulala-ls</a>
and got recently introduced in a
<a href="https://github.com/nvim-lua/kickstart.nvim">kickstart.nvim</a>.</p>
<h2 id="the-installation">The installation</h2>
<p>Is easy with <a href="https://lazy.folke.io/">lazy.nvim</a>.</p>
<pre><code class="language-lua">{
  'saghen/blink.cmp',
  -- optional: provides snippets for the snippet source
  dependencies = { 'rafamadriz/friendly-snippets' },

  -- use a release tag to download pre-built binaries
  version = '1.*',
}
</code></pre>
<h2 id="the-configuration">The configuration</h2>
<p>While my complete configuration file
<a href="https://codeberg.org/vyskocilm/dotfiles/src/branch/main/.config/nvim/lua/plugins/blink.lua">blink.lua</a>
is a big one, the plugin is actually pretty easy to install and start with. What
is really nice is that it offers a <em>lot</em> of completions by default. The
<code>&quot;lsp&quot;</code>, <code>&quot;buffer&quot;</code>, <code>&quot;path&quot;</code> are all builtin. The <code>&quot;snippets&quot;</code> too, just
require an external plugin with defined snippets.</p>
<p>What I really like are the defaults. The <code>keymap = { preset = &quot;default&quot; }</code> uses
the <code>['&lt;C-y&gt;'] = { 'select_and_accept' },</code> so <code>Ctrl+y</code> accepts the completion
as every vim nerd expects.</p>
<p>However when needed, it is easy to configure the plugin to the linking. So I
enabled
<a href="https://cmp.saghen.dev/configuration/completion.html#documentation">documentation</a>
to be opened by default or to enable <code>ghost_text</code> so the completion will be
visible as a virtual text in the buffer. The documentation is simply amazing.</p>
<p>What really amazed me was a <a href="https://cmp.saghen.dev/configuration/completion.html#menu-draw">menu-draw</a> configuration. What was problem and I did not appreciated was it does not show the completion source by default. The configuration was pretty straightforward. First add a <code>source_name</code> into columns.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span>columns <span style="color:#f92672">=</span> { { <span style="color:#e6db74">&#34;kind_icon&#34;</span> }, { <span style="color:#e6db74">&#34;label&#34;</span>, <span style="color:#e6db74">&#34;label_description&#34;</span>, gap <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span> }, { <span style="color:#e6db74">&#34;source_name&#34;</span> } },</span></span></code></pre></div>
<p>And then define how the <code>source_name</code> will look like. This define max width,
the highlight class and most importantly the content of the field like <code>[LSP]</code>
or <code>[Buffer]</code> or <code>[Copilot]</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span>components <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>	source_name <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>		width <span style="color:#f92672">=</span> { max <span style="color:#f92672">=</span> <span style="color:#ae81ff">30</span> },
</span></span><span style="display:flex;"><span>		text <span style="color:#f92672">=</span> <span style="color:#66d9ef">function</span>(ctx)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;[&#34;</span> <span style="color:#f92672">..</span> ctx.source_name <span style="color:#f92672">..</span> <span style="color:#e6db74">&#34;]&#34;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">end</span>,
</span></span><span style="display:flex;"><span>		highlight <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;BlinkCmpSource&#34;</span>,
</span></span><span style="display:flex;"><span>	},
</span></span><span style="display:flex;"><span>},</span></span></code></pre></div>
<p>Support for snippets is builtin, given the external plugin is installed. Either
luasnip or mini.snippets are supported.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span>snippets <span style="color:#f92672">=</span> { preset <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;luasnip&#34;</span> },</span></span></code></pre></div>
<p>And besides documentation blink has a completion for function signatures, so it can provide a context help when using a function too.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span>signature <span style="color:#f92672">=</span> { enabled <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span> },</span></span></code></pre></div>
<h2 id="to-rust-or-not-to-rust">To Rust or not to Rust</h2>
<p>The name blink does refer to the speed plugin. So except native engine written
in Lua, there is a Rust based one claiming a significant speedup. The Rust
based <a href="https://github.com/saghen/frizbee">frizbee</a> is a SIMD fuzzy matcher
which achieves ~6x the performance of fzf while ignoring typos.</p>
<ol>
<li><a href="https://github.com/Saghen/blink.cmp/tree/main/lua/blink/cmp/fuzzy/lua"><code>lua</code></a></li>
<li><a href="https://github.com/Saghen/blink.cmp/tree/main/lua/blink/cmp/fuzzy/rust"><code>rust</code></a></li>
</ol>
<p>I ended up using <code>fuzzy = { implementation = &quot;lua&quot; },</code> which is the one
Kickstart offers. It worked well for me - except the emoji and nerdfont
providers. But more specifically I do not want <code>neovim</code> plugins to download and
install random binaries from the Internet. And that&rsquo;s sadly the only one option
for a <code>frizbee</code> at the moment.</p>
]]></content:encoded></item></channel></rss>