A place where I capture raw, quick notes worth remembering.

Hello World, strings in Zig

I am doing the Exercism exercises for Zig and decided to document my progress since, with every exercise, I learn new and interesting information.

The first exercise is doing a “Hello World” and the end solution is simple:

pub fn hello() []const u8 {
    return "Hello, World!";

The interesting part is that Zig represents string literals as []const u8, which is an array of bytes.

const foo = "Hello" is (almost) the same as const foo = [_]u8{ 'H', 'e', 'l', 'l', 'o' }; Let’s break it down:

  • const means it’s a constant value or immutable.
  • [_]u8 means it’s an array of bytes.
  • The compiler will infer the [_] to [5].

Where the string uses double quotes (“H”), and the individual characters (‘H’) use single quotes.

But there is more because a string also contains a sentinel value. A sentinel value is something that indicates the end of a sequence. A popular choice of a sentinel value is the value 0, also called the null character.

Zig supports sentinel-terminated arrays, slices, and pointers:

//.    This is taken from Ziglings, exercise 76.
//     const a: [4:0]u32       =  [4:0]u32{1, 2, 3, 4};
//     const b: [:0]const u32  = &[4:0]u32{1, 2, 3, 4};

Array a stores five values, the last of which is a 0. Array b is only allowed to point to zero-terminated arrays.

Now we can determine the actual type of a string in Zig:

@TypeOf("foo") == *const [3:0]u8

Translated to English, a string is a “constant pointer to a null-terminated fixed-size array of u8”.

Now, why would you still have a 0 at the end when you know the size of the array? That’s because Zig strings are compatible with C strings, which are also null-terminated.

If you want to learn more about strings, I can recommend the Zig / Strings in 5 minutes article.


April 15, 2024


Kaizen Principle

Kaizen is a Japanese term for “continuous improvement”, the Kaizen principle is the idea that small, continuous changes can lead to significant improvements.

I’m often reminded of this quote by one of my heroes Joe Armstrong, who phrased it as follows:

Make it work, then make it beautiful. Then, if you really, really have to, make it fast. 90 percent of the time, if you make it beautiful, it will already be fast. So, really, just make it beautiful!


April 14, 2024

elixir asdf

Install Elixir with asdf

The following is what I use to setup Elixir on my machines, where I want to make sure that I can jump to all definitions of Elixir library functions.

First, I install asdf. I go the Homebrew and Fish route, but you can also do it from the source and use your shell of choice.

Before I install Erlang, I make sure to set the right flags, because I don’t want Java or build any GUI stuff:

KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac --without-wx"

After that, I go to the asdf-erlang repository, check that I have all the build requirements and install Erlang with:

asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git

# check available Erlang versions
asdf list all erlang

# install the latest version
asdf install erlang 26.2.4

# enable it globally
asdf global erlang 26.2.4

Now, let’s install Elixir. Go to the asdf-elixir repository, make sure you have unzip installed, and do the following.

asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git

# check all available versions
asdf list all elixir

# install the latest version, but! Install by source, so we can jump to the definition of Elixir functions
asdf install elixir ref:v1.16.2

# enable elixir globally
asdf global elixir ref:v1.16.2

Now, it says it in the comments, but the important difference is that I install Elixir by reference instead of the pre-compiled versions because I also like to be able to jump to definitions of Elixir library functions.


Switching between devices with Syncthing

I have a desktop machine running Linux and a Macbook running MacOS, and I often switch between devices while working on a project.

Now, I can sync states using Git, but I often find that doing so makes me create pointless commits just to sync states.

That’s where I started using Syncthing on all my devices. I’ll leave installing Syncthing for you, the reader, but there is one handy trick I think you need to know.

If you create a .stignore file in your project root, and you add:

#include .gitignore

It will take your .gitnore and exclude the items in it from being synced. This is helpful for reducing the size of the sync but also means that you won’t sync build artifacts, which may not be compatible across devices.

Try it out!


March 25, 2024

zig testing

Test selection with Zig

With Zig, I usually run zig build test to run all the tests. However, sometimes, I need to run a single test, and that option is not available through the build command.

In this case, I learned you can use the zig test command along with the --test-filter option.

For example, to test a single test named “lexer initialization” located in src/lexer/lexer.zig, you can run zig test --test-filter “lexer initialization” src/lexer/lexer.zig. It’s worth noting that the test filter doesn’t have to be the full name of the test to be able to find it.


February 23, 2024


Docker completions in Fish

To get Docker completions for the Fish shell, all you need to run is:

docker completion fish > ~/.config/fish/completions/docker.fish

This will not only give you completions for every command, but also completions on containers.


December 10, 2023

clojure macos lisp

Clojure on MacOS

To install the latest version of Clojure on MacOS, you first need to install Java, after which you can install Clojure.

To install Java, I use Temurin and pick the latest LTS (Long-Term Support) version.

Assuming you already have Homebrew:

brew install temurin
brew install clojure/tools/clojure

This will also install rlwrap which makes sure that you can use the arrow keys in the REPL.

I also use neil for some common aliases, for example, to create a new project.

brew install babashka/brew/neil

Now you can run the neil new command to create a new Clojure repository.


August 23, 2023

vim wsl

Copy/Paste for Neovim in WSL

If you want a seamless copy/paste experience on Neovim in WSL there is a newly recommended method for doing so, without having to install extra software.

Paste the following Lua snippet in your configuration and "+y to yank the selected text into your global register. Similarly, use "+p to paste.

if vim.fn.has "wsl" == 1 then
  vim.g.clipboard = {
    name = "WslClipboard",
    copy = {
      ["+"] = "clip.exe",
      ["*"] = "clip.exe",
    paste = {
      ["+"] = 'powershell.exe -c [Console]::Out.Write($(Get-Clipboard -Raw).tostring().replace("`r", ""))',
      ["*"] = 'powershell.exe -c [Console]::Out.Write($(Get-Clipboard -Raw).tostring().replace("`r", ""))',
    cache_enabled = 0,


August 22, 2023

windows wsl

Faster key repeat on Windows

With WSL, Windows can be a great developer experience. The one thing which I did find lacking, especially when moving around in Vim, is that the maxixmum key repeat rate in the configuration panel is too slow.

I was able to fix that with the following settings in Regedit:

[HKEY_CURRENT_USER\Control Panel\Accessibility\Keyboard Response]

You have to login and out again for it to take effect.


August 22, 2023

vim linux

Latest Neovim on Ubuntu

I’m using Ubuntu for my development environment, and Ubuntu does not have the latest Neovim in their repository.

That’s why I choose to install the last version by source. The steps I take are:

wget https://github.com/neovim/neovim/releases/download/v0.9.1/nvim-linux64.tar.gz
tar xfz nvim-linux64.tar.gz
sudo mv nvim-linux64 /opt/nvim

Now you have to make sure that /opt/nvim/bin is part of your path. If you are smart, and using the Fish shell, add this to your Fish config.

fish_add_path -aP /opt/nvim/bin

If you are on Bash:

export PATH=/opt/nvim/bin:$PATH

Enjoy your latest version of Neovim!