<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dayvster 🌊</title>
    <description>The latest articles on DEV Community by Dayvster 🌊 (@dayvster).</description>
    <link>https://dev.to/dayvster</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F274998%2F09f57f7f-daec-4303-8766-138cfd1396d9.png</url>
      <title>DEV Community: Dayvster 🌊</title>
      <link>https://dev.to/dayvster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dayvster"/>
    <language>en</language>
    <item>
      <title>My Problem With Omarchy Is What I Like About Arch</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Thu, 08 Oct 2026 09:03:40 +0000</pubDate>
      <link>https://dev.to/dayvster/my-problem-with-omarchy-is-what-i-like-about-arch-2nk8</link>
      <guid>https://dev.to/dayvster/my-problem-with-omarchy-is-what-i-like-about-arch-2nk8</guid>
      <description>&lt;p&gt;The things I like most about using Arch are also the things people keep trying to save me from. Setting something up, getting it wrong, reading the documentation and eventually understanding why it works. I didn't come to Linux just to get a different desktop. I wanted to know what my computer was doing.&lt;/p&gt;

&lt;p&gt;So when somebody packages that whole experience into a ready-made setup, I understand the appeal. But when the next promise is that an AI can handle the things I don't understand, I start wondering what kind of relationship with my computer I'm being offered. I wanted to become more capable of looking after it myself.&lt;/p&gt;

&lt;p&gt;That is my problem with &lt;a href="https://omarchy.org/" rel="noopener noreferrer"&gt;Omarchy&lt;/a&gt;. It looks good. Clearly a lot of work has gone into making the pieces fit together. But I think its pitch puts too much value on what I can avoid learning and too little on what I could learn to control. Making those decisions myself was a large part of why I wanted Arch in the first place.&lt;/p&gt;

&lt;p&gt;Yes, this is going to be an Arch user explaining why he likes configuring things. You had fair warning from the title.&lt;/p&gt;

&lt;p&gt;I've held off on writing this for a while because I really don't want to be the guy greeting someone who's excited about Linux with "you're doing it wrong." I like the momentum Omarchy has around bringing new people in. If someone installs it, enjoys it and finally feels comfortable using Linux, I'm happy about that. The last thing I want is to contribute to the negativity that makes them wonder why they bothered.&lt;/p&gt;

&lt;p&gt;So if Omarchy is how you got here, welcome. I hope you stick around. What I want to make a case for is taking the next step, getting curious about the system you've installed and finding out how much of it you can make your own. That's the part I enjoy most, and I'd hate for the promise of an easy setup to make it sound like something worth avoiding.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Wanted to Know Why
&lt;/h2&gt;

&lt;p&gt;There is something satisfying about looking at your setup and knowing why it's like that. You picked the window manager. You know where the keybindings live. That slightly odd behaviour exists because you changed something to suit the way you work, and you remember what you changed.&lt;/p&gt;

&lt;p&gt;It doesn't have to be impressive. Nobody else needs to want your configuration. You understand it, and when you want it to behave differently you have some idea of where to start.&lt;/p&gt;

&lt;p&gt;That matters more to me than having a desktop that looks good in a screenshot. I like a beautiful desktop too, obviously, I've spent enough time on &lt;a href="https://github.com/dayvster/zenkai" rel="noopener noreferrer"&gt;zenkai&lt;/a&gt;'s themes to lose any right to pretend otherwise. But I want to understand the thing underneath it.&lt;/p&gt;

&lt;p&gt;Some of that understanding comes from curiosity. Some comes from a much less noble place: something annoys you enough that you finally go and find out how to change it. You read about an option you didn't know existed, find the file that controls it, and suddenly one more part of the system makes sense.&lt;/p&gt;

&lt;p&gt;You might not have set out to learn anything that evening. You just wanted your shortcut to work. But now you know something you can use the next time you have a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Finished Desktop Is Someone Else's Starting Point
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://omarchy.org/" rel="noopener noreferrer"&gt;Omarchy's own pitch&lt;/a&gt; is a working system with the tools and details already chosen for you. It also explicitly says you're free to change everything. The &lt;a href="https://github.com/omacom/omarchy" rel="noopener noreferrer"&gt;source is available&lt;/a&gt;, so I'm not going to pretend this is a locked appliance or that installing it takes away your permission to edit a configuration file.&lt;/p&gt;

&lt;p&gt;You can use it as a starting point and learn from it. Reading a working configuration is useful. My criticism is of the direction being sold, not a claim that every Omarchy user accepts it or stops learning.&lt;/p&gt;

&lt;p&gt;But you can also stop at the finished desktop. Everything works, the choices look reasonable, and there's no particular reason to investigate what made it work. The opportunities to learn are still available. The reasons that would have pushed you into them have become less immediate.&lt;/p&gt;

&lt;p&gt;This is where I think Omarchy's pitch falls short. It puts a lot of emphasis on removing the setup, the decisions and the troubleshooting. Those can be frustrating, but treating them mainly as problems to eliminate undersells what you can learn from them. I'd like to see as much enthusiasm for helping people understand the system as there is for getting them past the need to understand it.&lt;/p&gt;

&lt;p&gt;Of course someone with work to do might happily accept that tradeoff. I do too, with plenty of tools. But it is a tradeoff, and I don't think a shorter installation tells us much about how capable we'll feel when we want something the default setup didn't anticipate.&lt;/p&gt;

&lt;p&gt;For me, though, being handed a setup where the interesting decisions have already been made can feel pretty boring, even when the result is pretty. I want to try things, disagree with the defaults and work out what I actually like. Getting to a usable desktop was never the whole project.&lt;/p&gt;

&lt;p&gt;An opinionated desktop also means adopting somebody else's preferences before you've worked out your own. That can be helpful, but it isn't automatically simpler once you start disagreeing with those choices. Now you have to learn what the underlying tools do and how the curated setup connects them. The complexity hasn't disappeared just because you didn't encounter it during installation.&lt;/p&gt;

&lt;h2&gt;
  
  
  You Can Put This Together Yourself
&lt;/h2&gt;

&lt;p&gt;Omarchy isn't black magic. There is real work in integrating a desktop and maintaining it for other people, and I don't want to dismiss that. But you don't have to recreate everything Omarchy ships to put together an Arch and Hyprland setup that works for you. You only need the parts you actually want.&lt;/p&gt;

&lt;p&gt;I think that is a much more approachable project than people make it sound. Start with a working Arch installation and follow the &lt;a href="https://wiki.hypr.land/Getting-Started/Master-Tutorial/" rel="noopener noreferrer"&gt;Hyprland tutorial&lt;/a&gt;. Get a terminal open, configure your display and choose a few keybindings. Then add the things you need: a launcher, notifications, a bar if you want one. You don't need somebody else's entire desktop before you can start using yours.&lt;/p&gt;

&lt;p&gt;Hardware can complicate things, and a first installation will take longer than your fifth. But choosing a terminal, changing a shortcut and adjusting a configuration file are things you can learn. You don't need years of Linux experience before you're allowed to try. A basic personal setup can be fairly straightforward; getting every colour and animation exactly how you want it is the part you can happily turn into a hobby.&lt;/p&gt;

&lt;p&gt;And it doesn't have to look like anybody else's. Maybe you don't want a bar. Maybe you want different shortcuts, fewer animations or a launcher that behaves differently. You get to make those decisions one at a time, and by the end you know why each piece is there.&lt;/p&gt;

&lt;p&gt;That is a practical kind of ownership. When a shortcut stops working, you know where you defined it. When you want to replace a program, you know what calls it. You can keep your configuration in Git, see what you changed and go back when an experiment doesn't work out. The time you spend setting it up buys you some independence the next time you need to change it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convenience Can Become Dependence
&lt;/h2&gt;

&lt;p&gt;For me, ownership includes being able to disagree with the people who made the tool. I want to remove something they like, keep something they don't, and fix a problem without waiting for their preferred solution. Having permission to do those things matters. Having enough understanding to actually do them is what makes that permission useful.&lt;/p&gt;

&lt;p&gt;Omarchy doesn't have to take away the first for me to worry about the second. If every change goes through a menu or an agent, you can end up with a computer you're allowed to modify but don't really know how to investigate. When something stops working, your next step is to ask the same tool for another fix.&lt;/p&gt;

&lt;p&gt;That can happen on a manually installed Arch system too. Copying commands you don't understand isn't magically educational because you typed them into a terminal. And a good graphical tool can explain what it's doing better than a page of unexplained shell commands. The interface isn't the deciding factor.&lt;/p&gt;

&lt;p&gt;Imagine replacing your launcher and finding that the old one still opens from a shortcut. If you know where the binding lives and what starts the program, that's something you can investigate. If your only approach is to ask an agent to fix it, you have to trust that it finds the actual cause rather than adds another change around it. You might get the shortcut back without getting any closer to understanding your setup.&lt;/p&gt;

&lt;p&gt;That is the loss of ownership I'm worried about. The files can still belong to you while your ability to make decisions about them depends on something else. No one has to lock you out for you to feel unable to proceed without help.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is the Same Argument I Keep Having About AI
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/blog/we-were-not-all-terrible-developers/"&gt;We Were Not All Terrible Developers&lt;/a&gt; I wrote about the idea that generating code removes the need for developers who understand it. This bothers me for the same reason. Getting a working result and learning enough to take responsibility for that result are different achievements.&lt;/p&gt;

&lt;p&gt;Omarchy makes the connection quite directly now. Its &lt;a href="https://omarchy.org/" rel="noopener noreferrer"&gt;homepage&lt;/a&gt; promotes using agents to troubleshoot and alter the system. That could be useful. An agent that helps you read a log, explains the relevant configuration and lets you check its reasoning could teach you plenty.&lt;/p&gt;

&lt;p&gt;But there's another version of that interaction where you describe the problem, accept the changes and move on because it works again. Repeat that enough times and you can accumulate a lot of changes without much understanding of how they relate to one another.&lt;/p&gt;

&lt;p&gt;And I think Omarchy leans too hard into selling that convenience. Its homepage even describes "agents that debug all issues." All issues? That is a hell of an expectation to set. I'd want to inspect what changed and test whether it addressed the cause before calling a problem solved. It's still my machine that ends up with the changes, and I still have to live with them when the first fix turns out to be incomplete.&lt;/p&gt;

&lt;p&gt;An agent being confident doesn't help me decide whether it was right. If I can't follow the explanation or recognise an unrelated change, I'm approving work I don't understand. Having a second agent approve it doesn't give me that understanding either.&lt;/p&gt;

&lt;p&gt;The next time something breaks, what have I learned that helps me fix it?&lt;/p&gt;

&lt;p&gt;This is where I get frustrated with the promise that we can skip all the difficult parts. Some difficult parts are just tedious and I'm happy to automate them. Others are where you develop the ability to recognise a bad explanation, question an assumption or work out that the proposed fix has nothing to do with the problem.&lt;/p&gt;

&lt;p&gt;You don't automatically acquire that judgement because the tool gives you a detailed answer. You develop it by working through things, checking them and sometimes finding out you were wrong. I want tools that help me do that. I get much less excited when understanding is presented as something I no longer need to bother with.&lt;/p&gt;

&lt;p&gt;Heck, use AI to help you build your setup. Have it guide you. Ask which component handles a behaviour, which file controls it and why a particular setting would change it. Ask for a link to the documentation so you can check. Make one change, try it and see whether you understand the result before moving on.&lt;/p&gt;

&lt;p&gt;You can literally tell it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Help me set up Hyprland one step at a time. Explain the choices and the commands before I run them. Show me how to check each change and undo it. I want to understand my configuration, so don't just generate a complete setup for me.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds like a great use of AI to me. You have something to ask when the documentation assumes knowledge you don't have yet, and you still do the work of making decisions and understanding what happened. The point is to become more capable as you go. If you've finished the setup and can explain how to change it yourself, you've gained more than a nice desktop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the Convenience Be Optional
&lt;/h2&gt;

&lt;p&gt;I use Qt for zenkai because I don't want to write a GUI toolkit. But when I needed to understand why the launcher was slow, I had to look into what Qt was doing. The abstraction saved me work. It didn't excuse me from understanding the behaviour my application depended on.&lt;/p&gt;

&lt;p&gt;That's what I want from my desktop too. Help me get started, but leave me better equipped to take over. A menu could show me the configuration it edits. An agent could explain a proposed change before applying it and show me how to verify it afterwards. Documentation could make replacing the default tools feel like an ordinary thing to do.&lt;/p&gt;

&lt;p&gt;These are the things I'd put at the centre of the pitch. Being able to stop relying on a convenience is a feature worth selling. I want to hear that I can learn to manage my own system, not just that there's always another thing available to manage it for me.&lt;/p&gt;

&lt;p&gt;If the only progress we celebrate is how much knowledge a tool lets us avoid, eventually the person trying to understand their computer starts looking like they're wasting time. I think that's a bad direction for Linux, and AI makes it very easy to keep moving in that direction without noticing what we've given up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Want From Linux
&lt;/h2&gt;

&lt;p&gt;I want Omarchy's newcomers to stay. I also want them to discover that the system underneath the presentation is within their reach. They can read the configuration, replace the launcher, change the shortcuts and build something that suits them better. They don't need to wait until they're experts to start becoming more independent.&lt;/p&gt;

&lt;p&gt;Whether Omarchy encourages that or makes it feel unnecessary is what will decide how I feel about its contribution to Linux. Bringing people in is good. Teaching them to depend on an agent for every unfamiliar problem would be a poor follow-up.&lt;/p&gt;

&lt;p&gt;So yes, I am critical of Omarchy and the AI pitch around it. I want more than a machine that does what I ask as long as the defaults and the agent cooperate. I want to understand enough to take over when they don't.&lt;/p&gt;

&lt;p&gt;I chose Linux because I wanted that control. A prettier way to delegate it isn't what I was looking for.&lt;/p&gt;



&lt;p&gt;Thank you for your time and patience reading this article&lt;/p&gt;

</description>
      <category>learning</category>
      <category>linux</category>
      <category>opensource</category>
      <category>archlinux</category>
    </item>
    <item>
      <title>zenkai: The App Launcher I Wrote Because I Wanted Something Fast and Beautiful</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Fri, 02 Oct 2026 09:47:37 +0000</pubDate>
      <link>https://dev.to/dayvster/zenkai-the-app-launcher-i-wrote-because-i-wanted-something-fast-and-beautiful-1gkc</link>
      <guid>https://dev.to/dayvster/zenkai-the-app-launcher-i-wrote-because-i-wanted-something-fast-and-beautiful-1gkc</guid>
      <description>&lt;p&gt;If you only came here to try it, grab it at &lt;a href="https://zenkai-launcher.netlify.app/" rel="noopener noreferrer"&gt;zenkai-launcher.netlify.app&lt;/a&gt; and the source is on &lt;a href="https://github.com/dayvster/zenkai" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. The rest of this is about why it ended up built the way it is.&lt;/p&gt;

&lt;p&gt;Every few months I install another app launcher, convince myself this is the one, and then about a week or so later I uninstall it because something small irritates me or it cannot do what I want it to do. After repeating this process about a dozen times, I finally gave up looking and wrote my own.&lt;/p&gt;

&lt;p&gt;I have always struggled with naming things and this project was no exception. After an embarrassingly long time I settled on Zenkai, which comes from my childhood obsession, Dragon Ball. "Zenkai" is a massive power up that a Saiyan obtains after a difficult battle, what a perfect name, because that is exactly the vision I had for this project. I wanted it stronger, faster and more performant after every single iteration. I wanted to dive deep into performance optimisation rabbit holes where I spend hours on something that maybe shaves off 20ms or so. I wanted this to be a part time hobby.&lt;/p&gt;

&lt;p&gt;I also wanted it to be cross-platform, because I use Linux and as any good Arch Linux user (BTW!!) I have to have a customizable performant launcher. For work I use macOS and while the native launcher there is great, I wanted to have the same experience on both systems. Windows I will be honest, I only occasionally use when I have to, so I would be lying if I said it was a high priority. Plus, Windows is pretty challenging to make a launcher for, but more on that later!&lt;/p&gt;

&lt;p&gt;I got all 3 systems working and fully supported.&lt;/p&gt;

&lt;p&gt;I also wanted it to be fast and beautiful, because I wanted to use it every single day and I wanted to enjoy using it. I wanted to be able to press a hotkey, type two letters, and have the thing I wanted already highlighted, hit enter, and have the launcher gone before I had even registered that it was ever there.&lt;/p&gt;

&lt;p&gt;That is the whole idea, and so I started building it. The first choice I had to make was Language and GUI framework. I chose Zig and Qt6!&lt;/p&gt;

&lt;h2&gt;
  
  
  Zenkai Uses QT and Zig
&lt;/h2&gt;

&lt;p&gt;I have been developing in Zig for a while now. I love the language, I love the simplicity that you can manually scale into complexity if you want to. Zig feels like C, it looks like C, but with sane memory management tools, but without a compiler that throws a hissy fit if you do not handle every single potential memory issue under the sun. It trusts you to be a craftsman, and it trusts you to know what you are doing. It is a language that does not get in your way, and it is a language that I can trust to be fast and efficient.&lt;/p&gt;

&lt;p&gt;It is as if someone handed you a well-sorted professional toolbox.&lt;/p&gt;

&lt;p&gt;Qt I did not choose on principle, I chose because I have used it before. I have shipped Qt applications professionally, I like its widget set, I like that you can make it look genuinely beautiful instead of the usual grey business-tool look that everything ends up as, and it is cross-platform. Zig I chose because I love it and it fits how I think, manual memory with sane defaults and a standard library that actually helps. So that is the entire strategic decision and it is not a particularly clever one. I picked the two things I already loved and hoped the rest would sort itself out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zenkai Is Fast
&lt;/h2&gt;

&lt;p&gt;I have been obsessed with performance for as long as I can remember. Usually there is no room for it, because whatever you are working on belongs to somebody else or somebody is eagerly waiting for it, there is a deadline, and spending a week shaving 40ms off a startup path makes you a difficult person to work with. zenkai is mine and more than that I took on the project as a hobby and not a clear goal or product that is supposed to make me money. Which means nobody is waiting on it and nothing is going to break if I spend three days on something that makes it 15ms faster. For the first time in forever I could indulge my performance obsession properly, and it felt liberating.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;zenkai &lt;span class="nt"&gt;--debug&lt;/span&gt; &lt;span class="nt"&gt;--quit-when-shown&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;zenkai] window fully visible &lt;span class="k"&gt;in &lt;/span&gt;140ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So anything under 200ms is percieved as instantaneous to a human, in zenkai's case I got it down to 140ms on my machine with plugins loaded, 100-200 apps parsed and icons loaded and rendered. &lt;/p&gt;

&lt;p&gt;But it gets even faster, if we run it without plugins and the &lt;code&gt;no-icons&lt;/code&gt; flag we get 20-70ms until fully interactive. That is frankly ridiculous considering it's a non native GUI application built with Qt and Zig, and it is a testament to the power of both.&lt;/p&gt;

&lt;p&gt;What was even more shocking was how little of that startup time was mine. Breaking the startup down put everything I actually wrote at 14.75ms-20ms of the 140, and the rest was Qt building widgets and the compositor putting pixels on the screen. &lt;/p&gt;

&lt;p&gt;All of this is very brief and I will provide a more detailed breakdown of the performance in a future post, because just that alone is a topic that deserves a longer form content type post to explain fully how I got there and what techniques I used to get there. But for now, just know that it is fast, and it is fast because I wanted it to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zenkai Is Beautiful
&lt;/h2&gt;

&lt;p&gt;There is this false dichotomy that you can either have a beautiful product or a fast one. I have always resented that idea to an extent. Because it is clearly not true. We do not have to look any further than Italian supercars, which are beautiful and fast. With enough time, love for the craft and dedication, you can have a product that is fast, well made and beautiful. It is not easy, but it is possible.&lt;/p&gt;

&lt;p&gt;That is a large part of why I made this.&lt;/p&gt;

&lt;p&gt;A launcher that looks beautiful but takes four seconds to open is not a beautiful launcher. Its slowness is part of the experience. And a launcher that starts instantly but looks like a settings dialog is not a great launcher either.&lt;/p&gt;

&lt;p&gt;I wanted both.&lt;/p&gt;

&lt;p&gt;Because to me, a product isn't truly beautiful unless it feels good to use, and it isn't truly fast if speed is the only thing it has going for it.&lt;/p&gt;

&lt;p&gt;The goal was to make zenkai look good the first time you run it, before you have configured a single thing. No config file, no wiki page, no spending half an hour tweaking colors just to make it presentable. Install it, launch it, and it should already look like something somebody cared about.&lt;/p&gt;

&lt;p&gt;But good defaults should not get in the way of making it yours. Changing the entire look of zenkai is just a CLI flag away, so it can fit into whatever setup you already have without turning customization into a project of its own.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdayvster%2Fzenkai%2Fblob%2Fmain%2Fscreenshots%2Flaunchpad.png%3Fraw%3Dtrue" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdayvster%2Fzenkai%2Fblob%2Fmain%2Fscreenshots%2Flaunchpad.png%3Fraw%3Dtrue" alt="zenkai in the launchpad theme, a full screen grid of application icons in the style of the macOS launchpad" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;launchpad, which is the one I spent the most time on and the least sure about
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;The other half of that goal is fitting in. Most people who care enough to rice their desktop have already picked a colour scheme, usually from the list everybody uses, and they do not want to pick a second one for their launcher. So all of the popular ones are in there, gruvbox, dracula, ayu, catppuccin in all four flavours, tokyo night in its four variants, nord, rose pine, kanagawa, everforest, noctis, solarized, monokai, one dark, github, and plenty more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdayvster%2Fzenkai%2Fblob%2Fmain%2Fscreenshots%2Fayu-dark.png%3Fraw%3Dtrue" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdayvster%2Fzenkai%2Fblob%2Fmain%2Fscreenshots%2Fayu-dark.png%3Fraw%3Dtrue" alt="zenkai in the ayu dark theme, near black background with muted orange selection highlight" width="900" height="600"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;ayu-dark
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdayvster%2Fzenkai%2Fblob%2Fmain%2Fscreenshots%2Fdracula.png%3Fraw%3Dtrue" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdayvster%2Fzenkai%2Fblob%2Fmain%2Fscreenshots%2Fdracula.png%3Fraw%3Dtrue" alt="zenkai in the dracula theme, dark purple background with a bright purple selection highlight" width="900" height="600"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;dracula
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdayvster%2Fzenkai%2Fblob%2Fmain%2Fscreenshots%2Fgruvbox.png%3Fraw%3Dtrue" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdayvster%2Fzenkai%2Fblob%2Fmain%2Fscreenshots%2Fgruvbox.png%3Fraw%3Dtrue" alt="zenkai in the gruvbox theme, warm dark background with orange selection highlight" width="900" height="600"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;gruvbox
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;There is a screenshot of every single one of them in the &lt;a href="https://github.com/dayvster/zenkai/tree/main/screenshots" rel="noopener noreferrer"&gt;screenshots directory on GitHub&lt;/a&gt;, if you want to pick before you install rather than after.&lt;/p&gt;

&lt;p&gt;That works because Qt styles a widget set through QSS, which is Qt’s stylesheet format, and a theme in zenkai is a data file rather than code. That is the whole reason there are 65 of them and the reason adding another one is not work. You are not writing a new screen, you are filling in colours.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdayvster%2Fzenkai%2Fblob%2Fmain%2Fscreenshots%2Fretro.png%3Fraw%3Dtrue" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdayvster%2Fzenkai%2Fblob%2Fmain%2Fscreenshots%2Fretro.png%3Fraw%3Dtrue" alt="zenkai in the retro theme, amber text on a near black background" width="900" height="600"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;retro, which is amber on black and is not a colour scheme anybody ships
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;I also did some creative ones that are not really anybody’s scheme. And the one I am genuinely proud of is the native Windows theme, because it reads your accent colour and themes itself around it, so on a machine where you have already told Windows what colour you like, zenkai just quietly agrees with you.&lt;/p&gt;

&lt;p&gt;Then there are the two &lt;code&gt;high-contrast&lt;/code&gt; themes, and those exist for one reason only, which is accessibility. Pure black on pure white, bold everywhere, no greys at all, and every state inverts completely instead of tinting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdayvster%2Fzenkai%2Frefs%2Fheads%2Fmain%2Fscreenshots%2Fhigh-contrast-dark.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdayvster%2Fzenkai%2Frefs%2Fheads%2Fmain%2Fscreenshots%2Fhigh-contrast-dark.png" alt="zenkai in the high contrast theme, pure black background with bold white text and a white selection highlight" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;high-contrast
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;Most schemes spend their greys on the dimmed text and the inactive borders, and greys are exactly what disappears on a bad screen or with low vision. Not being able to see which row is selected every single time you open a launcher is a real problem, so these two throw the greys out entirely. There is a dark and a light version and nothing left to tune.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zenkai Is Yours Too
&lt;/h2&gt;

&lt;p&gt;A theme gets you most of the way there, and then there is the stuff a theme cannot do.&lt;/p&gt;

&lt;p&gt;Some of that is just flags. &lt;code&gt;--menu&lt;/code&gt; is the one I use most, because it lets me put my own entries into the launcher without writing anything. &lt;code&gt;--menu=gh|GitHub|github-icon&lt;/code&gt; and suddenly a search for &lt;code&gt;gh&lt;/code&gt; opens a browser tab instead of starting an application. There are flags for the icon size, the window size, which monitor it opens on, whether the animations run at all. It is a launcher that assumes you want to bend it, which is not an assumption most of them make.&lt;/p&gt;

&lt;p&gt;The rest is Lua, through &lt;code&gt;ziglua&lt;/code&gt;, so themes and actions can be extended without rebuilding. That was supposed to be the small feature. &lt;code&gt;plugins.zig&lt;/code&gt; is now 26KB where I expected a fraction of that, which is what happened the one time I said it would be small.&lt;/p&gt;

&lt;p&gt;The part that took the most work was the sandbox, because embedding a scripting language in a GUI application turns out to need three separate things and I only know the list because I got the first two wrong. Capabilities get removed rather than policed, so there is no permission list for a plugin author to misconfigure. And there is an instruction limit, because a plugin stuck in an accidental infinite loop hard hangs the whole window, since the Qt event loop is sitting on the other side of that call and never gets a chance to run again. Errors go to the log instead of stdout, attributed to the plugin that caused them.&lt;/p&gt;

&lt;p&gt;That list deserves its own post, so I am not going to stretch it here.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Want It To Become
&lt;/h2&gt;

&lt;p&gt;The honest version is that this started as a launcher and keeps turning into something else, and I am fine with that.&lt;/p&gt;

&lt;p&gt;The biggest thing I want is &lt;strong&gt;more capabilities&lt;/strong&gt;. Right now it launches applications and runs things, which is a launcher. What I actually want is closer to what &lt;a href="https://vicinae.com" rel="noopener noreferrer"&gt;Vicinae&lt;/a&gt; and &lt;a href="https://www.raycast.com" rel="noopener noreferrer"&gt;Raycast&lt;/a&gt; do, which is a command palette with genuinely useful commands beyond starting processes. Clipboard history, quick calculations, scripts, window and workspace control, that whole category. I use a launcher every single day and the fastest thing on my keyboard right now is starting applications, which is a waste of a good global hotkey. And I do not mean that as a complaint, because the thing already keeps a scorecard of what I actually use it for, so at that point it might as well be funny about it.&lt;/p&gt;

&lt;p&gt;I also want a &lt;strong&gt;Hyprland commander mode&lt;/strong&gt;, which is a slightly more niche thing but I use Hyprland and I want zenkai to be able to drive it properly rather than being an application launcher I happen to also run on that machine.&lt;/p&gt;

&lt;p&gt;And then there is the part that is less fun to admit, which is &lt;strong&gt;technical debt and performance&lt;/strong&gt;. A few months in, this has left some genuinely ugly corners that I know about and have been choosing not to look at, in the way you do not open a file you know is going to cost you an afternoon when you are having a good day. Technical debt is a bad joint, you can get away with one and you can get away with a few if you are careful about which ones, but eventually the whole thing comes apart in your hands and you find out which ones mattered all along. I want to clean that up properly. And I want it faster still, because 140ms is a good number and there is no reason it should be the number I stop at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Something
&lt;/h2&gt;

&lt;p&gt;None of the parts I am actually proud of were the plan. The lazy icons came from realising I was paying for a hundred rows nobody would see, the deferred scan came from wanting the window up before anything heavy started, and the whole thing got fast because I was stubborn about a number in a way I never get to be at work.&lt;/p&gt;

&lt;p&gt;So if you have been uninstalling launchers looking for one, or if you have looked at Zig and wondered whether it is any use for anything graphical, this is my answer to both. Go build the thing you kept looking for, even if the only reason it does not exist yet is that nobody bothered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going Deeper
&lt;/h2&gt;

&lt;p&gt;Time for some more honesty, this post was originally A LOT longer, I had to trim it down to something more reasonable to not bore most of you. Striking a good balance between technical detail and readability is hard, and I am not sure I got it right. So I am going to write at least 3 more posts where I go into the details of how I got it to be fast, how I got it to be beautiful, and how I got it to be cross-platform. The first one will be about performance, because that is the part that took me the longest to understand and the part that I am most proud of.&lt;/p&gt;

&lt;p&gt;What I have written here is the short version. There is a lot underneath it and I want to go through it properly, because the interesting parts are the ones that took me the longest to understand and I have mostly just said they work.&lt;/p&gt;

&lt;p&gt;So that is what is coming. Getting the whole thing under 200ms, including the scan of every application on the machine, and what happens in the sub 50ms cases when there is nothing left to skip. How it actually beat the Windows Run utility, which is a native dialog with no Qt runtime to load, and why a Qt application in Zig winning that was never really in doubt once I understood where the time was going. How I got Windows application discovery working at all, given that Windows describes the same application in five different places and some applications only exist in one of them, and why the honest answer there involved 148 lines of C++ in an otherwise pure Zig codebase.&lt;/p&gt;

&lt;p&gt;If you want the reasoning rather than the result, those are the ones worth reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Check it out at &lt;a href="https://zenkai-launcher.netlify.app/" rel="noopener noreferrer"&gt;zenkai-launcher.netlify.app&lt;/a&gt;, the source is on &lt;a href="https://github.com/dayvster/zenkai" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Give it a go. If you like it, drop the launcher a star on github or just a hey wassup, I like your launcher on &lt;a href="https://twitter.com/dayvsterdev" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, I will genuinely appreciate it.&lt;/p&gt;



&lt;p&gt;Thank you for your time and patience reading this article&lt;/p&gt;

</description>
      <category>zig</category>
      <category>linux</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why Bun leaving Zig is Great for Zig</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Mon, 25 May 2026 21:12:47 +0000</pubDate>
      <link>https://dev.to/dayvster/why-bun-leaving-zig-is-great-for-zig-hl2</link>
      <guid>https://dev.to/dayvster/why-bun-leaving-zig-is-great-for-zig-hl2</guid>
      <description>&lt;p&gt;The tech internet is reacting exactly how you would expect to the news that Bun has merged a near-total rewrite from Zig into Rust. Most of the Rust crowd is out in full force celebrating it as another big win for their side. They are already writing off Zig as some niche language that is not built for the future.&lt;/p&gt;

&lt;p&gt;They are missing the point.&lt;/p&gt;

&lt;p&gt;This is not cope. This is not some Zig fanboy rant. It is a realistic look at a very risky engineering decision with real consequences for the people who actually have to ship and maintain this thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Gamble
&lt;/h3&gt;

&lt;p&gt;Anthropic bought Bun in December 2025. The exact amount they paid is still unknown. Speculation ranges from a lowball one million dollars all the way up to around one hundred million. We do know Bun had raised roughly twenty six million before the acquisition. That is a serious amount of money for a JavaScript runtime, but it is basically nothing to a company valued at three hundred and eighty billion dollars.&lt;/p&gt;

&lt;p&gt;Even if they paid the full one hundred million, it would be like you or me walking around with one thousand dollars in our bank account and buying a pack of gum or a cup of coffee. Not exactly a dangerous bet for Anthropic.&lt;/p&gt;

&lt;p&gt;The online crowd is acting like this rewrite is already a guaranteed success just because they switched to Rust. In reality this is a high-stakes gamble, mostly for the Bun team. They are the ones whose reputation is on the line if things go wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four Possible Outcomes
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Scenario A: It Actually Works&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The translated Rust code ends up fast enough and mostly stable. Anthropic gets to show it off as proof that their AI can rewrite large systems code. Bun ends up with the same product they already had, just written in a different language.&lt;/p&gt;

&lt;p&gt;Even in this best case there is a catch. Early checks on the Rust port showed over thirteen thousand unsafe blocks because the AI just carried over the old Zig pointer style. The Rust community might end up doing a bunch of unpaid cleanup work on the messy generated code. So even if it ships, it could still be a weird Frankenstein project under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Scenario B: Flaky and Unreliable&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The runtime mostly runs but has weird regressions, random panics, and edge cases that tests did not catch. Anthropic will probably downplay it and call it progress. For the Bun team this would be painful. A lot of people picked Bun specifically because it promised incredible performance. If that trust breaks, many will quietly switch back to Node or Deno and never look back.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Scenario C: Total Disaster&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The new codebase has serious bugs that break production apps for users. Upgrades become a nightmare. Anthropic takes a small hit to their image but keeps running their main business without much trouble. How ever bun as a product might no longer really exists after such an event. Once trust is gone in the JavaScript runtime space it is very hard to get it back. The project could slowly fade into irrelevance.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Scenario D: It Works But Becomes Maintenance Hell&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is the one a lot of people are not talking about. The code compiles, passes the tests, and mostly works in production. But the generated Rust is ugly, hard to read, and full of weird patterns that no human would write. Over time it becomes much more painful to maintain and extend than the old Zig version ever was.&lt;/p&gt;

&lt;p&gt;New features take longer, bugs are harder to track down, and the original Bun team ends up spending most of their time fighting the codebase instead of improving the runtime. Users get slower progress and the project loses momentum even if nothing catastrophic happens. This is probably the most likely middle-ground outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  Outcome Breakdown
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Anthropic Result&lt;/th&gt;
&lt;th&gt;Bun Team Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clean Success&lt;/td&gt;
&lt;td&gt;Big Win&lt;/td&gt;
&lt;td&gt;Break-even&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flaky and Unreliable&lt;/td&gt;
&lt;td&gt;Small Win&lt;/td&gt;
&lt;td&gt;Loss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Disaster&lt;/td&gt;
&lt;td&gt;No Real Damage&lt;/td&gt;
&lt;td&gt;Horrible Loss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works But Maintenance Hell&lt;/td&gt;
&lt;td&gt;Minor Win&lt;/td&gt;
&lt;td&gt;Slow Pain / Stagnation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What Did Zig Actually Lose?
&lt;/h3&gt;

&lt;p&gt;Pretty much nothing important.&lt;/p&gt;

&lt;p&gt;They lost a corporate sponsor check and a team that never really fit the Zig way of doing things.&lt;/p&gt;

&lt;p&gt;Zig is about taking it easy, moving carefully, and doing the work right. It is built for patient craftsmanship. The Bun team was running on a completely different mindset. They operated in full hype mode, rushing everything out to feed the JavaScript ecosystem’s demand for speed.&lt;/p&gt;

&lt;p&gt;That constant rush led to a lot of breakage. Instead of owning it, they blamed Zig. They pointed at the pre-1.0 compiler and breaking changes as the reason their runtime kept feeling unstable. They even forked the language and tried forcing lifetimes and heavy AI patches into it, trying to turn Zig into something it was never meant to be.&lt;/p&gt;

&lt;p&gt;Ditching that mismatch is probably a relief for the Zig project.&lt;/p&gt;

&lt;p&gt;But the real gift is what comes next. Anthropic just paid for the cleanest before-and-after comparison the Zig community could ever ask for. We had Bun running on Zig for years. That was the before. Now we have the exact same codebase, same architecture, and same team running on a machine-translated Rust version. That is the after.&lt;/p&gt;

&lt;p&gt;No more excuses. No more arguing about pre-1.0 volatility or breaking changes. All the variables are removed. The Zig community now gets to watch what happens in plain sight.&lt;/p&gt;

&lt;p&gt;If Bun suddenly becomes rock solid and reliable, then maybe Zig really was holding it back. But if the flakiness, regressions, and instability follow it into Rust anyway, that will be very telling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zig didn’t lose its flagship project. It let a multi-billion-dollar company pay for the crash test.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>zig</category>
    </item>
    <item>
      <title>In Defense of Manual Memory Management</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Mon, 25 May 2026 21:11:01 +0000</pubDate>
      <link>https://dev.to/dayvster/in-defense-of-manual-memory-management-3jpo</link>
      <guid>https://dev.to/dayvster/in-defense-of-manual-memory-management-3jpo</guid>
      <description>&lt;h2&gt;
  
  
  Why Manual Memory Management Still Matters ?
&lt;/h2&gt;

&lt;p&gt;It is now easier than ever to write software without ever needing to learn about the heap or the stack. Languages like JavaScript, Python and even Go have built-in ways to manage your memory usage for you at runtime with acceptable performance hits for most modern consumer grade hardware. However consumer hardware is not the end all be all of computing. There are still many use cases where the performance benefits of manual memory management are worth the effort, such as in embedded systems, game development, and high-performance computing. &lt;/p&gt;

&lt;p&gt;I'd even argue that it's a valuable skill to have and learn even if you don't need it for your day to day work. It can give you a deeper understanding of how computers work and how to optimize your code for performance.&lt;/p&gt;

&lt;p&gt;When you understand how memory management works how your system works under the hood, you will also as a third order effect understand your own code and software better. Plus you can then use super cool and trendy languages like Zig, Odin and C3 which have manual memory management as a core feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Usual Pitfalls
&lt;/h2&gt;

&lt;p&gt;Here are some things that you should be aware of when doing manual memory management:&lt;/p&gt;

&lt;h3&gt;
  
  
  Dangling Pointers
&lt;/h3&gt;

&lt;p&gt;A dangling pointer is a pointer that points to memory that has already been deallocated. Imagine lending your friend a book and making a note of it and when you go to ask for it back they tell you that they've already thrown it away, rude yes, but now you have a note that points to a book that no longer exists. Bad analogy aside a dangling pointer is a pointer that points to memory that has already been freed, and if you try to access it you will get undefined behavior, which can lead to crashes, data corruption, and security vulnerabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Double Free
&lt;/h3&gt;

&lt;p&gt;Continuing my bad analogy say that you've lent your friend a book and now you wish to throw it away, so first you have to get it back from your friend, who has rudely already thrown it away, even though you wanted to throw it away yourself. So you attempt to throw away a book that has already been thrown away. Wow I'm really bad at this whole analogy thing. Regardless, a double free is when you try to free memory that has already been freed, and this can lead to undefined behavior, which can lead to crashes, data corruption, and security vulnerabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use After Free
&lt;/h3&gt;

&lt;p&gt;You know what I'm sticking to my bad analogy, so say that you've lent your friend a book and then you attempt to read it after your friend has already thrown it away, you will get a book that is no longer there and you will be confused and upset. This is what happens when you try to access memory that has already been freed, you will get undefined behavior, which can lead to... yes you've guessed it, crashes, data corruption, and security vulnerabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leaking Memory
&lt;/h3&gt;

&lt;p&gt;Your friend lives in a small apartment and you lend them a book, then you lend them 10 more books and after that you just keep lending them books like the crazed book lender that you are without ever asking for them back. Eventually your friend will have a huge pile of books that they can't manage(that'll show em) and they will have no space left in their apartment. This is what happens when you allocate memory but never free it, you will eventually run out of memory and your application will crash. This is called a memory leak, and it can be a serious problem if not managed properly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Buffer Overflows
&lt;/h3&gt;

&lt;p&gt;Your friend is a bit OCD they reserved a singular bookshelf for the books you lend them, it only has space for about 10 books because your very OCD friend likes the number 10, so you lend them 10 books one week and the next week you lend them 1 more book. Your friend has a meltdown because they have 11 books and can't fit them on their shelf that can only ever hold 10 books. Yes I am sticking to my bad analogy. A buffer overflow is essentially when you try to write more data to a buffer than it can hold. Guess what this can lead to? Yes you guessed it, crashes, data corruption, and security vulnerabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory Fragmentation
&lt;/h3&gt;

&lt;p&gt;Your very OCD friend built a bigger shelf and has a new strategy of how to place books on that shelf, they wanna sort them alphabetically or by color or something I don't know it's an OCD thing. So you keep lending them book chaotically which leads your friend to have a bunch of empty space between the books on the shelf, the shelf itself is bigger yes, but the empty space between the books is wasted space that can't be used for anything else. This is what happens when you have a lot of small allocations and deallocations, it can lead to memory fragmentation, which can lead to inefficient use of memory and eventually running out of memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incorrect Ownership
&lt;/h3&gt;

&lt;p&gt;Ok I got no idea how to work my bad analogy into this one, you and your ... roommates ? all try to modify the same book on the shelf at the same time, this opens a rift in the space time continuum because somehow you've now produced a quantum book that exists in multiple states at the same time, and you have no idea which state is the correct one. Wow that went off the rails real quick. Incorrect ownership is basically when you have multiple pointers to the same memory and each of them tries modifying it. This can lead to race conditions and data corruption, and it can be very difficult to debug.&lt;/p&gt;




&lt;p&gt;As you can see doing manual memory management is a lot more involved than just using a garbage collected language, but it can also be a lot more rewarding and fun. You get to have more control over your application's memory usage and you can really optimize your software for performance and for known hardware. However, you also have to be very careful and pay attention to the details, because if you make a mistake it can lead to some very bad consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Avoid These Pitfalls
&lt;/h2&gt;

&lt;p&gt;Just use a garbage collected language, problem solved. End of article. Just kidding, if you want to do manual memory management there are some things you can do to avoid these pitfalls, you've already solved the biggest problem by now which is understanding the pitfalls (I hope). But just understanding the problem is only half the journey the solution is the other half. So let's think about some solutions to these problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Mental Model
&lt;/h3&gt;

&lt;p&gt;The first thing you need to do is to have a mental model of how memory management works. Let's start with 2 terms which immediately describe what they do, the &lt;strong&gt;stack&lt;/strong&gt; and the &lt;strong&gt;heap&lt;/strong&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;For a stack we always default to a stack of plates when describing it because it's the easiest way to visualize it, you can only add or remove plates from the top of the stack because we really do not wanna break any plates. So the stack is a region of memory that is used for storing local variables and function call information. It is organized in a last-in-first-out (LIFO) manner, which means that the most recently added item is the first one to be removed. The stack is typically much faster than the heap because it has a fixed size and does not require dynamic memory allocation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heap
&lt;/h2&gt;

&lt;p&gt;For the heap imagine a big disorganized pile of clothes, you can very quickly throw clothes onto the pile and you can also quickly grab clothes from the pile, but you have no idea where anything is and it can get really messy really fast. The heap is a region of memory that is used for dynamic memory allocation. It is organized in a more flexible manner than the stack, which means that you can allocate and deallocate memory in any order. The heap is typically slower than the stack because it requires dynamic memory allocation and deallocation.&lt;/p&gt;

&lt;p&gt;I could easily write full articles on just the stack and just the heap alone but for the sake of keeping your attention span I think this is a good enough mental model to get you started. &lt;/p&gt;

&lt;h3&gt;
  
  
  Tools and Tricks
&lt;/h3&gt;

&lt;p&gt;Use the tools available to you. We have debuggers and sanitizers, logging and profiling tools, and even static analysis tools that can help you catch these issues before they become a problem. Use them, they are there for a reason and they can save you a lot of time and headaches.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debuggers&lt;/strong&gt;: A debugger is a tool that allows you to step through your code line by line and inspect the state of your program at any point in time. This can be incredibly helpful for finding and fixing memory management issues, as you can see exactly where things are going wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanitizers&lt;/strong&gt;: Sanitizers are tools that can help you detect memory management issues at runtime. For example, AddressSanitizer can help you detect buffer overflows and use-after-free errors, while LeakSanitizer can help you detect memory leaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging and Profiling Tools&lt;/strong&gt;: Logging can help you track the flow of your program and see where memory is being allocated and deallocated. Profiling tools can help you identify performance bottlenecks in your code, which can be caused by inefficient memory management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static Analysis Tools&lt;/strong&gt;: Static analysis tools can analyze your code without running it and can help you find potential memory management issues before they become a problem. For example, tools like Clang's static analyzer can help you find potential memory leaks and other issues in your code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Reviews&lt;/strong&gt;: Code reviews can be a great way to catch memory management issues before they make it into production. Having another set of eyes on your code can help you spot potential issues that you might have missed, ask a friend to review your code if it's a hobby or solo project if you have to, just maybe not the same friend that you lend books to in the previous analogies, they might be a bit overwhelmed at this point.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best Practices and Habits
&lt;/h3&gt;

&lt;p&gt;Tools are great and all but at the core of manual memory management is you, the developer. You need to have a good set of habits and practices that can help you avoid these pitfalls in the first place. By delving into the world of manual memory management you take responsibility for your own code and you have to be disciplined about how you manage your memory. Here are some best practices and habits that can help you avoid these pitfalls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always Free What You Allocate&lt;/strong&gt;: This is the most basic rule of manual memory management, if you allocate memory you need to make sure that you free it when you're done with it. This can help you avoid memory leaks and ensure that your application does not run out of memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Smart Pointers&lt;/strong&gt;: If you're using a language that supports smart pointers, such as C++, use the tools they give you, smart pointers are easy to learn and they can help you avoid a ton of pitfalls listed above like dangling pointers, double free, use after free, and even incorrect ownership. They are a great way to manage memory safely and efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Favor scope-based cleanup&lt;/strong&gt;: If your language supports it, use scope-based cleanup mechanisms. In C++, this is RAII (Resource Acquisition Is Initialization), where resources are automatically released when they go out of scope. In Zig, you can use &lt;code&gt;defer&lt;/code&gt; to schedule cleanup code that runs when a block ends. These tools help prevent memory leaks and make sure your application doesn’t run out of memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test small and often&lt;/strong&gt;: When you're doing manual memory management, it's important to test your code frequently and in small increments. This can help you catch issues early on before they become bigger problems. Write unit tests that specifically test the memory management aspects of your code, and run them regularly to ensure that everything is working as expected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respect your future self, document your damn code&lt;/strong&gt;: You will forget about the details of your code in the future, especially when it comes to manual memory management. Make sure to document your code well, especially the parts that deal with memory management. This can help you understand your code better in the future and can help you avoid making mistakes when you come back to it later on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Different Languages Tackle Manual Memory Management
&lt;/h2&gt;

&lt;p&gt;Ok we're done with bad analogies for now, so let's discuss how different languages that do not come with a garbage collector handle manual memory management, and how they help you avoid the pitfalls mentioned above.&lt;/p&gt;

&lt;h3&gt;
  
  
  C
&lt;/h3&gt;

&lt;p&gt;C is a fine classic, it's timeless and beautiful in its simplicity. I appreciate just how minimal it is and how much you can do with just raw C and the standard library. Famously C has &lt;code&gt;malloc&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt; for manual memory management, and it is up to the developer to make sure that they are used correctly. C does not have any built-in tools to help you avoid the pitfalls of manual memory management, so you need to be very careful when using it. However, there are many third-party libraries and tools that can help you manage memory in C, such as Valgrind for detecting memory leaks and AddressSanitizer for detecting buffer overflows and use-after-free errors.&lt;/p&gt;

&lt;p&gt;But recently we've gotten some new tools and helpers in the C ecosystem such as Fil-C.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fil-C
&lt;/h3&gt;

&lt;p&gt;Fil-C is a modified toolchain that adds a ton of memory safety to C and C++ at a very minimal cost and no need for expensive rewrites to other languages or paradigms which may lead you to throw away decades of battle tested code and bugfixes. &lt;/p&gt;

&lt;p&gt;What Fil-C delivers is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modified Compiler&lt;/strong&gt;: Fil-C uses a modified compiler based on LLVM/Clang that instruments your code during compilation. It rewrites pointer operations and inserts safety checks so that invalid memory accesses such as out of bounds reads or writes are detected at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Runtime&lt;/strong&gt;: Fil-C links programs against its own runtime which is responsible for enforcing these safety guarantees. It tracks memory objects, validates pointer accesses, and aborts execution if the program attempts an unsafe operation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modified Standard Library&lt;/strong&gt;: The C standard library used with Fil-C is adapted to operate within the same safety model. This ensures that safety guarantees apply consistently across both your code and the libraries it depends on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Memory Safe ABI&lt;/strong&gt;: Fil-C programs run with a modified application binary interface designed to support its safety checks and metadata tracking. This ABI allows the runtime to reliably enforce memory safety across the entire program.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these components allow Fil-C to keep the simplicity and ecosystem of C, while adding a layer of protection against many of the most common and dangerous memory errors.&lt;/p&gt;

&lt;p&gt;But the magic happens when you compile your existing C or C++ code with the Fil-C compile, it will automatically rewrite your code to add safety checks, insert runtime checks and link against the Fil-C runtime, all without you having to change a single line of your code. This means that you can take your existing C or C++ codebase and compile it with Fil-C to get memory safety guarantees without having to rewrite your code or change your programming paradigm.&lt;/p&gt;

&lt;p&gt;Essentially something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will compile to something sorta like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;check_pointer_bounds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;load_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  C++
&lt;/h2&gt;

&lt;p&gt;Ah yes the one everyone loves to hate but also the one that has had one of the biggest impact on the software industry. I know C++ gets a bad rep, in fact I even wrote a whole article &lt;a href="https://dev.to/blog/in-defense-of-cpp/"&gt;in defense of C++&lt;/a&gt; give it a read if you have some time. But back to the main point, C++ has evolved a lot since its inception and it has added a lot of features to help with manual memory management in a safe and controlled way, such as &lt;code&gt;smart pointers&lt;/code&gt; which are basically wrappers around raw pointers that automatically manage the memory they point to. &lt;/p&gt;

&lt;p&gt;Also modern C++ strongly encourages the use of containers instead of manual memory allocation, usually if you use the following: &lt;code&gt;std::vector&lt;/code&gt;, &lt;code&gt;std::string&lt;/code&gt;, &lt;code&gt;std::map&lt;/code&gt;, &lt;code&gt;std::unordered_map&lt;/code&gt; and so on, you will be using manual memory management under the hood but you won't have to worry about it because the containers will take care of it for you.&lt;/p&gt;

&lt;p&gt;Trouble arises if you use something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a raw pointer and you will have to manually manage the memory for it. The same can be achieved with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will give you a vector of 10 integers and you won't have to worry about memory management&lt;/p&gt;

&lt;h2&gt;
  
  
  Zig
&lt;/h2&gt;

&lt;p&gt;My favorite of the bunch, Zig is a modern programming language that has manual memory management as a core feature. It has a unique approach to memory management that is designed to be safe and efficient, while still giving developers full control over their memory usage. You can accomplish this by &lt;a href="https://dev.to/blog/zig-allocators-explained/"&gt;understanding zig allocators&lt;/a&gt; and using them correctly in your code. Zig also has the &lt;code&gt;defer&lt;/code&gt; keyword which allows you to schedule cleanup code that runs when a block ends, this can help you avoid memory leaks and ensure that your application does not run out of memory.&lt;/p&gt;

&lt;p&gt;Basically Zig gives you the tools you need to manage your memory safely and efficiently with minimal effort on your end and all this with basically no overhead or significant performance hits, it's a win-win situation.&lt;/p&gt;

&lt;p&gt;Plus zig can interop with C and even be used as a C or C++ compiler. &lt;/p&gt;

&lt;h2&gt;
  
  
  Odin
&lt;/h2&gt;

&lt;p&gt;I've covered how Odin manages memory in &lt;a href="https://dev.to/blog/is-odin-just-a-more-boring-c/"&gt;is odin just a more boring C?&lt;/a&gt; the title is a bit clickbaity, I'm sorry about that, I mean it in a good way, I like a language that is simple and straightforward and gets out of my way and lets me be productive. Odin is precisely that and I love it. I really would recommend reading through my article on Odin because it has a very cool and unique approach to manual memory management that is worth learning about.&lt;/p&gt;

&lt;p&gt;What makes Odin interesting when it comes to memory management is that it does not try to hide memory from you. Instead it makes it explicit and flexible through allocators. In Odin you can pass allocators around just like normal values and decide exactly how memory should be handled in a given part of your program. Many APIs also default to using context.allocator, which gives you a convenient default while still letting you override it when needed.&lt;/p&gt;

&lt;p&gt;This means you can easily switch strategies depending on the problem. Maybe you want an arena allocator for short lived data, or a custom allocator for a performance sensitive subsystem. Odin makes that kind of thing straightforward.&lt;/p&gt;

&lt;p&gt;In practice this leads to code that is very predictable. You know where allocations happen, you know who owns them, and you can change the allocation strategy without rewriting half your program. It fits nicely with Odin’s general philosophy of simplicity and control.&lt;/p&gt;

&lt;h2&gt;
  
  
  C3
&lt;/h2&gt;

&lt;p&gt;I have not had the time to properly dive into C3 yet. I tinkered with it a bit and I like the direction they are taking.&lt;/p&gt;

&lt;p&gt;From what I have seen so far, C3 leans heavily on scope based allocation and deterministic cleanup. The idea is that a lot of memory you allocate only needs to live for the duration of a function or a block. Instead of manually tracking every allocation and freeing it later, the language and its libraries make it easy to tie allocations to a scope and clean them up when that scope ends.&lt;/p&gt;

&lt;p&gt;C3 also provides helpers like &lt;code&gt;mem::new_array&lt;/code&gt; and &lt;code&gt;mem::alloc_array&lt;/code&gt; in the standard library so you are not constantly dealing with raw allocation primitives. The project also promotes the use of temporary or arena style allocators for short lived data.&lt;/p&gt;

&lt;p&gt;What I like about this approach is that it tackles one of the biggest sources of memory bugs which is lifetime confusion. If allocations clearly belong to a scope, a lot of mistakes simply disappear.&lt;/p&gt;

&lt;p&gt;I still need to spend more time with C3 before forming a strong opinion, but from what I have seen so far it looks like another interesting attempt at making manual memory management practical instead of painful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rust Detour
&lt;/h2&gt;

&lt;p&gt;I'll be honest with you, I don't consider Rust to be a manual memory management language. It's a language that has a unique approach to memory management yes but at no point does it require you to manually allocate and deallocate memory, it has a built in ownership system that takes care of all of that for you. Rust is basically a set of compiler rules which basically just shifts the responsibility of memory management away from the developer, if that sounds familiar it's because that's precisely what garbage collected languages do. They assume you will mess up and they take care of it for you, Rust just does that at compile time instead of runtime. &lt;/p&gt;

&lt;p&gt;Is that a good approach? Honestly no. I think it's a horrible approach that adds more problems than it solves and in the end it does not even guarantee memory full safety, that's how you get the famous quotes like &lt;code&gt;memory leaks are considered memory safe in Rust&lt;/code&gt;. So far Rust has failed to impress in production as well such as when Cloudflare went down because of a memory issue in their Rust code, a problem Rust was specifically designed to prevent. &lt;/p&gt;

&lt;p&gt;As you can tell I'm not a fan of Rust and I don't think it's a good language at all. It came in hot with wild promises and failed to deliver on any of them so far, but that's a topic for another article.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Should you Reach for Manual Memory Management?
&lt;/h2&gt;

&lt;p&gt;Manual memory management is really powerful and if you understand it well you can write incredibly efficient and performant code, but it also comes with a lot of responsibility and potential for mistakes. So when should you reach for manual memory management?&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Critical Code
&lt;/h3&gt;

&lt;p&gt;In high performance systems, even small inefficiencies can add up. Games, trading systems, real time audio processing, and high frequency networking software often benefit from predictable memory usage and deterministic cleanup.&lt;/p&gt;

&lt;p&gt;Garbage collectors can introduce pauses or unpredictable allocation patterns. Manual memory management allows developers to control exactly when allocations happen and when memory is released.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedded Systems
&lt;/h3&gt;

&lt;p&gt;Embedded devices often run with extremely limited memory and CPU resources. A garbage collector may simply be too expensive or too unpredictable in these environments.&lt;/p&gt;

&lt;p&gt;Manual memory management lets developers carefully control how memory is used and ensures that the system never exceeds its constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Systems Programming
&lt;/h3&gt;

&lt;p&gt;Operating systems, drivers, compilers, and networking stacks all tend to require direct control over memory. In these areas you often need to manage memory layouts explicitly, work with hardware, or operate in environments where a runtime simply does not exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Large Existing Codebases
&lt;/h3&gt;

&lt;p&gt;Sometimes the reason is simple. There are decades of battle tested C and C++ code in the world. Rewriting those systems into a different paradigm or language is often unrealistic or would lead to throwing away decades worth of battle tested code and bug fixes. &lt;/p&gt;

&lt;p&gt;In those cases it makes more sense to improve tooling, add safety layers, or adopt better practices rather than start over from scratch.&lt;/p&gt;

&lt;p&gt;And that is really the key point. Manual memory management is not something you reach for just because you can. It is something you use when you need control, predictability, and performance that higher level runtimes cannot always provide.&lt;/p&gt;

&lt;p&gt;Understanding it also changes how you think about software in general. Once you know what is actually happening under the hood when memory is allocated, freed, or reused, many parts of programming start to make a lot more sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Manual memory management has a reputation for being dangerous and outdated, but that reputation mostly comes from misunderstanding and misuse.&lt;/p&gt;

&lt;p&gt;Yes, the pitfalls are real. But so are the performance benefits and the deeper understanding of how software works that comes with mastering it.&lt;/p&gt;

&lt;p&gt;The reality is that manual memory management is still everywhere. Operating systems, game engines, databases, embedded systems, networking stacks and high performance software all rely on it every day. Entire industries run on systems that manage memory manually and have done so successfully for decades.&lt;/p&gt;

&lt;p&gt;What has changed is the ecosystem around it. We now have better debugging tools, sanitizers, static analyzers and improved language features that make writing safe manual memory code far more approachable than it used to be. Modern C++, Zig, Odin and C3 are all exploring different ways to give developers control without unnecessary pain.&lt;/p&gt;

&lt;p&gt;At the end of the day, manual memory management is just another tool in the toolbox. Not every project needs it, and for many applications a garbage collected language is perfectly fine. But when performance, determinism or low level control matter, understanding manual memory management becomes incredibly valuable.&lt;/p&gt;

&lt;p&gt;And honestly, once you understand it, it stops being scary. It just becomes another part of writing good software.&lt;/p&gt;

&lt;p&gt;Thank you for your time, this was a long one and I sincerely hope you enjoyed it.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>zig</category>
      <category>systems</category>
    </item>
    <item>
      <title>AI’s Trap: Settling for Boilerplate Over Elegant Code</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Mon, 27 Oct 2025 11:04:09 +0000</pubDate>
      <link>https://dev.to/dayvster/ais-trap-settling-for-boilerplate-over-elegant-code-n32</link>
      <guid>https://dev.to/dayvster/ais-trap-settling-for-boilerplate-over-elegant-code-n32</guid>
      <description>&lt;p&gt;We are all familiar with Picasso's "The Bull" series, in which he progressively simplifies the image of a bull down to its most basic, yet still recognizable form. Steve Jobs was famously inspired by this concept, leading him to advocate for simplicity and elegance in design and technology above countless features and excessive complexity. Distill a concept even as complex as software or UX down to its essence, and what you are left with is something beautiful and elegant that fulfills its purpose with minimal fuss.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdayvster.com%2Fimages%2Fblog%2Fais-trap-settling-for-boilerplate-over-elegant-code%2Fbull.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdayvster.com%2Fimages%2Fblog%2Fais-trap-settling-for-boilerplate-over-elegant-code%2Fbull.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So Why Do We Accept Ugly Code Then?
&lt;/h2&gt;

&lt;p&gt;I've noticed a worrying trend in programming that, as tools around a programming language improve and automate more of our work, we increase our tolerance for boilerplate, repetitive, and frankly, ugly code. We accept it and we tell ourselves it's ok, the linter will fix it, the formatter will fix it, the compiler will optimize it, in the end it's all ones and zeroes anyway, right, why would any of this matter?&lt;/p&gt;

&lt;p&gt;Since AI has entered the equation, tolerance for boilerplate and excuses for ugly code has only increased. We tell ourselves that as long as the AI can generate the code for us, it doesn't matter if it's elegant or not. After all, we didn't write it, the AI did. So why should we care about the quality of the code? After all, we relinquished ownership of the code the moment we asked the AI to generate it for us. &lt;/p&gt;

&lt;p&gt;Now, you may not be someone who uses AI to generate code, and kudos to you, welcome to the club, however, even as someone who relatively early noticed that AI does not produce something that I could sign off on as my own work proudly and with confidence. I have engaged in the practice of using AI to generate some more tedious tasks for myself, such as just taking a JSON response from an API and asking Copilot, ChatGPT, or Grok to generate a type definition in the language I am currently working with. &lt;/p&gt;

&lt;p&gt;I work on many personal and professional projects, and I encounter different types of people and teams, some embrace AI, others shun it. However I have noticed that in teams or projects where AI is embraced or even encouraged or mandated as part of the development process, tend to produce a lot of boilerplate and a lot of very ugly inelegant code that few wish to take real ownership of, because it is not their creation they handed ownership of it to the AI and therefore abandoned the developer - code relationship that is so essential to producing quality software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developers Should Love Their Code
&lt;/h2&gt;

&lt;p&gt;I harp on this a lot, development is a unique one of a kind blend of engineering and creative expression, those two aspects of our craft should not be at odds with each other, but should rather complement each other.&lt;/p&gt;

&lt;p&gt;When you write code, you should love it, you should be proud of it, you should want to show it off to others who can understand it and appreciate it, in essence, your way of expressing your way of thinking and how you tackle problems.&lt;/p&gt;

&lt;p&gt;I've briefly touched upon the fact that handing off ownership of your code to AI means abandoning that relationship between you, the developer, and your code. I want to expand on that a bit more. If you don't care about the code you write, if you don't love what you are doing or the creative process to solve a specific problem, you will forever lack understanding of that specific problem that you are solving. It will get solved for you by the AI, and you will rob yourself of the opportunity to learn and understand that problem on a deeper level.&lt;/p&gt;

&lt;p&gt;This is something that has kicked me in the ass a fair few times in the past, I'd get a problem to solve, think oh this is easy, draft up an initial solution solve the problem at a very superficial level and then in the coming weeks get 10 QA tickets filed against that problem because there is more to the problem than meets the eye and there are often things that you miss or do not even consider during your first implementation. &lt;/p&gt;

&lt;p&gt;AI will do exactly the same thing every single time. The difference, though, is that every time I was given a problem to solve, it resulted in fewer and fewer QA tickets because I understood and learnt from my past experiences and mistakes and knew how to approach problem-solving more effectively.&lt;/p&gt;

&lt;p&gt;AI will not do any of that, it will always solve the problem given to it at face value without any deeper understanding or context. It will not learn from past mistakes, grow, or adapt its mindset to shift its approach based on new information or insights. It will always solve the problem, and worst of all, it will never learn to understand the problem so well that it can simplify the solution down to its essence and produce something elegant.&lt;/p&gt;

&lt;p&gt;There is a certain beauty to solving a problem and all of its potential side effects in a very minimal and easily readable way. That is an ideal that I strive for in my own work, and I encourage you to do the same. Love your code, be proud of it, and strive for elegance over boilerplate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost of Boilerplate
&lt;/h2&gt;

&lt;p&gt;Boilerplate code is not just an eyesore, it comes with real costs. It increases the cognitive load on developers, makes the project less enjoyable to work on, and increases the time to onboard new team members. And increases the time to fix or resolve issues, which directly impacts the experience of the end user of your product.&lt;/p&gt;

&lt;p&gt;As a third-order effect, it also increases the soft requirement for more tooling and more AI assistance to manage and maintain your codebase, which is a dangerous spiral best avoided altogether. &lt;br&gt;
A question I've been asking myself is how much of this is by happenstance and how much of this is by design. I don't want to get all conspiracy theorist on you, but it does make me wonder if there are vested interests in making us accept boilerplate and ugly code as the norm, because it increases the demand for AI tools and services that can help us manage and maintain our codebases. I'm not confident enough to attach any weight to this thought, but it's something worth pondering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aesthetic Responsibility
&lt;/h2&gt;

&lt;p&gt;It's very easy for us as developers to simply dismiss aesthetics as something superficial and unimportant in the grand scheme of things. After all, we are not building art, we are building software that solves problems and delivers value to users. However, I would argue that aesthetics play a crucial role in the quality and maintainability of our code. &lt;/p&gt;

&lt;p&gt;When we write code that is elegant and beautiful, we are not just making it easier for ourselves to read and understand, we are also making it easier for others to read and understand. We are creating a shared language and a shared understanding of the problem we are solving and the solution we are implementing. When we write ugly and boilerplate code, we are creating barriers and obstacles for ourselves and others. We are making it harder to read and understand, we are making it harder to maintain and evolve, and we are making it harder to collaborate and share knowledge. We're increasing the friction instead of reducing it.&lt;/p&gt;

&lt;p&gt;It always feels nice as a human to look at something beautiful, whether it's a piece of art, a well-designed product, a sports car, a building with stunning architecture, and so on and so forth. But when it comes to code, we often dismiss aesthetics as unimportant, after all, it's a means to an end, right? I would argue that as developers, we have an aesthetic responsibility to ourselves and to others to write code that is elegant and beautiful, not just for the sake of aesthetics, but for the sake of quality and maintainability.&lt;/p&gt;

&lt;p&gt;But it is very easy lately to just let the tools we have been given and AI do the heavy lifting for us, and in doing so, we risk losing sight of our aesthetic responsibility as developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does AI Increase the Tolerance for Boilerplate?
&lt;/h2&gt;

&lt;p&gt;You know the drill: you are working away on a piece of a project, and you're on a tight deadline with a backlog full of features to implement and bugs to fix. You think to yourself, "I just need to get this done quickly, I can refactor it later(a mindset I generally encourage)." So you ask the AI to generate the code for you, and it spits out a solution that does not work, so you refine your prompt and try again, and again, and again until you get something that works. Now you have a working solution, great! One quick glance at your &lt;code&gt;git status&lt;/code&gt;, &lt;strong&gt;HOLY SH.. WHY ARE THERE 32 FILES CHANGED? AND WHY ARE MOST OF THEM NEW FILES?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ok..OK, you have a working solution. It's best to open up a PR and let the Copilot PR bot handle the code review, and maybe a couple of co-workers will spot some things and suggest improvements. Once you have more time on your hands, you will absolutely go back and refactor this mess down to something elegant and beautiful. You just need to get this next task done first, oh, and it's on a tight deadline as well... &lt;/p&gt;

&lt;p&gt;Before you know it, you have a codebase full of boilerplate and code that could easily be done in half as many lines or less, you will never go back and refactor it because there is always something more urgent to do, and the cycle continues. &lt;/p&gt;

&lt;p&gt;Maintenance? Not a problem, we can just use AI to generate documentation for us or explain code blocks for us. Testing? AI can generate tests for us, no need to think about edge cases or test coverage. Performance? AI can optimize our code for us, no need to understand the underlying algorithms or data structures.&lt;/p&gt;

&lt;p&gt;It creeps up on you slowly, but surely. AI stands as an excuse to not care about the quality of your code, but only the quality or functionality of your outcome. There's a lot to be said and discussed about this topic.&lt;/p&gt;

&lt;p&gt;You might be rightfully asking yourself, does it even matter if the code is ugly and I feel no pride in it, as long as the end user gets the functionality they need?&lt;/p&gt;

&lt;h2&gt;
  
  
  "Good Enough" Is Not Good Enough
&lt;/h2&gt;

&lt;p&gt;I always liked the saying, "How you do anything is how you do everything." It has multiple different explanations and interpretations, but to me, it boils down to this: if you accept mediocrity in one aspect of your life, you will accept mediocrity in all aspects of your life. Or, how you do the little things is how you do the big things. &lt;/p&gt;

&lt;p&gt;If you accept "good enough" code, you will eventually have a good enough product on your hands that might have to compete with products that were built with care and pride. Do you want to deliver good enough products, or do you want to deliver great products that you can be proud of?&lt;/p&gt;

&lt;p&gt;Completely ignoring the market and financial viability of this approach, will you be happy with the work you do if mediocrity is the standard you hold yourself to? Or worse, if mediocrity is the modus operandi of your team or company? &lt;/p&gt;

&lt;p&gt;If you truly believe that "good enough" is good enough, then by all means continue down that path. But I urge you to test that belief, challenge it, start a project that you may never finish or get paid for, but attempt to take a complex concept and distill it down to its essence in the most elegant way possible, do what Picasso did with his bull series, and see how far you can push yourself to create something beautiful out of something complex while still maintaining its core functionality and purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AI is a powerful tool that can help us be more productive and efficient, but it should not be used as an excuse to accept boilerplate and ugly code. As developers, we should strive for elegance and simplicity in our code, and we should take pride in the work we do. We should love our code and our craft, and we should never settle for "good enough" when it comes to the quality of our work. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write code you would sign your name under.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've enjoyed this article and made it this far, thank you sincerely for your time. I hope it was worth it and that it sparked some thoughts and reflections on your own approach to coding and craftsmanship, and I sincerely hope it did not feel wasted.&lt;/p&gt;

&lt;p&gt;If you have any thoughts or feedback on this article, please feel free to reach out to me on &lt;a href="https://twitter.com/dayvsterdev" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. I'm always open to discussions and feedback, or just general chit chat about just about anything I find interesting.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why Zig + Qt Feels Like Doing the Impossible Right</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Wed, 22 Oct 2025 19:34:37 +0000</pubDate>
      <link>https://dev.to/dayvster/why-zig-qt-feels-like-doing-the-impossible-right-2ibn</link>
      <guid>https://dev.to/dayvster/why-zig-qt-feels-like-doing-the-impossible-right-2ibn</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;It's no secret that I love Zig, and it's also no secret that my favorite GUI framework by far is Qt. So naturally, when I discovered that there were Zig bindings for Qt I had to give it a try and build something with it. What I decided to build was a fairly rudimentary shopping list application, nothing fancy just one of my standard things I like to build when exploring a new GUI framework of any kind. Because a shopping list app is simple enough to not get in the way of learning the framework but also complex enough to cover a lot of the basic concepts that you'll need to know when building a GUI application such as handling user input, displaying data, responding to different events, sorting and filtering the data and so on. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Library
&lt;/h2&gt;

&lt;p&gt;For this article and experiment I went with &lt;a href="https://github.com/rcalixte/libqt6zig" rel="noopener noreferrer"&gt;libqt6zig&lt;/a&gt;, there are other bindings libraries out there for Zig but they are QML based whereas libqt6zig is a direct binding to the Qt C++ API which I tend to prefer and it interested me a lot more because direct bindings are generally more performant and a lot harder to do correctly, so I wanted to see how well it was implemented and how well it worked in practice. The results were frankly surprisingly good and I was able to build a fully functional shopping list application with it in a relatively short amount of time, with minimal friction or trouble, I did manage to segfault a couple of times but that's to be expected and honestly half the fun, made me kinda nostalgic in fact. I've also spoken about the library with its author and maintainer &lt;a href="https://github.com/rcalixte" rel="noopener noreferrer"&gt;rcalixte&lt;/a&gt; who is a super nice and helpful guy with an enthusiasm for tech and programming that is infectious and I can really relate to. So honestly if you've ever done any GUI programming before and you're interested in trying out Zig for GUI development I highly recommend you give libqt6zig a try, it's a solid library with a ton of potential.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Experience
&lt;/h2&gt;

&lt;p&gt;The very first touch point is the &lt;strong&gt;installation&lt;/strong&gt; of the bindings to your Zig project, this is fairly easy and well documented &lt;a href="https://github.com/rcalixte/libqt6zig?tab=readme-ov-file#building" rel="noopener noreferrer"&gt;here&lt;/a&gt;, after you install all your dependencies libraries, you can initialize your new zig project with &lt;code&gt;zig init&lt;/code&gt; as you always would, then you simply need to add the libqt6zig bindings to your zig project, you can do this a number of ways but my personal favorite is to simply use &lt;code&gt;zig fetch --save git+https://github.com/rcalixte/libqt6zig&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does Zig fetch do?
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;zig fetch&lt;/code&gt; command will add the specified dependency to your &lt;code&gt;build.zig.zon&lt;/code&gt; with two fields, &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;hash&lt;/code&gt;, the &lt;code&gt;url&lt;/code&gt; field is the URL of the git repository and the &lt;code&gt;hash&lt;/code&gt; field is a hash of the commit at the time you added the dependency, this ensures that your project will always use the same version of the dependency even if the repository is updated later on. You can also specify the hash in your &lt;code&gt;zig fetch&lt;/code&gt; command by adding &lt;code&gt;#&amp;lt;commit-hash&amp;gt;&lt;/code&gt; like so: &lt;code&gt;zig fetch --save https://github.com/username/repository/archive/&amp;lt;commit&amp;gt;&lt;/code&gt; which will essentially lock your dependency to that specific commit. Note even if you omit the hash it will lock your dependency in &lt;code&gt;build.zig.zon&lt;/code&gt; to the the latest commit at the time you added the dependency. &lt;/p&gt;

&lt;p&gt;What it then does is fetch the contents of the repository and stores it in your zig cache directory &lt;code&gt;.zig-cache&lt;/code&gt;, so that it does not need to refetch it every time you build your project nor does it need internet connectivity every time you build your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Library
&lt;/h3&gt;

&lt;p&gt;We're not quite done yet, after you've added the dependency to your project you need to actually link it in your &lt;code&gt;build.zig&lt;/code&gt; file, this is fairly straightforward as well, you simply need to add the following lines to your &lt;code&gt;build.zig&lt;/code&gt; file, so that zig build knows to link the libqt6zig library when building your project. Luckily rxcalixte has documented this process well in his readme in the &lt;a href="https://github.com/rcalixte/libqt6zig?tab=readme-ov-file#usage" rel="noopener noreferrer"&gt;Usage&lt;/a&gt; section. I recommend you check it out for the full details as it is subject to change and a link to the readme will always be best for the most up to date information. &lt;/p&gt;

&lt;h2&gt;
  
  
  OK Now let's Build Something
&lt;/h2&gt;

&lt;p&gt;This is what we are building:&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdayvster.com%2Fimages%2Fblog%2Fzigqt%2Fshopping-list-screenshot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdayvster.com%2Fimages%2Fblog%2Fzigqt%2Fshopping-list-screenshot.png" alt="Shopping List App Screenshot" width="711" height="922"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;So as I've mentioned before I decided to build a simple shopping list application, nothing fancy just a basic CRUD application that allows you to add items to your shopping list, mark them as bought, upon which I want to apply some styles to it such as a strikethrough effect and a different color to indicate that the item has been bought and sort the item to the bottom of the list so that the unbought items are always at the top of the list. We also wanna be able to easily clean up the entire list with a single button click on "Clear All" or just clear all bought items with a click on "Clear Bought" which will only remove all the items that have been marked as bought.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opening the Application Window
&lt;/h2&gt;

&lt;p&gt;The first step to any GUI application is to first open the window, I won't make the obvious bad pun here, you're welcome for that by the way. To open a window with &lt;code&gt;libqt6zig&lt;/code&gt; is fairly straightforward, you simply need to create a new widget and tell Qt to show it. That widget will serve as the main application window, and everything else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New2&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;qwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetWindowTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Shopping List"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetMinimumSize2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might be confused by the use of &lt;code&gt;New2&lt;/code&gt; here instead of just &lt;code&gt;New&lt;/code&gt;, this is because Qt has a lot of overloaded functions and Zig does not support function overloading, so the bindings library has to create multiple versions of the same function with different names to accommodate for that. In this case &lt;code&gt;New2&lt;/code&gt; is the version of &lt;code&gt;New&lt;/code&gt; that takes no arguments, whereas &lt;code&gt;New&lt;/code&gt; takes a parent widget as an argument.&lt;br&gt;
After creating the window widget we set its title and minimum size, then we can show it by calling &lt;code&gt;qwidget.Show(window);&lt;/code&gt;. Which I will do later after setting up the rest of the application, but if you are just playing around feel free to call it right after setting the title and size to see the window pop up.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up the Layout
&lt;/h2&gt;

&lt;p&gt;In Qt, layouts are what make widgets actually show up in a sensible way. to achieve what we have in the screenshot above we need to set up a vertical box layout or &lt;code&gt;QVBoxLayout&lt;/code&gt; if you are familiar with Qt terminology. A vertical box layout is a layout that arranges widgets vertically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;main_layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qvboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New2&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;qvboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetSpacing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qvboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetContentsMargins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes a nice vertical layout with some breathing room for the elements it will contain.&lt;/p&gt;

&lt;p&gt;Now let's make our first row which will contain the input field and the add button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;input_row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qhboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New2&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;qhboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetSpacing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;item_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlineedit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New2&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;qlineedit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetPlaceholderText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"e.g. Milk, Eggs, Bread"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;add_btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qpushbutton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Add"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qpushbutton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetFixedWidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add_btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we have here is a horizontal box layout or &lt;code&gt;QHBoxLayout&lt;/code&gt; that will contain our input field and add button. We set the spacing between the elements to 6 pixels for a bit of breathing room. Then we create a &lt;code&gt;QLineEdit&lt;/code&gt; widget which is the input field where the user can type in the name of the item they want to add to their shopping list. We also set a placeholder text to give the user an idea of what to type in. Finally we create a &lt;code&gt;QPushButton&lt;/code&gt; widget which is the add button that the user will click to add the item to their shopping list. We set its fixed width to 90 pixels so that it doesn't stretch too much.&lt;/p&gt;

&lt;p&gt;That's the nice part about Qt+Zig every call here is explicit and tells you exactly what it does, no magic or hidden behavior, no global "current layout" no hidden parent assignments, just pure explicitness which I find a lot nicer to read.&lt;/p&gt;

&lt;p&gt;Now we can attach our input row and add button to the horizontal layout and then the horizontal layout to the main vertical layout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="n"&gt;qhboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item_input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qhboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_btn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c"&gt;// ...More code will go here&lt;/span&gt;
&lt;span class="c"&gt;// Add the input_row to the main layout&lt;/span&gt;
&lt;span class="c"&gt;// qvboxlayout.AddLayout(main_layout, input_row);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see we are simply adding the widgets in the order we want them to appear in the layout, then taking that layout and adding it to the main layout. This should produce the top row of our application. But it won't do much so far, we still need to add the list view and the clear buttons. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the List and Controls
&lt;/h2&gt;

&lt;p&gt;Next up is the list where items will appear and the two control buttons at the very bottom with the labels "Clear All" and "Clear Bought".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;list_label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlabel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Items:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qlabel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetStyleSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"font-weight: 600; margin-top:6px; margin-bottom:2px;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;list_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New2&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Qt comes with a lot of useful widgets that you can use out of the box, in this case we are using a &lt;code&gt;QLabel&lt;/code&gt; widget to display the "Items:" label above the list and a &lt;code&gt;QListWidget&lt;/code&gt; to display the list of items. The &lt;code&gt;QListWidget&lt;/code&gt; is a convenient widget that allows us to easily add, remove and manipulate items in the list. We also set a bit of styling on the label to make it look a bit nicer, if you are familiar with CSS this should look pretty straightforward to you.&lt;/p&gt;

&lt;p&gt;Now for the bottom control row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;controls_row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qhboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New2&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;clear_btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qpushbutton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Clear All"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qpushbutton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetFixedWidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clear_btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;clear_bought_btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qpushbutton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Clear Bought"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qpushbutton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetFixedWidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clear_bought_btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;qhboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controls_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clear_btn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qhboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controls_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clear_bought_btn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we create another horizontal box layout for the control buttons, then we create two &lt;code&gt;QPushButton&lt;/code&gt; widgets for the "Clear All" and "Clear Bought" buttons. We set their fixed widths to make them look a bit more uniform. Finally we add the buttons to the horizontal layout.&lt;/p&gt;

&lt;p&gt;And just like that we can now bring it all together by adding everything to the main vertical layout in order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="n"&gt;qvboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;// input row goes here where it belongs&lt;/span&gt;
&lt;span class="n"&gt;qvboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_label&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qvboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qvboxlayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;controls_row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point our layout hierarchy looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Main Window
├── Input Row
│   ├── QLineEdit (item_input)
│   └── QPushButton ("Add")
├── QLabel ("Items:")
├── QListWidget (list_widget)
└── Controls Row
    ├── QPushButton ("Clear All")
    └── QPushButton ("Clear Bought")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now we got our layouts and widgets all set up we can get to my favorite part which is the logic and interactivity/functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Context System
&lt;/h2&gt;

&lt;p&gt;One of the more clever parts of this app is how it maps each widget to its corresponding Zig state.&lt;br&gt;
Because Qt signals are C-style callbacks with no built-in state, we use a global hash map to link each widget pointer to its appropriate state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AppCtxMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AutoHashMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ctx_map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AppCtxMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each context holds references to the main widgets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;item_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we initialize the app, we register everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;ctx_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;ctx_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;ctx_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add_btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;ctx_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clear_btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;ctx_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;ctx_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clear_bought_btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s how any callback can later say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;get_ctx_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctx_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;orelse&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only problem and downside with this is that we are relying heavily on opaque pointers which are not type safe at all, so we have to be extra careful when working with them to avoid segfaults and other memory issues and this is precisely where I ran into some self inflicted issues and headaches with this application, completely my own fault for accepting a lack of type safety and memory safety for the sake of convenience, but it is what it is and it's not like I was building a production ready application here, I just wanted to be clever and explore Zig and Qt together, so I accepted the tradeoff. If you decide to build something more serious with this approach I highly recommend you find a way to make it more type safe and memory safe. This is just an example of me experimenting and exploring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hooking up the Logic
&lt;/h2&gt;

&lt;p&gt;Now that we have the UI and context system ready, it’s time to connect everything together. This is where the application actually comes alive.&lt;br&gt;
In Qt, widgets typically use a signal and slot mechanism, where signals (like button clicks) are connected to functions (slots) that respond to those events.&lt;br&gt;
In libqt6zig, these signals are exposed as plain callback functions that you can assign in Zig, so you can handle events like button clicks in a straightforward way.&lt;/p&gt;
&lt;h3&gt;
  
  
  Handling the Add Button
&lt;/h3&gt;

&lt;p&gt;When the user clicks Add, we want to: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Grab the text from the input field.&lt;/li&gt;
&lt;li&gt;If it’s not empty, create a new list item.&lt;/li&gt;
&lt;li&gt;Add it to the list widget.&lt;/li&gt;
&lt;li&gt;Clear the input field.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;on_add_clicked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;callconv&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="py"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_ctx_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;orelse&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;item_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlineedit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;item_input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;len&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;list_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidgetitem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;qlineedit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;item_input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We connect this function to the Add button’s clicked signal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="n"&gt;qpushbutton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnClicked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add_btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_add_clicked&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handling the Clear Buttons
&lt;/h3&gt;

&lt;p&gt;Next, we want to make the "Clear All" and "Clear Bought" buttons actually do something. The logic is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clear All&lt;/strong&gt;: Remove all items from the list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear Bought&lt;/strong&gt;: Remove only items that have been marked as bought (we’ll handle this later by checking a property or style on each item).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;on_clear_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;callconv&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="py"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_ctx_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;on_clear_bought&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;callconv&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="py"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_ctx_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c"&gt;// iterate backwards so removing items doesn't shift unprocessed indices&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;c_int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;itm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;itm&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidgetitem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CheckState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;itm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c"&gt;// remove and delete the item&lt;/span&gt;
                &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TakeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="c"&gt;// taken is removed from the widget; drop the pointer (Qt will clean up)&lt;/span&gt;
                &lt;span class="mi"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;taken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see in &lt;code&gt;on_clear_all&lt;/code&gt; the logic is pretty straightforward, we simply call &lt;code&gt;qlistwidget.Clear&lt;/code&gt; and give it a reference to our list widget, then Qt will handle the rest for us.&lt;/p&gt;

&lt;p&gt;Now the more clever part comes in &lt;code&gt;on_clear_bought&lt;/code&gt;, here we iterate through all the items in the list widget and check their state, however we know that we will sort bought items to the bottom of the list when the user ticks them as "done" or in this case "bought", so we can optimize this a bit by iterating backwards through the list and stopping as soon as we find an unbought item, because all bought items will be at the bottom of the list. This way we avoid unnecessary iterations and checks.&lt;/p&gt;

&lt;p&gt;All there's left to do is to actually hook these two up to their respective buttons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="n"&gt;qpushbutton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnClicked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clear_btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_clear_all&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;qpushbutton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnClicked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clear_bought_btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_clear_bought&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Marking Items as Bought
&lt;/h3&gt;

&lt;p&gt;Finally, we need to handle changes to the items themselves. In our shopping list, items can be marked as bought by checking a checkbox next to them. In Qt, this is done via the CheckState property of a QListWidgetItem. Whenever the user checks or unchecks an item, we want to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Apply a strikethrough effect to indicate that the item is bought.&lt;/li&gt;
&lt;li&gt;Move the bought item to the bottom of the list to keep unbought items at the top.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;on_item_changed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;callconv&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="py"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_ctx_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidgetitem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CheckState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;is_checked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidgetitem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;qfont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetStrikeOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_checked&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;qlistwidgetitem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TakeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_checked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddItem2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InsertItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how we use &lt;code&gt;TakeItem&lt;/code&gt; to temporarily remove the item from the list, then re-insert it in the appropriate position. This is a common trick in Qt when you want to reorder items dynamically.&lt;/p&gt;

&lt;p&gt;Then we connect this callback to the list widget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="n"&gt;qlistwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnItemChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_item_changed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that every time an item's checkbox is toggled, the visual state updates and the item is moved accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling the Return Key
&lt;/h3&gt;

&lt;p&gt;For convenience, we also allow the user to add items by pressing Enter in the input field. This is done using the &lt;code&gt;OnReturnPressed&lt;/code&gt; signal of &lt;code&gt;QLineEdit&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;on_return_pressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;callconv&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="py"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_ctx_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlineedit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;item_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;len&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;qlineedit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnReturnPressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_return_pressed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function simply grabs the text from the input, adds the item if it’s non-empty, and clears the input field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Items Helper
&lt;/h3&gt;

&lt;p&gt;Both the Add button and the Enter key use the same helper function to actually create and insert the list item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidgetitem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New7&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;list_widget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qlistwidgetitem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;qlistwidgetitem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;qnamespace_enums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;ItemFlag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;ItemIsUserCheckable&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;qlistwidgetitem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetCheckState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qnamespace_enums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;CheckState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Unchecked&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;qlineedit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;item_input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it easy to maintain consistent behavior across multiple input methods. Notice the 0x20 flag, this is Qt::ItemIsUserCheckable, which allows the checkbox to appear next to the item.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;At this point, all the interactive logic is wired up. Users can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type an item and press Enter or click Add to add it.&lt;/li&gt;
&lt;li&gt;Check/uncheck items to mark them bought/unbought with a strikethrough effect.&lt;/li&gt;
&lt;li&gt;Clear all items or only bought items using the respective buttons.&lt;/li&gt;
&lt;li&gt;Automatically reorder items so unbought items remain at the top.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a fully functional, minimal shopping list application built entirely in Zig using direct Qt bindings.&lt;/p&gt;

&lt;p&gt;With the functionality in place, the next step is to make the application actually look nice. Qt widgets come with default styles that are functional but not particularly attractive. Fortunately, Qt supports CSS-like styling through SetStyleSheet, and libqt6zig exposes this feature directly.&lt;/p&gt;

&lt;p&gt;Here’s the style sheet I used for the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;const&lt;/span&gt; &lt;span class="nt"&gt;STYLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="s1"&gt;"QWidget { background: #121212; color: #e6e6e6; font-family: 'Sans'; }"&lt;/span&gt;
  &lt;span class="s1"&gt;"QListWidget { background: #1e1e1e; color: #e6e6e6; border: 1px solid #2a2a2a; }"&lt;/span&gt;
  &lt;span class="s1"&gt;"QListWidget::item { padding: 6px 4px; }"&lt;/span&gt;
  &lt;span class="s1"&gt;"QListWidget::item:selected { background: #2a2a2a; color: #ffffff; }"&lt;/span&gt;
  &lt;span class="s1"&gt;"QLineEdit { background: #222222; color: #e6e6e6; border: 1px solid #2a2a2a; padding: 6px; border-radius: 4px; }"&lt;/span&gt;
  &lt;span class="s1"&gt;"QLineEdit::placeholder { color: #9a9a9a; }"&lt;/span&gt;
  &lt;span class="s1"&gt;"QPushButton { background: #2a2a2a; color: #e6e6e6; border: 1px solid #333; padding: 4px 8px; border-radius: 4px; }"&lt;/span&gt;
  &lt;span class="s1"&gt;"QPushButton:hover { background: #313131; }"&lt;/span&gt;
  &lt;span class="s1"&gt;"QPushButton:pressed { background: #3a3a3a; }"&lt;/span&gt;
  &lt;span class="s1"&gt;"QLabel { color: #e6e6e6; font-weight: 600; }"&lt;/span&gt;
&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can apply this style sheet to the main window like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="n"&gt;qwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetStyleSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;STYLE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or alternatively, you can make a new file named &lt;code&gt;style.qss&lt;/code&gt; and load it at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;qss_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/shoppinglist/style.qss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;qss_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qss_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qss_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readToEndAlloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;qwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetStyleSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;qwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetStyleSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;STYLE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way you can easily tweak the styles without recompiling the app each time, additionally you can store this file anywhere on your system and allow users to customize the look of the app by providing their own style sheets if you want to get fancy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Application
&lt;/h2&gt;

&lt;p&gt;Now we just need to show the window and start the event loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="n"&gt;qwidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qapplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exec&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there we have it, a simple yet fully functional shopping list application, built entirely with &lt;code&gt;Zig&lt;/code&gt; and &lt;code&gt;Qt&lt;/code&gt; using direct bindings, with not a hint of &lt;code&gt;QML&lt;/code&gt; in sight. Sure, I opted to rely heavily on opaque pointers, trading off some type safety for convenience and rapid development, and I fully accept that tradeoff. For a more serious project, I would strongly recommend finding a way to make the code safer and more type-aware. Yet, despite these compromises, I was able to produce a complete, cross-platform GUI application in surprisingly little time and with minimal code. I honestly didn’t think it would be this straightforward. This is a testament both to the power of &lt;code&gt;Zig&lt;/code&gt; as a systems programming language and to the excellent work of &lt;code&gt;rcalixte&lt;/code&gt; in creating the &lt;code&gt;libqt6zig&lt;/code&gt; bindings.&lt;/p&gt;

&lt;p&gt;This wasn’t the only experiment I tried with &lt;code&gt;libqt6zig&lt;/code&gt;. I also built a minimal launcher for my &lt;code&gt;Hyperland&lt;/code&gt; setup, which presented some intriguing challenges related more to &lt;code&gt;Wayland&lt;/code&gt; than the library itself. With a mix of clever window flags, absolute positioning, and careful sizing, I managed to create a “floating” window that escaped &lt;code&gt;Hyperland&lt;/code&gt;’s tiling behavior. Thankfully, &lt;code&gt;libqt6zig&lt;/code&gt; is robust enough that I didn’t need to manipulate &lt;code&gt;XCB&lt;/code&gt; or &lt;code&gt;wlroots&lt;/code&gt; directly. The bindings handled everything for me, which made the process far smoother, though it required some trial and error.&lt;/p&gt;

&lt;p&gt;During these experiments, I also felt the absence of features like generics and compile-time reflection, which would have allowed for cleaner, more type-safe abstractions. While &lt;code&gt;Zig&lt;/code&gt; does offer &lt;code&gt;comptime&lt;/code&gt; capabilities, I couldn’t quite achieve what I envisioned. I also missed having access to a &lt;code&gt;QTNativeWindow&lt;/code&gt; abstraction, which would have simplified certain window management tasks. These limitations stem more from the current state of the bindings than from &lt;code&gt;Qt&lt;/code&gt; itself. I’ve discussed these points with the library’s author, who is aware and might implement them in the future. Until then, patience is key. We’re benefiting from the dedication of a single maintainer producing high-quality, low-level bindings for &lt;code&gt;Zig&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflections on the Library
&lt;/h2&gt;

&lt;p&gt;Working with &lt;code&gt;libqt6zig&lt;/code&gt; was an eye-opening experience. Right from the start, I was impressed by how faithfully it exposes Qt’s C++ API while keeping the interface accessible to Zig developers. The library is surprisingly complete, covering most of the widgets, layouts, and common functionality you would expect from a typical Qt project. Despite being maintained by a single developer, the bindings are robust and consistent, which made experimentation smooth and enjoyable.&lt;/p&gt;

&lt;p&gt;One thing that stood out is how much control you get over the GUI elements. Every widget, layout, and property is explicit, which forces you to think about your UI structure carefully. Unlike higher-level frameworks that abstract away the details, &lt;code&gt;libqt6zig&lt;/code&gt; lets you handle memory management, widget hierarchies, and layout logic directly. This level of control can be intimidating at first, but it quickly becomes empowering once you get used to it. You feel like you are working with the core of the system rather than relying on layers of abstraction.&lt;/p&gt;

&lt;p&gt;At the same time, the library has some limitations. Working with opaque pointers can feel risky because it removes compile-time type safety, and the lack of certain abstractions like generics or a &lt;code&gt;QTNativeWindow&lt;/code&gt; wrapper means you occasionally need to write more boilerplate. However, these trade-offs are manageable, and the experience is educational. You learn not only how Qt works but also how to structure a cross-platform GUI application effectively in Zig.&lt;/p&gt;

&lt;p&gt;Overall, my experience with &lt;code&gt;libqt6zig&lt;/code&gt; was extremely positive. It made creating a fully functional shopping list application straightforward and even enjoyable. The library is well-documented, the API feels natural for Zig, and the bindings handle the low-level details so you can focus on building your application rather than wrestling with the underlying system. The potential for future improvements is exciting, and I look forward to seeing how the library evolves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, using &lt;code&gt;Zig&lt;/code&gt; with &lt;code&gt;libqt6zig&lt;/code&gt; provides a powerful combination for building cross-platform GUI applications. While it requires attention to memory management and pointer safety, the explicitness and control make it a rewarding experience. You can produce functional, responsive, and visually appealing applications with surprisingly little code. &lt;/p&gt;

&lt;p&gt;For anyone interested in exploring GUI development in Zig, &lt;code&gt;libqt6zig&lt;/code&gt; is an excellent starting point. It demonstrates that Zig can be used effectively beyond systems programming, opening the door to lightweight, high-performance desktop applications. The library is already impressive, and with continued development, it could become a go-to choice for Zig developers looking to build modern GUIs. My experiments with both the shopping list application and my Hyperland launcher showed that even complex interactions are achievable with patience and careful design. Overall, this experience reinforced my enthusiasm for Zig and its growing ecosystem, and it leaves me optimistic about the future of GUI development in this language.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and Resources
&lt;/h2&gt;

&lt;p&gt;If you want to explore &lt;code&gt;libqt6zig&lt;/code&gt; yourself or learn more about the library and its author, here are some useful links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dayvster/zig-qt-shoppinglist" rel="noopener noreferrer"&gt;Shopping List App Source Code&lt;/a&gt; – The complete source code for the shopping list application built in this article.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rcalixte/libqt6zig" rel="noopener noreferrer"&gt;libqt6zig GitHub Repository&lt;/a&gt; – The library used for these experiments.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rcalixte" rel="noopener noreferrer"&gt;rcalixte GitHub Profile&lt;/a&gt; – The author and maintainer of libqt6zig.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://doc.qt.io/" rel="noopener noreferrer"&gt;Qt Documentation&lt;/a&gt; – Official Qt documentation for widgets, layouts, and styling.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ziglang.org/" rel="noopener noreferrer"&gt;Zig Programming Language&lt;/a&gt; – Learn more about Zig, its syntax, and features.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://doc.qt.io/qt-6/stylesheet-syntax.html" rel="noopener noreferrer"&gt;QSS Styling Guide&lt;/a&gt; – Qt's CSS-like styling system for widgets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exploring these resources will help you get started with Zig GUI development and allow you to dive deeper into creating more complex applications.&lt;/p&gt;

</description>
      <category>zig</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Miss when Software Ended</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Thu, 16 Oct 2025 14:03:37 +0000</pubDate>
      <link>https://dev.to/dayvster/i-miss-when-software-ended-43h</link>
      <guid>https://dev.to/dayvster/i-miss-when-software-ended-43h</guid>
      <description>&lt;h2&gt;
  
  
  We used to ship software that ended
&lt;/h2&gt;

&lt;p&gt;Remember when software had an end, like a movie or a book? You bought it, installed it, used it. There were no weekly updates, no monthly subscriptions, no constant notifications to update or accept frankly ever more intrusive permissions or ridiculous terms of service. Software was a product, not a service. You owned it, you controlled it. It was yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happened?
&lt;/h2&gt;

&lt;p&gt;Somewhere along the way, software companies decided that they could make more money by turning software into a service. Instead of selling you a product, they would sell you a subscription. This meant that you would have to pay them every month or year to continue using their software. This also meant that they could update the software whenever they wanted.&lt;/p&gt;

&lt;p&gt;Now in my honest opinion this was mostly a good thing, because it meant that software could be updated without much fuss, bugs and issues could be patched out and in the early days of SaaS you'd mostly get more value for your money, because you'd get new features and improvements without having to buy a new version of the software. &lt;/p&gt;

&lt;p&gt;Back then when software was a full on product and delivery of updates was not as seamless as it is today, you'd often have to wait quite a while for a patch to get released and available for download or purchase via service packs or even worst case scenario new versions of the software. Think Photoshop 6 to Photoshop 7, or Windows XP to Windows Vista. &lt;/p&gt;

&lt;p&gt;So SaaS did solve a really great problem there where you did not have to be locked to a certain version of the software with all it's bugs, issues, security flaws, or just lack of features. &lt;/p&gt;

&lt;h2&gt;
  
  
  When Updates Stopped feeling like progress
&lt;/h2&gt;

&lt;p&gt;However over time the promise got blurry, updates stopped feeling like more value delivered to the end user and more like obligations to keep the service running. Updates became more frequent, more intrusive and more often than not they would break things rather than fix them.&lt;/p&gt;

&lt;p&gt;Features quietly got removed, or hidden behind more premium paywalls so that companies could incentivize users to slowly upgrade to more expensive plans. Instead of delivering valuable updates or features SaaS was now used to fine tune companies revenue streams. &lt;/p&gt;

&lt;p&gt;And at this point I was still okay with it, because as an end user I still got value for my money, it cost more money sure but the way most companies structured their plans it was negligible for most common users.&lt;/p&gt;

&lt;p&gt;Can't fault a company for trying to make money off of their work right? As long as they pay their developers fairly and deliver a good product I don't see a problem with it.&lt;/p&gt;

&lt;p&gt;But it still felt odd because we were essentially renting software rather than owning it, we no longer bought a product but rather a service that was completely in the hands of the provider and for a while that relationship was pretty fair and balanced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Subscription Model got out of hand
&lt;/h2&gt;

&lt;p&gt;But then came the era of "everything subscription". Suddenly every single piece of software you used was a subscription. No matter how small or trivial the functionality was it would sooner or later succumb to the SaaS demon. &lt;/p&gt;

&lt;p&gt;This is how we got simple things like Postman, Git clients, Email clients and in some extreme cases even very basic text editors that we suddenly had to pay a monthly fee to use.&lt;/p&gt;

&lt;p&gt;This is where I started to feel uneasy about the whole SaaS model, because no longer did the value provided to the end user justify the cost of the subscription. Do you as a user really need all the features that the $14 a month postman plan provides? Or do you really need to pay $5 a month for a git client how about that $20 a month email client? &lt;/p&gt;

&lt;p&gt;The price is not even my main problem, I want to support all of these companies and their developers, I want them to be able to make a living off of their work, but they do not offer me a way to pay them fairly.&lt;/p&gt;

&lt;p&gt;All of the examples above have free tiers and then the next payed tier is a monthly subscription filled to the brim with stuff that I will very very rarely need. &lt;/p&gt;

&lt;p&gt;I would love to be able to pay a one time fee of whatever their software is worth to them and I'd happily be locked at a certain version of their software that I can use for as long as I want. But I am not allowed to do that because the subscription model does not allow for it.&lt;/p&gt;

&lt;p&gt;I have to either use the free tier or pay a monthly subscription filled with stuff that I do not really need or want.&lt;/p&gt;

&lt;p&gt;But the worst part is yet to come.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lack of Ownership means Lack of Control
&lt;/h2&gt;

&lt;p&gt;This is where the SaaS model really starts to fall apart for me, because I no longer own the software I use. I’m merely renting it. And as a renter, I have no real control.&lt;/p&gt;

&lt;p&gt;The software we rely on for our work, our hobbies, and sometimes even our ability to function day to day can be taken away at any time for any reason. A company can shut it down, change the pricing, or rewrite the terms of service overnight. If you don’t like it, you’re out.&lt;/p&gt;

&lt;p&gt;In some extreme cases, users have been forced to waive their basic consumer rights just to keep using software or the hardware it runs on. Disney, for instance, once argued in court that by using their streaming service you agreed not to sue them even if they acted illegally or unethically. They actually tried to use that argument to dodge a lawsuit from a man whose wife died in Disneyland after an allergic reaction, even though the staff had been warned about her allergy.&lt;/p&gt;

&lt;p&gt;That’s the level of power imbalance we’ve created. Software you rent can demand that you accept terms that are questionable at best and dangerous at worst, and your only alternative is to lose access to something you depend on.&lt;/p&gt;

&lt;p&gt;This is not what SaaS was meant to be. The original idea was to deliver more value, not to take away control or ownership. SaaS should have evolved into a model of better service, not deeper dependency.&lt;/p&gt;

&lt;p&gt;And now, there’s a new layer to it. Companies like Adobe and Notion quietly use your work, your data, and your creations to train their AI models. All justified by a single line in an update that you “agreed” to when you clicked through just to get back to work.&lt;/p&gt;

&lt;p&gt;That’s not progress. That’s permission laundering.&lt;/p&gt;

&lt;p&gt;So where does this leave us? Somewhere between the promise SaaS once held and the reality many of us experience today. The model itself isn’t broken, but the way it’s often applied is. Updates, subscriptions, and cloud features should be tools to deliver value, not mechanisms to extract it.&lt;/p&gt;

&lt;p&gt;Maybe the answer isn’t abandoning SaaS entirely. Maybe it’s building a model that respects both the user and the developer, giving people choice, stability, and control while still funding ongoing improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Third More Reasonable Way?
&lt;/h2&gt;

&lt;p&gt;There’s a middle ground that gets mostly ignored in today’s software landscape. It’s simple: give users choice. Let people decide how they want to interact with your product.&lt;/p&gt;

&lt;p&gt;Some users are perfectly fine renting access, staying on the bleeding edge, and paying for convenience. Others just want to own the software they rely on, lock in a version, and use it indefinitely without worrying about subscriptions, sudden paywalls, or disappearing features.&lt;/p&gt;

&lt;p&gt;Imagine a model where the same software offers both paths:&lt;/p&gt;

&lt;p&gt;A one-time purchase option for users who are happy to stay on version X. No forced upgrades. No subscription. Just a finished product they can control.&lt;/p&gt;

&lt;p&gt;A subscription option for users who want continuous updates, cloud features, and new tools as soon as they’re ready.&lt;/p&gt;

&lt;p&gt;This approach respects both sides. It honors the nostalgic value of software as a finished product while keeping the benefits of SaaS alive. Users get what they need, developers get paid fairly, and everyone keeps a sense of control.&lt;/p&gt;

&lt;p&gt;It’s not about rejecting SaaS. It’s about rebalancing it. Making updates and subscriptions tools for value, not leverage. Let people pay for what they actually want, and stop using access as a weapon.&lt;/p&gt;

&lt;p&gt;Software can still evolve. SaaS can still be great. But it doesn’t have to come at the cost of ownership, autonomy, or trust.&lt;/p&gt;

&lt;p&gt;This approach isn’t just about giving people options. It’s about &lt;strong&gt;returning control&lt;/strong&gt; to the users, respecting the work they create, and protecting their data. Ownership and privacy aren’t optional features, they are the foundation of trust between software and the people who rely on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reclaiming What’s Ours
&lt;/h2&gt;

&lt;p&gt;Software belongs to the people who bought it. Full stop. We should control our tools, our work, and our data without being coerced or trapped by subscriptions, paywalls, or shady terms. Ownership and privacy are not optional conveniences, they are fundamental rights.&lt;/p&gt;

&lt;p&gt;I am asking for a software world where users are not just passive consumers. Let us own what we create. Let us decide if we want to stay on a version indefinitely or pay for updates on our terms. Let companies innovate without taking control from the people who depend on their products.&lt;/p&gt;

&lt;p&gt;This is not a plea for nostalgia. It is a demand for respect, trust, and autonomy. SaaS can be powerful, but it must not come at the cost of ownership or privacy. Users deserve tools that serve them, not cages disguised as convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving Forward
&lt;/h2&gt;

&lt;p&gt;We don’t have to accept a software landscape where ownership, control, and privacy are optional. Developers and users can coexist in a fairer ecosystem, one where innovation thrives without taking advantage of those who rely on the software.&lt;/p&gt;

&lt;p&gt;If you’re a developer, consider offering choice. Let users pay for updates if they want them, but don’t trap those who are happy with a stable version. Respect the work your users create and give them control over their data.&lt;/p&gt;

&lt;p&gt;If you’re a user, don’t settle for losing ownership. Support companies that value transparency, respect privacy, and give you options. Demand software that works for you, not against you.&lt;/p&gt;

&lt;p&gt;SaaS can still be powerful, flexible, and convenient. But it should serve the people who use it, not turn them into passive renters of their own tools. Let’s build a future where software ends gracefully, respectfully, and on our terms, while still evolving for those who want it to.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Accidentally Made a Zig dotenv Parser</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Tue, 07 Oct 2025 19:00:00 +0000</pubDate>
      <link>https://dev.to/dayvster/accidentally-made-a-zig-dotenv-parser-311f</link>
      <guid>https://dev.to/dayvster/accidentally-made-a-zig-dotenv-parser-311f</guid>
      <description>&lt;p&gt;Recently I've made a Zig based CLI argument parser called &lt;a href="https://github.com/dayvster/argh" rel="noopener noreferrer"&gt;argh&lt;/a&gt; I've even written myself a roadmap I'd like to follow with this project. And so far I have been following it pretty well. However last week I began working on the next item on my roadmap which was to add support for environment variables to the arg parser, where you could set an environment variable to set the value of a flag. &lt;/p&gt;

&lt;p&gt;Not many use cases for it I know it's more of an edge case but I thought it would be a fun exercise to implement that. So I started working on it. I carefully scaffolded out the feature what functions I'd like to see in it how they should behave what guard rails I'd like to set to ensure type safety, memory safety, and so on.&lt;/p&gt;

&lt;p&gt;As I was implementing all the things I scaffolded out one by one I realized that I was basically writing a fully fledged dotenv parser. And I thought to myself "hey this is pretty cool I should probably make this a separate library so other people can use it too" and it's not deeply coupled to the arg parser so it makes sense to do that. So I did. I extracted the code out into its own repository called &lt;a href="https://github.com/dayvster/zdotenv" rel="noopener noreferrer"&gt;zdotenv&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Library
&lt;/h2&gt;

&lt;p&gt;The dotenv parser itself is pretty simple to use and I've even provided a basic example and tests in my repository that you can check out to see how it works. But regardless here's a quick example of how to use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;@import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"std"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Dotenv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;@import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dotenv"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="py"&gt;Dotenv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;allocator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;page_allocator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;_&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"./examples/example.env"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOO"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOO={s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOO not found&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will load the environment variables from the specified file and you can then access them using the &lt;code&gt;get&lt;/code&gt; method. The &lt;code&gt;load&lt;/code&gt; method takes an array of file paths so you can load multiple dotenv files if you want to.&lt;/p&gt;

&lt;p&gt;That's it, that's all there is to it. It's simple and straightforward as a dotenv parser should ideally be.&lt;/p&gt;

&lt;p&gt;I've also added support for specifying custom .env file names since not everyone uses the default &lt;code&gt;.env&lt;/code&gt; file all the time, you may wanna dynamically load different env files based on the environment you're in (development, staging, production, etc). &lt;/p&gt;

&lt;p&gt;I've added support for comments, blank lines and values in double quotes. So it should cover most use cases out there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loads key-value pairs from &lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt; basically the core feature it does that it says, it parses an &lt;code&gt;.env&lt;/code&gt; file and loads the key-value pairs into memory so that you can access them later via the &lt;code&gt;get&lt;/code&gt; method.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supports comments and blank lines&lt;/strong&gt; it ignores comments and blank lines so you can have a well formatted &lt;code&gt;.env&lt;/code&gt; file without any issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Provides easy access to values via hash map&lt;/strong&gt; you can access the values using the &lt;code&gt;get&lt;/code&gt; method which returns an optional value so you can handle the case where the key doesn't exist. I picked a hash map for this since generally speaking your &lt;code&gt;.env&lt;/code&gt; files won't ever be that large so we can just store them in memory for easy and fast access.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory safe&lt;/strong&gt; all allocations are freed with a &lt;code&gt;deinit&lt;/code&gt; method so you don't have to worry about memory leaks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Well that's it for now. I'll be adding additional functionality and patches to the library as I see fit. But for now it's a pretty solid and usable dotenv parser that you can use in your Zig projects. I for one have already used it in my &lt;code&gt;notes CLI&lt;/code&gt; application I made a while back to compare Zig against Rust and it works like a charm.&lt;/p&gt;

&lt;p&gt;As always I welcome contributions and feedback so if you have any ideas or suggestions feel free to open an issue or a pull request on the repository.&lt;/p&gt;

&lt;p&gt;And if you have any questions or would like to reach out to me you can find me on &lt;a href="https://twitter.com/dayvsterdev" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; &lt;/p&gt;

</description>
      <category>zig</category>
      <category>programming</category>
      <category>discuss</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Is Odin Just a More Boring C</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Sat, 04 Oct 2025 15:21:49 +0000</pubDate>
      <link>https://dev.to/dayvster/is-odin-just-a-more-boring-c-9j7</link>
      <guid>https://dev.to/dayvster/is-odin-just-a-more-boring-c-9j7</guid>
      <description>&lt;h2&gt;
  
  
  Why I Tried Odin
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;My recent posts have been diving deep into Zig and C, a shift from my earlier focus on React and JavaScript. This isn’t a pivot but a return to my roots. I started programming at 13 with C and C++, and over the years, I’ve built a wide range of projects in systems programming languages like C, C++, Rust, and now Zig. From hobby experiments and custom Linux utilities to professional embedded systems work think vehicle infotainment, tracking solutions, and low-level components I’ve always been drawn to the power and precision of systems programming. Alongside this, I’ve crafted tools for my own environment and tackled plenty of backend engineering, blending my full-stack expertise with a passion for low-level control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Odin Caught My Eye
&lt;/h3&gt;

&lt;p&gt;I like many others initially dismissed Odin as that language that was primarily intended for game development. It took me a moment or should I say many moments to realize just how stupid that notion was. Because let's analyze what game development actually means, it means building complex systems that need to be efficient, performant and reliable. It means working with graphics, physics, input handling, networking and more. It means dealing with concurrency, memory management and low level optimizations. In other words, game development is a perfect fit for a systems programming language like Odin.&lt;/p&gt;

&lt;p&gt;So basically if it's intended for game development, then it should be a great fit for general systems programming, desktop applications and since game dev usually means manual memory management without a garbage collector, it should also be possible to some extent to use it for embedded systems. &lt;/p&gt;

&lt;p&gt;So after I've gave myself a good slap on the forehead for being a bit of an idiot. I decided why not give Odin a fair shot and build something useful with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project
&lt;/h2&gt;

&lt;p&gt;Now I may have been a bit liberal with the word useful there, what I actually decided to build was something that I usually like to build whenever I wanna try out a new language, namely a tiny key-value store with a pub/sub system. It won't win any awards for originality and I'm pretty sure the folks over at redis aren't exactly shaking in their boots. It is the most basic most barebones implementation of both lacking any real useful features that would make it usable in a production environment. But it is a good exercise in understanding the language and its capabilities.&lt;/p&gt;

&lt;p&gt;Mainly because it involves a few different aspects of programming that are relevant to systems programming. It involves data structures, memory management, concurrency and networking. &lt;/p&gt;

&lt;p&gt;And even if you create something as basic and lacking as I have in this example, you still have room for experimentation and exploration to add more features. &lt;/p&gt;

&lt;h3&gt;
  
  
  Building a Tiny KV Store With Pub/Sub
&lt;/h3&gt;

&lt;p&gt;My initial minimal proof of concept was simple and straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import "core:fmt"
import "core:time"

KVStore :: struct {
    store: map[string]string,
}

kvstore_init :: proc() -&amp;gt; KVStore {
    return KVStore{store = map[string]string{}}
}

kv_put :: proc(kv: ^KVStore, key: string, value: string) {
    kv.store[key] = value
}

kv_get :: proc(kv: ^KVStore, key: string) -&amp;gt; string {
    if value, ok := kv.store[key]; ok {
        return value
    }
    return ""
}

PubSub :: struct {
    subscribers: map[string][]proc(msg: string),
}

pubsub_init :: proc() -&amp;gt; PubSub {
    return PubSub{subscribers = map[string][]proc(msg: string){}}
}

subscribe :: proc(ps: ^PubSub, topic: string, handler: proc(msg: string)) {
    if arr, ok := ps.subscribers[topic]; ok {
        new_arr := make([]proc(msg: string), len(arr)+1);
        for i in 0..&amp;lt;len(arr) {
            new_arr[i] = arr[i];
        }
        new_arr[len(arr)] = handler;
        ps.subscribers[topic] = new_arr;
    } else {
        ps.subscribers[topic] = []proc(msg: string){handler};
    }
}

publish :: proc(ps: ^PubSub, topic: string, msg: string) {
    if handlers, ok := ps.subscribers[topic]; ok {
        for handler in handlers {
            handler(msg);
        }
    }
}

kv: KVStore;

main :: proc() {
    kv = kvstore_init();
    ps := pubsub_init();

    handler1 :: proc(msg: string) {
        fmt.println("Sub1 got:", msg);
        kv_put(&amp;amp;kv, "last_msg", msg);
    }

    handler2 :: proc(msg: string) {
        fmt.println("Sub2 got:", msg);
    }

    handler3 :: proc(msg: string) {
        fmt.println("Sub3 got:", msg);
    }

    subscribe(&amp;amp;ps, "demo", handler1);
    subscribe(&amp;amp;ps, "demo", handler2);
    subscribe(&amp;amp;ps, "demo", handler3);

    publish(&amp;amp;ps, "demo", "Welcome to dayvster.com");

    time.sleep(2 * time.Second);
    publish(&amp;amp;ps, "demo", "Here's another message after 2 seconds");

    last := kv_get(&amp;amp;kv, "last_msg");
    fmt.println("Last in kvstore:", last);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see it currently lacks any real error handling, concurrency and persistence. But it does demonstrate the basic functionality of a key-value store with pub/sub capabilities. What I have done is created two main structures, &lt;code&gt;KVStore&lt;/code&gt; and &lt;code&gt;PubSub&lt;/code&gt;. The &lt;code&gt;KVStore&lt;/code&gt; structure contains a map to store key-value pairs and provides functions to put and get values. The &lt;code&gt;PubSub&lt;/code&gt; structure contains a map of subscribers for different topics and provides functions to subscribe to topics and publish messages.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;main&lt;/code&gt; function initializes the key-value store and pub/sub system, defines a few handlers for incoming messages, subscribes them to a topic, and then publishes some messages to demonstrate the functionality.&lt;/p&gt;

&lt;p&gt;From this basic example we've explored how to handle memory management in Odin, how to work with data structures like maps and slices, and how to define and use procedures. &lt;/p&gt;

&lt;h3&gt;
  
  
  Memory Management
&lt;/h3&gt;

&lt;p&gt;Like C and Zig, Odin employs manual memory management, but it offers user-friendly utilities to streamline the process, much like Zig, in contrast to C’s more rudimentary approach. For instance, the &lt;code&gt;make&lt;/code&gt; function in Odin enables the creation of slices with a defined length and capacity, akin to Zig’s slice allocation. In the code above, &lt;code&gt;make([]proc(msg: string), len(arr)+1)&lt;/code&gt; generates a slice of procedure pointers with a length of &lt;code&gt;len(arr)+1&lt;/code&gt;. Essentially, it allocates memory on the heap and returns a slice header, which includes a pointer to the allocated memory, along with the length and capacity of the slice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;but how and when is that memory freed?&lt;/strong&gt; In this code, memory allocated by &lt;code&gt;make&lt;/code&gt; (e.g., for the slice in &lt;code&gt;subscribe&lt;/code&gt;) and for maps (e.g., &lt;code&gt;kv.store&lt;/code&gt; and &lt;code&gt;ps.subscribers&lt;/code&gt;) is not explicitly freed. Since this is a short-lived program, the memory is reclaimed by the operating system when the program exits. However, in a long-running application, you’d need to use Odin’s delete procedure to free slices and maps explicitly. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
kvstore_deinit :: proc(kv: ^KVStore) {
    delete(kv.store);
}

pubsub_deinit :: proc(ps: ^PubSub) {
    for topic, handlers in ps.subscribers {
        delete(handlers);
    }
    delete(ps.subscribers);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So let's add that in the &lt;code&gt;main&lt;/code&gt; function before it exits to ensure we clean up properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ... existing code ...
main :: proc() {
    // ... existing code ...
    pubsub_deinit(&amp;amp;ps);
    kvstore_deinit(&amp;amp;kv);
} // end of main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well would you look at that, we just added proper memory management to our tiny KV store with pub/sub system and all it took was a few lines of code. I'm still a huge fan of C but this does feel nice and clean, not to mention really readable and easy to understand.&lt;/p&gt;

&lt;p&gt;Is our code now perfect and fully memory safe? Not quite, it still needs error handling and thread safety(way later) for production use, but it’s a solid step toward responsible memory management.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding concurrency
&lt;/h3&gt;

&lt;p&gt;Enhancing Pub/Sub with Concurrency in Odin&lt;/p&gt;

&lt;p&gt;To make our pub/sub system more realistic, we've introduced concurrency to the publish procedure using Odin's core:thread library. This allows subscribers to process messages simultaneously, mimicking real-world pub/sub behavior. Since handler1 modifies kv.store via kv_put, we've added a mutex to KVStore to ensure thread-safe access to the shared map. Here's how it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Concurrent Execution with Threads&lt;/strong&gt;: The publish procedure now runs each handler in a separate thread created with thread.create. Each thread receives the handler and message via t.user_args, and thread.start kicks off execution. Threads are collected in a dynamic array (threads), which is cleaned up using defer delete(threads). The thread.join call ensures the program waits for all threads to finish, and thread.destroy frees thread resources. This setup enables handler1, handler2, and handler3 to process messages concurrently, with output order varying based on thread scheduling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Thread Safety with Mutex&lt;/strong&gt;: Since handler1 updates kv.store via kv_put, concurrent access could lead to race conditions, as Odin's maps aren't inherently thread-safe. To address this, a sync.Mutex is added to KVStore. The kv_put and kv_get procedures lock the mutex during map access, ensuring only one thread modifies or reads kv.store at a time. The mutex is initialized in kvstore_init and destroyed in kvstore_deinit.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;publish :: proc(ps: ^PubSub, topic: string, msg: string) {
    if handlers, ok := ps.subscribers[topic]; ok {
        threads := make([dynamic]^thread.Thread, 0, len(handlers))
        defer delete(threads)

        // Allocate ThreadArgs for each handler
        thread_args := make([dynamic]^ThreadArgs, 0, len(handlers))
        defer {
            for args in thread_args {
                free(args)
            }
            delete(thread_args)
        }

        for handler in handlers {
            msg_ptr := new(string)
            msg_ptr^ = msg
            t := thread.create(proc(t: ^thread.Thread) {
                handler := cast(proc(msg: string)) t.user_args[0]
                msg_ptr := cast(^string) t.user_args[1]
                handler(msg_ptr^)
                free(msg_ptr)
            })
            t.user_args[0] = rawptr(handler)
            t.user_args[1] = rawptr(msg_ptr)
            thread.start(t)
            append(&amp;amp;threads, t)
        }

        for t in threads {
            thread.join(t)
            thread.destroy(t)
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation adds concurrency by running each handler in its own thread, allowing parallel message processing. The mutex ensures thread safety for kv.store updates in handler1, preventing race conditions. Odin's core:thread library simplifies thread management, offering a clean, pthread-like experience. &lt;/p&gt;

&lt;p&gt;Odin’s threading feels like a bit like C’s pthreads but without the usual headache, and it’s honestly a breeze to read and write. For this demo, the mutex version keeps everything nice and tidy, However in a real application, you'd still want to consider more robust error handling and possibly a thread pool for efficiency and also some way to handle thread lifecycle and errors and so on...&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Persistence
&lt;/h2&gt;

&lt;p&gt;I haven't added persistence to this code-block personally because I feel that would quickly spiral the demo that I wanted to keep simple and focused into something much more complex. But if you wanted to add persistence, you could use Odin's &lt;code&gt;core:file&lt;/code&gt; library to read and write the &lt;code&gt;kv.store&lt;/code&gt; map to a file. You would need to serialize the map to a string format (like &lt;code&gt;JSON&lt;/code&gt; or &lt;code&gt;CSV&lt;/code&gt;) when saving and deserialize it when loading. Luckily odin has  &lt;code&gt;core:encoding/json&lt;/code&gt; and &lt;code&gt;core:encoding/csv&lt;/code&gt; libraries that can help with this. Which should at the very least make that step fairly trivial.&lt;/p&gt;

&lt;p&gt;So if you feel like it, give it a shot and let me know how it goes. Do note that this step is a lot harder than it may seem especially if you want to do it properly and performantly. &lt;/p&gt;

&lt;h2&gt;
  
  
  Now to Compile and Run
&lt;/h2&gt;

&lt;p&gt;Now here's the thing the first time I ran &lt;code&gt;odin build .&lt;/code&gt; I thought I messed up somewhere because, it basically took a split second and produced no output no warnings no nothing. But I did see that a binary was produced named after the folder I was in. So I ran it with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ ./kvpub
Sub1 got: Welcome to dayvster.com
Sub2 got: Welcome to dayvster.com
Sub3 got: Welcome to dayvster.com
Sub1 got: Here&lt;span class="s1"&gt;'s another message after 2 seconds
Sub2 got: Here'&lt;/span&gt;s another message after 2 seconds
Sub3 got: Here&lt;span class="s1"&gt;'s another message after 2 seconds
Last in kvstore: Here'&lt;/span&gt;s another message after 2 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there you have it, a tiny key-value store with pub/sub capabilities built in Odin. That compiled bizarrely fast, in fact I used a util (&lt;a href="https://github.com/dayvster/pulse" rel="noopener noreferrer"&gt;pulse&lt;/a&gt;) I wrote to benchmark processes and their execution time and it clocked in at a blazing 0.4 seconds to compile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ pulse &lt;span class="nt"&gt;--benchmark&lt;/span&gt; &lt;span class="nt"&gt;--cmd&lt;/span&gt; &lt;span class="s1"&gt;'odin build .'&lt;/span&gt; &lt;span class="nt"&gt;--runs&lt;/span&gt; 3

┌──────────────┬──────┬─────────┬─────────┬─────────┬───────────┬────────────┐
│ Command      ┆ Runs ┆ Avg &lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; ┆ Min &lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; ┆ Max &lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; ┆ Max CPU%  ┆ Max RAM MB │
╞══════════════╪══════╪═════════╪═════════╪═════════╪═══════════╪════════════╡
│ odin build &lt;span class="nb"&gt;.&lt;/span&gt; ┆ 3    ┆ 0.401   ┆ 0.401   ┆ 0.401   ┆ 0.00      ┆ 0.00       │
└──────────────┴──────┴─────────┴─────────┴─────────┴───────────┴────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well I couldn't believe that so I ran it again this time with &lt;code&gt;--runs 16&lt;/code&gt; to get a better average and it still came in at a very respectable &lt;code&gt;0.45&lt;/code&gt; (MAX) seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OK that is pretty impressive.&lt;/strong&gt; but consistent maybe my tool is broken? I'm not infallible after all. So I re-confirmed it why &lt;code&gt;hyperfine&lt;/code&gt; and it came out at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ hyperfine &lt;span class="s2"&gt;"odin build ."&lt;/span&gt;
Benchmark 1: odin build &lt;span class="nb"&gt;.&lt;/span&gt;
  Time &lt;span class="o"&gt;(&lt;/span&gt;mean ± σ&lt;span class="o"&gt;)&lt;/span&gt;:     385.1 ms ±  12.5 ms    &lt;span class="o"&gt;[&lt;/span&gt;User: 847.1 ms, System: 354.6 ms]
  Range &lt;span class="o"&gt;(&lt;/span&gt;min … max&lt;span class="o"&gt;)&lt;/span&gt;:   357.3 ms … 400.1 ms    10 runs

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;God damn that is fast, now I know the program is tiny and simple but still that is impressive and makes me wonder how it would handle a larger codebase. Please if you have any feedback or insights on this let me know I am really curious.&lt;/p&gt;

&lt;p&gt;just for sanitysake I also ran &lt;code&gt;time odin build .&lt;/code&gt; and it came out at you've guessed it &lt;code&gt;0.4&lt;/code&gt; seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Right so it's fast, but how's the experience?
&lt;/h3&gt;

&lt;p&gt;Well I have to say it was pretty smooth overall. The compiler is fast and the error messages are generally clear and helpful if not perhaps a bit... verbose for my taste&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For example&lt;/strong&gt; I've intentionally introduced a simple typo in the &lt;code&gt;map&lt;/code&gt; keyword and named is &lt;code&gt;masp&lt;/code&gt; to showcase what I mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ odin build &lt;span class="nb"&gt;.&lt;/span&gt;
/home/dave/Workspace/TMP/odinest/main.odin&lt;span class="o"&gt;(&lt;/span&gt;44:31&lt;span class="o"&gt;)&lt;/span&gt; Error: Expected an operand, got &lt;span class="o"&gt;]&lt;/span&gt;
        subscribers: masp[string][]proc&lt;span class="o"&gt;(&lt;/span&gt;msg: string&lt;span class="o"&gt;)&lt;/span&gt;,
                                  ^
/home/dave/Workspace/TMP/odinest/main.odin&lt;span class="o"&gt;(&lt;/span&gt;44:32&lt;span class="o"&gt;)&lt;/span&gt; Syntax Error: Expected &lt;span class="s1"&gt;'}'&lt;/span&gt;, got &lt;span class="s1"&gt;'proc'&lt;/span&gt;
        subscribers: masp[string][]proc&lt;span class="o"&gt;(&lt;/span&gt;msg: string&lt;span class="o"&gt;)&lt;/span&gt;,
                                   ^
/home/dave/Workspace/TMP/odinest/main.odin&lt;span class="o"&gt;(&lt;/span&gt;44:40&lt;span class="o"&gt;)&lt;/span&gt; Syntax Error: Expected &lt;span class="s1"&gt;')'&lt;/span&gt;, got &lt;span class="s1"&gt;':'&lt;/span&gt;
        subscribers: masp[string][]proc&lt;span class="o"&gt;(&lt;/span&gt;msg: string&lt;span class="o"&gt;)&lt;/span&gt;,
                                           ^
/home/dave/Workspace/TMP/odinest/main.odin&lt;span class="o"&gt;(&lt;/span&gt;44:41&lt;span class="o"&gt;)&lt;/span&gt; Syntax Error: Expected &lt;span class="s1"&gt;';'&lt;/span&gt;, got identifier
        subscribers: masp[string][]proc&lt;span class="o"&gt;(&lt;/span&gt;msg: string&lt;span class="o"&gt;)&lt;/span&gt;,
                                            ^
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose specifically this map because I wanted to showcase how Odin handles errors when you try to build, it could simply say &lt;code&gt;Error: Unknown type 'masp'&lt;/code&gt; but instead it goes on to produce 4 separate errors that all stem from the same root cause. This is obviously because the parser gets confused and can't make sense of the code anymore. So essentially you get every single error that results from the initial mistake even if they are on the same line. Now would I love to see them condensed into a single error message? Because it stems from the same line and the same root cause? Yes I would. But that's just my personal preference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Odin Shines
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Simplicity and Readability
&lt;/h3&gt;

&lt;p&gt;Odin kinda feels like a modernized somehow even more boring C but in the best way possible. It's simple, straightforward and easy to read. It does not try to have some sort of clever syntax or fancy features, it really feels like a no-nonsense no frills language that wants you to start coding and being productive as quickly as possible.&lt;/p&gt;

&lt;p&gt;In fact this brings me to my next point.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Built in Libraries Galore
&lt;/h3&gt;

&lt;p&gt;I was frankly blown away with just how much is included in the standard and vendored(more on that later) libraries. I mean it has everything you'd expect from a modern systems programming language but it also comes with a ton of complete data structures, algorithms and utilities that you would usually have to rely on third-party libraries for in C or even Zig.&lt;/p&gt;

&lt;p&gt;For more info just look at &lt;a href="https://pkg.odin-lang.org/core/" rel="noopener noreferrer"&gt;Odin's Core Library&lt;/a&gt; and I mean really look at it and read it do not just skim it. Here's an example &lt;a href="https://pkg.odin-lang.org/core/flags/" rel="noopener noreferrer"&gt;flags&lt;/a&gt; which is a complete command line argument parser, or even &lt;a href="https://pkg.odin-lang.org/core/container/rbtree/" rel="noopener noreferrer"&gt;rbtree&lt;/a&gt; which is a complete implementation of a red-black tree data structure that you can just import and use right away&lt;/p&gt;

&lt;p&gt;But what really blew me away was&lt;/p&gt;

&lt;h3&gt;
  
  
  The Built in Vendor Libraries / Packages
&lt;/h3&gt;

&lt;p&gt;Odin comes with a set of vendor libraries that basically give you useful bindings to stuff like &lt;code&gt;SDL2/3&lt;/code&gt;, &lt;code&gt;OpenGL&lt;/code&gt;, &lt;code&gt;Vulkan&lt;/code&gt;, &lt;code&gt;Raylib&lt;/code&gt;, &lt;code&gt;DirectX&lt;/code&gt; and more. This is really impressive because it means you can start building games or graphics applications right away without having to worry about setting up bindings or dealing with C interop. Now I'm not super sure if these vendor bindings are all maintained and created by the Odin team from what I could gather so far, it would certainly seem so but I could be wrong. If you know more about this please let me know. &lt;/p&gt;

&lt;p&gt;But all that aside these bindings are really well done and easy to use. For example here's how you can create a simple window with SDL2 in Odin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import sdl "vendor:sdl2"

main :: proc() {
    sdl.Init(sdl.INIT_VIDEO)
    defer sdl.Quit()

    window := sdl.CreateWindow(
        "Odin SDL2 Black Window",
        sdl.WINDOWPOS_CENTERED,
        sdl.WINDOWPOS_CENTERED,
        800,
        600,
        sdl.WINDOW_SHOWN,
    )
    defer sdl.DestroyWindow(window)

    renderer := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED)
    defer sdl.DestroyRenderer(renderer)

    event: sdl.Event
    running := true
    for running {
        for sdl.PollEvent(&amp;amp;event) {
            if event.type == sdl.EventType.QUIT {
                running = false
            }
        }

        sdl.SetRenderDrawColor(renderer, 0, 0, 0, 255) 
        sdl.RenderClear(renderer) 
        sdl.RenderPresent(renderer) 
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code creates a simple window with a black background using SDL2. It's pretty straightforward and easy to understand, especially if you're already familiar with SDL2 or SDL3.&lt;/p&gt;

&lt;h3&gt;
  
  
  C Interop
&lt;/h3&gt;

&lt;p&gt;Odin makes it trivially easy to interop with C libraries, as long as that. This is done via their &lt;code&gt;foreign import&lt;/code&gt; where you'd create an import name and link to the library file and &lt;code&gt;foreign&lt;/code&gt; blocks to link to declared individual function or types.&lt;/p&gt;

&lt;p&gt;I could explain it with examples here but Odin's own documentation does a way better job and will keep this post from getting even longer than it already is. So please check out &lt;a href="https://odin-lang.org/news/binding-to-c/" rel="noopener noreferrer"&gt;Odin's C interop&lt;/a&gt; documentation for more info.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Odin Feels Awkward
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Standard Library Gaps
&lt;/h3&gt;

&lt;p&gt;While Odin's standard library is quite comprehensive, there are still some gaps and missing features that can make certain tasks more cumbersome. For example, while it has basic file I/O capabilities, it lacks more advanced features like file watching or asynchronous I/O. Additionally, while it has a decent set of data structures, it lacks some more specialized ones like tries or bloom filters I'd also love to see a b+ tree implementation in the core library. &lt;/p&gt;

&lt;p&gt;But those are at most nitpicks and finding third-party libraries or writing your own implementations is usually straightforward.&lt;/p&gt;

&lt;p&gt;However...&lt;/p&gt;

&lt;h3&gt;
  
  
  No Package Manager
&lt;/h3&gt;

&lt;p&gt;I really like languages that come with their own package manager, it makes it so much easier to discover, install and manage third-party libraries / dependencies. Odin currently lacks a built-in package manager, which means you have to manually download and include third-party libraries in your projects. This can be a bit of a hassle, especially I'd imagine for larger projects with multiple dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smaller Nitpicks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;dir inconsistencies&lt;/strong&gt;: I love how it auto named my binary after the folder I was in but I wish it did the same whenever I ran &lt;code&gt;odin run&lt;/code&gt; and &lt;code&gt;odin build&lt;/code&gt; I had to explicitly specify &lt;code&gt;odin run .&lt;/code&gt; and &lt;code&gt;odin build .&lt;/code&gt; that felt a bit inconsistent to me because if it knows the folder we are in why not just use that as the default value when we wanna tell it to run or build in the current directory?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error messages&lt;/strong&gt;: As mentioned earlier, while Odin's error messages are generally clear, they can sometimes be overly verbose, especially when multiple errors stem from a single root cause. It would be nice to see more concise error reporting in such cases.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So to fix this I'd love to either see error messages collapsed into a single message with an array of messages from the same line, or somehow grouped together into blocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pointers are ^ and not *
&lt;/h3&gt;

&lt;p&gt;I'm on a German keyboard and the &lt;code&gt;^&lt;/code&gt; character is a bit of a pain to type, especially when compared to the &lt;code&gt;*&lt;/code&gt; character which is right next to the &lt;code&gt;Enter&lt;/code&gt; key on my keyboard. I get that Odin wants to differentiate itself from C and C++ but this small change feels unnecessary and adds a bit of friction to the coding experience.&lt;/p&gt;

&lt;p&gt;These are as the title says just minor nitpicks and in no way detract from the overall experience of using Odin, just minor annoyances that I personally had while using the language your experience may differ vastly and none of these may even bother you.&lt;/p&gt;

&lt;h2&gt;
  
  
  So is Odin just a More Boring C?
&lt;/h2&gt;

&lt;p&gt;In a way, yes kind of. I mean it's very similar in approach and philosophy but with more "guard rails" and helpful utilities to make the experience smoother and more enjoyable and the what I so far assume are first party bindings to popular libraries via the vendors package really makes it stand out in a great way, where you get a lot more consistency and predictability than you would if you were to use C with those same libraries. &lt;/p&gt;

&lt;p&gt;And I guess that's the strength of Odin, it's so boring that it just let's you be a productive programmer without getting in your way or trying to be too clever or fancy. &lt;/p&gt;

&lt;p&gt;I use boring here in an affectionate way, if you've ever read any of my other posts you'll know that I do not appreciate complexity and unnecessary cleverness in programming which is why I suppose I'm often quite critical of rust even though I do like it for certain use cases. &lt;/p&gt;

&lt;p&gt;In this case I'd say Odin is very similar to Go both are fantastic boring languages that let you get stuff done without much fuss or hassle. The only difference is that Go decided to ship with a garbage collector and Odin did not, which honestly for me personally makes Odin vastly more appealing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Syntax and Ergonomics
&lt;/h3&gt;

&lt;p&gt;Odin’s syntax is like C with a modern makeover clean, readable, and less prone to boilerplate. It did take me quite a while to get used to replacing my muscle memory for &lt;code&gt;*&lt;/code&gt; with &lt;code&gt;^&lt;/code&gt; for pointers and &lt;code&gt;func&lt;/code&gt;, &lt;code&gt;fn&lt;/code&gt;, &lt;code&gt;fun&lt;/code&gt;, &lt;code&gt;function&lt;/code&gt; with &lt;code&gt;proc&lt;/code&gt; for functions. But once I got over that initial hump, it felt pretty natural.&lt;/p&gt;

&lt;p&gt;Also &lt;code&gt;::&lt;/code&gt; for type declarations is a bit unusual and took me longer than I care to admit, as I'm fairly used to &lt;code&gt;::&lt;/code&gt; being used for scope resolution in languages like C++ and Rust. But again, once I got used to it, it felt fine.&lt;/p&gt;

&lt;p&gt;Everything else about the syntax felt pretty intuitive and straightforward. &lt;/p&gt;

&lt;h2&gt;
  
  
  Who Odin Might Be Right For
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ideal Use Cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Game Development&lt;/strong&gt;: Honestly I totally see where people are coming from when they say Odin is great for game development. The built-in vendor libraries for SDL2/3, OpenGL, Vulkan, Raylib and more make it super easy to get started with game development. Plus the language's performance and low-level capabilities are a great fit for the demands of game programming.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Systems Programming&lt;/strong&gt;: Odin's manual memory management, low-level access, and performance make it a solid choice for systems programming tasks like writing operating systems, device drivers, or embedded systems.&lt;br&gt;
I will absolutely be writing some utilities for my Linux setup in Odin in the near future.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desktop Applications&lt;/strong&gt;: Again this is where those vendor libraries shine, making it easy to build cross-platform desktop applications with graphical interfaces as long as you're fine with doing some manual drawing of components, I'd love to see a binding for something like &lt;code&gt;GTK&lt;/code&gt; or &lt;code&gt;Qt&lt;/code&gt; in the vendor packages in the future.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;General Purpose Programming&lt;/strong&gt;: This brings me back to my intro where I said that it took me a while to realize that if Odin is good for game development then realistically by all means it should basically be good for anything and everything you wish to create with it. So yea give it a shot make something cool with it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where It’s Not a Good Fit Yet
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web Development&lt;/strong&gt;: The Net library is pretty darn nice and very extensive, however it does seem like it's maybe a bit more fit for general networking tasks rather than simplifying your life as a web backend developer. I'm sure there's already a bunch of third party libraries for this, but if you're a web dev you are almost spoiled for choice at the moment by languages that support web development out of the box with all the fixings and doodads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Would I Use It Again?
&lt;/h3&gt;

&lt;p&gt;Absolutely in fact I will, I've already started planning some small utilities for my Linux setup in Odin. I really like the simplicity and readability of the language, as well as the comprehensive standard and vendor libraries. The performance is also impressive, especially the fast compile times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source Code and Feedback
&lt;/h3&gt;

&lt;p&gt;You can find the complete source code for the tiny key-value store with pub/sub capabilities on my GitHub: &lt;a href="https://github.com/dayvster/odin-kvpubsub" rel="noopener noreferrer"&gt;dayvster/odin-kvpub&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;If you create anything cool with it I'd love to see it so do hit me up on any of my socials.&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts and experiences with Odin, whether you've used it before or are considering giving it a try. Feel free to leave a comment or reach out to me on Twitter &lt;a href="https://twitter.com/dayvsterdev" rel="noopener noreferrer"&gt;@dayvster&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Appreciate the time you took to read this post, and happy coding!&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>discuss</category>
    </item>
    <item>
      <title>My Battle Tested React Hooks Are Now Open Source</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Thu, 02 Oct 2025 14:53:23 +0000</pubDate>
      <link>https://dev.to/dayvster/my-battle-tested-react-hooks-are-now-open-source-13l0</link>
      <guid>https://dev.to/dayvster/my-battle-tested-react-hooks-are-now-open-source-13l0</guid>
      <description>&lt;h2&gt;
  
  
  Don't yap just give me the links
&lt;/h2&gt;

&lt;p&gt;Ok I get it you're busy here you go:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dayvster/react-kata" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/react-kata" rel="noopener noreferrer"&gt;NPM Package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have a great day!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's This?
&lt;/h2&gt;

&lt;p&gt;I've been developing react since 2013 it's been through a lot of changes and updates over the years. We've seen patterns come and go, however when hooks were introduced in 2019, I was instantly ... hooked. No wait please don't leave, that was a good joke and you know it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhatyupz13iexdcn6kxa.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhatyupz13iexdcn6kxa.gif" width="500" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyhow now that that's out of the way, I wanted to share with you a collection of various hooks that I have written over and over and over again in my past projects. These hooks have been battle tested in production applications and multiple client, personal and professional projects. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Source?
&lt;/h2&gt;

&lt;p&gt;Well I have this post on my blog about why I believe everyone should open source their work, &lt;a href="https://dayvster.com/blog/open-source-your-projects/" rel="noopener noreferrer"&gt;Open Source Your Projects&lt;/a&gt; I've been recently redesigning my website and fixing up my old blog posts and I stumbled upon this one decided to re-read it fully and discovered I am a hypocrite by value of pure laziness.&lt;/p&gt;

&lt;p&gt;Yes I did not open source these hooks sooner, because I was simply lazy. I had them in various different projects, repos, side projects etc. etc. But I never took the time to actually put them together in a single package and publish it. I just kept either navigating to these projects and constantly copy pasting them around OR worse yet I would rewrite them from scratch every time I needed them, which is just plain dumb.&lt;/p&gt;

&lt;p&gt;So I decided to stop being a hypocrite and finally open source a bunch of my stuff part of that was the task of collecting all of these react hooks and putting them together in a single package.&lt;/p&gt;

&lt;p&gt;So here it is&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing React-Kata
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dayvster/react-kata" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/react-kata" rel="noopener noreferrer"&gt;NPM Package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React-Kata is a collection of battle-tested React hooks that I've used in various projects over the years. These hooks are designed to make your life easier and help you write cleaner, more efficient code. &lt;/p&gt;

&lt;h3&gt;
  
  
  Why React-Kata?
&lt;/h3&gt;

&lt;p&gt;Kata is a term used in martial arts to describe a set of movements and techniques that are practiced repeatedly to improve skill and mastery. Similarly, React-Kata is a collection of hooks that I've rewritten and reused and refined over and over again. So I thought it would be appropriate to name it React-Kata. Also I did martial arts as a younger person and wanted to show off a bit, lay off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useLocalStorage&lt;/code&gt;: A hook that makes it easy to work with local storage in your React applications.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useDebounce&lt;/code&gt;: A hook that debounces a value, making it easier to work with user input and avoid unnecessary re-renders.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useThrottle&lt;/code&gt;: A hook that throttles a value, making it easier to work with user input and avoid unnecessary re-renders.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useTimeout&lt;/code&gt;: A hook that makes it easy to work with timeouts in your React applications.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useInterval&lt;/code&gt;: A hook that makes it easy to work with intervals in your React applications&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useSessionStorage&lt;/code&gt;: A hook that makes it easy to work with session storage in your React applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and many many more in fact to see a lot of all of them check out the &lt;a href="https://github.com/dayvster/react-kata?tab=readme-ov-file#-hooks-overview" rel="noopener noreferrer"&gt;GitHub Repo Hooks overview&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  useShimmer
&lt;/h3&gt;

&lt;p&gt;This hook generates a shimmer effect SVG that can be used as a placeholder while content is loading. It takes width and height as parameters and returns an SVG string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useShimmer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-kata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ShimmerDemo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shimmer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useShimmer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`data:image/svg+xml;utf8,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shimmer&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Loading..."&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  useWhyDidYouUpdate
&lt;/h3&gt;

&lt;p&gt;This hook helps you identify why a component re-rendered by logging the changed props to the console or handling them with a custom callback.&lt;/p&gt;

&lt;p&gt;I can not overstate how often this one has saved my ass in the past.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useWhyDidYouUpdate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-kata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Demo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Logs changed props to console by default&lt;/span&gt;
  &lt;span class="nf"&gt;useWhyDidYouUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Or provide a callback to handle changes&lt;/span&gt;
  &lt;span class="nf"&gt;useWhyDidYouUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;changedProps&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Custom handling, e.g. send to analytics&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Changed: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changedProps&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  useTheme
&lt;/h3&gt;

&lt;p&gt;Let's you simply manage themes in your application. Supports auto, light, dark, and custom themes.&lt;br&gt;
For custom themes you simply input the name of the theme and it will be applied as the data-theme attribute on the html element. &lt;/p&gt;

&lt;p&gt;It will also be stored in local storage and retrieved from local storage on page load.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-kata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Demo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Supports auto, light, dark, and custom themes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toggleTheme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTheme&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solarized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Current theme: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggleTheme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Toggle Theme&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solarized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Solarized&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Auto&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  useReload
&lt;/h3&gt;

&lt;p&gt;Allows you to pass a custom condition that when met will trigger a page reload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useReload&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-kata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Demo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Only reload if user confirms&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReload&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reload?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;reload&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Reload Page&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope you find these hooks as useful as I have. If you have any suggestions for new hooks or improvements to existing ones, please feel free to open an issue or submit a pull request on GitHub, I'll always appreciate feedback, criticism, suggestions and especially Pull Requests.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why Zig Feels More Practical Than Rust for Real-World CLI Tools</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Tue, 23 Sep 2025 09:59:39 +0000</pubDate>
      <link>https://dev.to/dayvster/why-zig-feels-more-practical-than-rust-for-real-world-cli-tools-2895</link>
      <guid>https://dev.to/dayvster/why-zig-feels-more-practical-than-rust-for-real-world-cli-tools-2895</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;So when it comes to memory management there are two terms you really need to know, the stack and the heap.&lt;/p&gt;

&lt;p&gt;The stack is a region of memory that stores temporary data that is only needed for a short period of time. It operates in a last-in, first-out (LIFO) manner, meaning that the most recently added data is the first to be removed, as the name suggests. Basically imagine a stack of plates, if you wanna remove one plate you remove the top one, remove the middle plate and disaster awaits in this analogy. The stack is typically used for storing function parameters, local variables, and return addresses. It is fast and efficient because it has a fixed size and does not require dynamic memory allocation.&lt;/p&gt;

&lt;p&gt;The size of the stack is usually limited, and if a program tries to use more stack space than is available, it can result in a stack overflow error. This can happen if a function calls itself recursively too many times or if a program allocates too much memory on the stack.&lt;/p&gt;

&lt;p&gt;Whereas the heap as the name suggests is a region of memory that is used for dynamic memory allocation. Unlike the stack, the heap does not have a fixed size and can grow or shrink as needed. The heap is typically used for storing data that needs to persist beyond the lifetime of a single function call, such as objects or data structures that are created at runtime. Imagine the heap as a pile of clothes in a disorganized household, you can add or remove clothes as needed and as long as the pile isn't too big you can find what you need with relative speed and ease. But it will quickly become a nightmare if you let it grow out of control. The heap is managed by the operating system and requires dynamic memory allocation, which can be slower and less efficient than stack allocation.&lt;/p&gt;

&lt;p&gt;The heap can also become fragmented over time, since we do not always store data in a contiguous block of memory. This can lead to performance issues and make it more difficult to allocate large blocks of memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rust's Borrow Checker
&lt;/h3&gt;

&lt;p&gt;Rust's borrow checker is a a pretty powerful tool that helps ensure memory safety during compile time. It enforces a set of rules that govern how references to data can be used, preventing common programming memory safety errors such as null pointer dereferencing, dangling pointers and so on. However you may have notice the word &lt;strong&gt;compile time&lt;/strong&gt; in the previous sentence. Now if you got any experience at systems programming you will know that compile time and runtime are two very different things. Basically compile time is when your code is being translated into machine code that the computer can understand, while runtime is when the program is actually running and executing its instructions. The borrow checker operates during compile time, which means that it can only catch memory safety issues that can be determined statically, before the program is actually run. &lt;/p&gt;

&lt;p&gt;This means that basically the borrow checker can only catch issues at comptime but it will not fix the underlying issue that is developers misunderstanding memory lifetimes or overcomplicated ownership. The compiler can only enforce the rules you’re trying to follow; it can’t teach you good patterns, and it won’t save you from bad design choices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Story TIme
&lt;/h3&gt;

&lt;p&gt;Last weekend I've made a simple CLI tool for myself to help me manage my notes it parses &lt;code&gt;~/.notes&lt;/code&gt; into a list of notes, then builds a tag index mapping strings to references into that list. Straightforward, right? Not in Rust. The borrow checker blocks you the moment you try to add a new note while also holding references to the existing ones. Mutability and borrowing collide, lifetimes show up, and suddenly you’re restructuring your code around the compiler instead of the actual problem.&lt;/p&gt;

&lt;p&gt;In Zig, we would just allocate the list with an allocator, store pointers into it for the tag index, and mutate freely when we need to add or remove notes. No lifetimes, no extra wrappers, no compiler gymnastics, that’s a lot more straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  But Dave isn't that the exact point of Rust's borrow checker?
&lt;/h3&gt;

&lt;p&gt;Yes it is, however by using Zig I managed to get most of the benefits of Rust's memory safety without the complexity or ceremony of the borrow checker. All it took was some basic understanding of memory management and a bit of discipline. I was able to produce two CLI's that are both memory safe and efficient however the Zig one was way more straightforward and easier to reason about and took less time to write.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Safety, Really for CLI Tools?
&lt;/h2&gt;

&lt;p&gt;This is where a lot of developers trip up, Rust markets itself as a language that produces safe software, great marketing hook, but one tiny problem, memory safety is one puzzle piece of overall software safety. I'm not sure if the Rust foundation does this on purpose sort of a blanket statement to make it seem like memory safety is the end all be all of software safety, or if they just don't want to constantly prefix safety with memory safety(even though they should).&lt;/p&gt;

&lt;p&gt;But back to the main point, memory safety is just one aspect of software safety. You can argue if it's a big or small piece of the puzzle, I'd say it depends on the software and use-case but it's definitely not the only piece. &lt;/p&gt;

&lt;p&gt;So What exactly is safety in terms of CLI tools?&lt;/p&gt;

&lt;p&gt;Memory safety alone does not make a program safe. Your CLI tool can still crash, produce wrong results, corrupt files, leak sensitive data, be vulnerable to various types of attacks or just behave in a way that is not expected. Let's go back to my &lt;code&gt;Notes CLI&lt;/code&gt; it's rust version may never segfault but it could silently overwrite my index or tags or corrupt my files if I make a mistake in my logic, or perhaps it could store my file in a temporary location that is world readable, exposing my notes to anyone on the system. Is that safe? No.&lt;/p&gt;

&lt;p&gt;Would using Zig solve any of those issues automatically, also no.&lt;br&gt;
Is my example a bit contrived, yes, but it illustrates the point that memory safety is not the only thing that matters when it comes to software safety.&lt;/p&gt;

&lt;p&gt;In fact you should also consider other aspects of safety such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Predictable Behavior&lt;/strong&gt;: The program should do what the user expects, even when input is malformed or unexpected. A CLI that panics on a missing file or fails silently on a corrupted note is not safe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoiding Crashes or Silent Corruption&lt;/strong&gt;: The program should handle errors gracefully, providing meaningful feedback to the user instead of crashing or corrupting data. A CLI that crashes on a malformed note or silently overwrites existing notes is not safe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manageable Performance&lt;/strong&gt;: The program should perform well under expected workloads, avoiding excessive resource consumption or slowdowns. A CLI that becomes unresponsive when managing a large number of notes is not safe. This is where it really helps to understand memory allocations and performance characteristics of your language of choice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sensitive Data Handling&lt;/strong&gt;: The program should protect sensitive data from unauthorized access or exposure. A CLI that stores notes in a world-readable temporary file is not safe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Robustness Against Attacks&lt;/strong&gt;: The program should be resilient against common attack vectors, such as injection attacks or buffer overflows. A CLI that can be exploited to execute arbitrary code or corrupt data is not safe. And this is precisely where Rust's memory safety shines, it can help prevent certain types of vulnerabilities that arise from memory mismanagement. However, it's not a silver bullet that guarantees overall safety.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Borrow Checker: Strengths and Limitations
&lt;/h2&gt;

&lt;p&gt;The borrow checker is impressive. It prevents dangling references, double frees, and mutable aliasing at compile time, things that would otherwise cause segfaults or undefined behavior. It’s why Rust can claim “memory safe without a garbage collector.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Strengths:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero data races / mutable aliasing issues&lt;/strong&gt;: The compiler guarantees that only one mutable reference exists at a time, and that immutable references cannot be combined with mutable ones.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Strong compile-time guarantees&lt;/strong&gt;: Many memory-related bugs are caught before you even run the program.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Early bug detection&lt;/strong&gt;: You find mistakes before shipping code, which is a huge win in long-lived services or concurrent systems.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limitations / Pain Points:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cognitive overhead&lt;/strong&gt;: You’re constantly thinking about lifetimes, ownership, and borrow scopes, even for simple tasks. A small CLI like my notes tool suddenly feels like juggling hot potatoes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boilerplate and contortions&lt;/strong&gt;: You end up introducing clones, wrappers (Rc, RefCell), or redesigning data structures just to satisfy the compiler. Your code starts serving the compiler, not the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compile-time only&lt;/strong&gt;: The borrow checker cannot fix logic bugs, prevent silent corruption, or make your CLI behave predictably. It only ensures memory rules are followed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Edge cases get messy&lt;/strong&gt;: Shared caches, global state, or mutable indexes often trigger lifetime errors that are annoying to work around.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, the Rust borrow checker can feel more like a mental tax than a helpful tool, especially for short-lived CLI projects. You’re trading developer ergonomics for a compile-time guarantee that, in many CLI scenarios, may be overkill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zig's Approach to Safety and Simplicity
&lt;/h2&gt;

&lt;p&gt;Zig takes a different approach to safety and simplicity. It provides manual memory management with optional safety checks, allowing developers to choose the level of control they need. This can lead to more straightforward code for certain use cases, like CLI tools. However where it really shines is how it does manual memory management, I've briefly touched upon this in my other blog post &lt;a href="https://dayvster.com/blog/zig-allocators-explained/" rel="noopener noreferrer"&gt;Zig Allocators Explained&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But basically long story short Zig gives you allocators, a set of tools that helps you manually manage your memory in a more structured and predictable way. You can choose to use a general purpose allocator like the &lt;code&gt;std.heap.page_allocator&lt;/code&gt; or you can create your own custom allocator that fits your specific needs. This allows you to have more control over how memory is allocated and deallocated, which can lead to more efficient and predictable memory usage. This combined with Zig's &lt;code&gt;defer&lt;/code&gt; statement which allows you to schedule cleanup code to run when a scope is exited, makes it easy to manage resources gives you most of the power of Rust's borrow checker at your disposal without the complexity and ritual. However it asks one thing in return of you, discipline, your software will be only as safe as you make it. We can make the same claim about Rust, you can throw &lt;code&gt;copy&lt;/code&gt; and &lt;code&gt;clone&lt;/code&gt; and &lt;code&gt;unsafe&lt;/code&gt; around your code and throw away all the benefits of the borrow checker in a heartbeat. &lt;/p&gt;

&lt;p&gt;The two languages are polar opposites in this regard, Zig places the burden on the developer and makes it easy for them to produce memory safe software, whereas Rust places the burden on the compiler and makes it hard for developers to produce memory unsafe software. &lt;/p&gt;

&lt;p&gt;Back to the main point, zig's approach to memory management is in my subjective opinion more practical for most of my use cases, especially for CLI tools. It allows me to write straightforward code that is easy to reason about and maintain, without the overhead of the borrow checker. I can allocate a list of notes, store pointers to them in a tag index, and mutate the list freely when I need to add or remove notes. No lifetimes, no extra wrappers, no compiler gymnastics, that’s a lot more straightforward.&lt;/p&gt;

&lt;p&gt;Oh I almost forgot, Zig also has the &lt;code&gt;comptime&lt;/code&gt; feature which allows you to execute code at compile time. This can be useful for generating code, performing static analysis, or optimizing performance and even for testing which is a really nice bonus and can be a small helper when it comes to memory safety. &lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Ergonomics Matter and Developers are not Idiots
&lt;/h2&gt;

&lt;p&gt;When developing software we want to be productive and efficient, most of all we want to be correct and produce good software, however we also want to enjoy the process of creation and not feel like we are fighting the tools we use. Developer ergonomics is a term that refers to how easy and comfortable it is to use a programming language or framework. It encompasses things like syntax, tooling, documentation, and community support. A language with good developer ergonomics can make it easier to write correct code, while a language with poor developer ergonomics can make it harder to do so. I'd say as it currently stands Rust has poor developer ergonomics but produces memory safe software, whereas Zig has good developer ergonomics and allows me to produce memory safe software with a bit of discipline. &lt;/p&gt;

&lt;p&gt;I personally usually prefer languages where I do not have to succumb to too much ceremony and ritual to get things done, I want to be able to express my ideas in code without having to constantly think about the underlying mechanics of the language and yet I want to be responsible and produce good software. So with C and C++ this was a tiny bit harder as you basically had to learn some useful and practical memory management patterns and techniques, Zig comes with them baked in. &lt;/p&gt;

&lt;p&gt;I feel like Zig really respects it's developers and treats them like adults, it gives you the tools and expects you to use them wisely. Rust on the other hand feels like it treats developers like children that need to be constantly supervised and guided, which can be frustrating and demotivating.&lt;/p&gt;

&lt;p&gt;Developers are not idiots, sure even the smartest amongst us still produce memory safety issues or bugs in their software and it's silly to assume that with enough training and practice we can become perfect developers, but we can become better developers. We can learn from our mistakes and improve our skills, we can learn to write better code and produce better software.&lt;/p&gt;

&lt;p&gt;It's not good to abstract that away to the compiler and assume that it will magically make us better developers, I don't personally think it will. In fact not to sound too cliche but I think that the journey to becoming a better developer is a series of mistakes and fixes, we learn from our mistakes and improve our skills. What does it say about a language that tries to abstract away the mistakes we make, does it really help us become better developers ?&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Rust is amazing, if you’re building something massive, multithreaded, or long-lived, where compile-time guarantees actually save your life. The borrow checker, lifetimes, and ownership rules are a boon in large systems.&lt;/p&gt;

&lt;p&gt;But for small, practical CLI tools? Rust can feel like overkill. That’s where Zig shines. Lightweight, fast, and straightforward, you get memory safety without constantly bending over backward for the compiler. You can allocate a list, track pointers, and mutate freely without extra wrappers, lifetimes, or contortions. Iterating feels natural, the code is easier to reason about, and you get stuff done faster.&lt;/p&gt;

&lt;p&gt;Memory safety is important, but it’s just one piece of the puzzle. Predictable behavior, maintainable code, and robustness are just as critical, and that’s exactly why Zig often feels more practical for real-world CLI tools.&lt;/p&gt;

&lt;p&gt;At the end of the day, it’s not about which language is “better.” It’s about what fits your workflow and the kinds of projects you build. For me, Zig hits the sweet spot: memory safe, low ceremony, and developer-friendly, perfect for small tools that actually get things done.&lt;/p&gt;

</description>
      <category>zig</category>
      <category>programming</category>
      <category>discuss</category>
      <category>rust</category>
    </item>
    <item>
      <title>Are We Chasing Language Hype Over Solving Real Problems?</title>
      <dc:creator>Dayvster 🌊</dc:creator>
      <pubDate>Sat, 20 Sep 2025 09:33:46 +0000</pubDate>
      <link>https://dev.to/dayvster/are-we-chasing-language-hype-over-solving-real-problems-36b</link>
      <guid>https://dev.to/dayvster/are-we-chasing-language-hype-over-solving-real-problems-36b</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;As you may have heard or seen, there is a bit of controversy around Ubuntu adopting a rewritten version of GNU Core Utils in Rust. This has sparked a lot of debate in the tech community. This decision by Canonical got me thinking about this whole trend or push of rewriting existing software in Rust which seems to be happening a lot lately.&lt;/p&gt;

&lt;p&gt;To put it bluntly I was confused by the need to replace GNU Core Utils with a new implementation as GNU Core Utils has been around since arguably the 90s and more realistically 2000s and it has been battle tested and proven to be reliable, efficient, effective and most importantly secure as it had basically never had any major security vulnerabilities in its entire existence. So why then would we deem it necessary to replace it with a new implementation in Rust? Why would anyone go through the trouble of rewriting something that already works perfectly fine and has been doing so for decades? When the end result at best is going to be a tool that does the same thing as the original and in the very best case scenario offer the same performance?&lt;/p&gt;

&lt;p&gt;What bothers me even more is the bigger pattern this points to. Are we as developers more interested in chasing new languages and frameworks than actually solving real problems? I strongly subscribe to the idea that software development 60% problem solving and 40% creative exploration and innovation. But lately it feels like the balance is shifting more and more towards the latter. We seem to be more interested in trying out the latest and greatest languages and frameworks than actually solving real problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hype of New Languages and Shiny Object Syndrome
&lt;/h2&gt;

&lt;p&gt;We've all been there in the past haven't we? Getting excited about a new programming language or framework that promises to solve all our problems and make our lives easier. It's easy to get caught up in the hype and want to try out the latest and greatest technology, but it's important to remember that just because something is new doesn't mean it's better. We need to be careful not to fall into the trap of "shiny object syndrome" where we chase after the latest trends without considering whether they actually solve our problems or improve our workflows. It's important to evaluate new technologies based on their merits and how they fit into our existing systems and workflows, rather than simply jumping on the bandwagon because everyone else is doing it.&lt;/p&gt;

&lt;p&gt;Now for the important question: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I think the developers of coreutils-rs are doing this just because Rust is the new hotness?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Short and simple: No, no I do not. I believe they have good intentions and are likely trying to improve upon the existing implementation in some way. However, I do not agree with them that there is a need for a rewritten version of GNU Core Utils in Rust. I also do not agree that GNU Core Utils is inherently insecure or unsafe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do we get Exited About New Languages?
&lt;/h3&gt;

&lt;p&gt;It's also important to briefly touch upon the psychological aspect of why we get excited about new languages. New languages often come with new features, syntax, and paradigms that can be appealing to developers. They may also promise to solve problems that existing languages struggle with, such as performance, concurrency, or memory safety. Additionally, new languages can offer a fresh perspective on programming and can inspire creativity and innovation. Not to mention the community aspect, new usually means a changing of the guard, new people, new ideas, new ways of thinking about problems. All of these factors can contribute to the excitement and enthusiasm that developers feel when a new language is introduced.&lt;/p&gt;

&lt;p&gt;This enthusiasm can sometimes lead to an almost zealous approach of wanting everything and anything to be written only in the new language by this new and fresh community of developers. This can lead to a situation where existing and well-established software is rewritten in the new language, even if there is no real need for it. This can be seen as a form of "language evangelism" where developers are trying to promote their favorite language by rewriting existing software in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Case of GNU Core Utils
&lt;/h2&gt;

&lt;p&gt;As I've briefly touched upon earlier, GNU Core Utils is a collection of basic file, shell and text manipulation utilities that are fundamental to the operation of Unix-like operating systems. These utilities include commands like &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;cp&lt;/code&gt;, &lt;code&gt;mv&lt;/code&gt;, &lt;code&gt;rm&lt;/code&gt;, &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;echo&lt;/code&gt;, and many others. They are essential for performing everyday tasks in the command line interface (CLI) and are used by system administrators, developers, and users alike.&lt;/p&gt;

&lt;p&gt;Some of these can run hundreds of times per second, so performance is absolutely crucial. Even a small reduction in performance to a utility that is run by some OS critical daemon can have a significant impact on the overall performance of the system. GNU core utils has been optimized for this for about 30+ years at this point and is it really worth just tossing all of those lessons and optimizations out the window just to rewrite it in a new language?&lt;/p&gt;

&lt;p&gt;I've also briefly touched upon that at best in the absolute &lt;strong&gt;best case scenario&lt;/strong&gt; a rewritten version of GNU Core Utils in Rust would be able to match the performance of the original implementation. As we know GNU Core Utils are mostly written in C and some C++ mixed in sparingly. So far benchmarks have shown time and time again that at best with a lot of optimizations and tweaks Rust can only ever match the performance of C and in most cases it is actually slower. So the best case outcome of this rewrite is that we get a tool that does the same thing as the original and at best offers the same performance. So what is the actual benefit of this rewrite?&lt;/p&gt;

&lt;p&gt;Where is the value, what is the actual problem that is being solved here? &lt;/p&gt;

&lt;h2&gt;
  
  
  When Hype Overshadows Real Problems
&lt;/h2&gt;

&lt;p&gt;This is the crux of the issue, it's very very easy to get swept up in the excitement of a new language and want to use it for everything and anything under the sun. As developers we love novelty and communities with enthusiasm and fresh ideas. It's stimulating it's fun it feels like progress it feels like we are finally building something again instead of just rehashing and maintaining. We all know from personal experience that creating a new project is more fun and enjoyable than maintaining and existing one and this is a natural human tendency.&lt;/p&gt;

&lt;p&gt;Now do I think this is one of the reasons the developers of coreutils-rs are doing this?&lt;/p&gt;

&lt;p&gt;Yes, I do. &lt;/p&gt;

&lt;p&gt;But in the end they are solving a problem that does not exist. &lt;/p&gt;

&lt;h3&gt;
  
  
  It's not just about Core Utils
&lt;/h3&gt;

&lt;p&gt;Now with how often I've mentioned this specific example of GNU Core Utils you might think I want to single them out or have some sort of grudge or specific issue with this particular project. No, not really... I think this project is indicative of the larger issue we face in the tech community. It's very easy to get caught up in the excitement of new languages and frameworks and want to use them for everything and anything. This can lead to a situation where we are rewriting existing software in new languages without considering whether it actually solves any real problems or improves our workflows. &lt;/p&gt;

&lt;h2&gt;
  
  
  Problem Solving Should Be Our North Star
&lt;/h2&gt;

&lt;p&gt;At the end of the day, software development is about solving real problems, not about chasing novelty, shiny new languages, or personal curiosity. Every line of code we write, every framework we adopt, every library we integrate should be justified by the problems it helps solve, not by the hype around it.&lt;/p&gt;

&lt;p&gt;Yet increasingly, it feels like the industry is losing sight of this. We celebrate engineers for building in the “new hot language,” for rewriting tools, or for adopting the latest framework, even when the original solution worked perfectly fine. We reward novelty over necessity, excitement over impact.&lt;/p&gt;

&lt;p&gt;This is not a phenomenon isolated to just core utils or systems programming, it happens in web development, mobile development, data science, and pretty much every other area of software development. We too often abandon tried and true solutions and ideas of new and exciting shiny ones without considering whether they actually solve any real problems or improve our workflows.&lt;/p&gt;

&lt;p&gt;For example web development went full circle with React Server Components where we went from separation of concerns straight back to PHP style mixing of HTML and logic, server rendering and delivering interactive components to the client. Or the whole GraphQL craze where traditional REST APIs were abandoned en masse for a new and exciting way of doing things that promised to solve the dreaded problem of "over-fetching" and "under-fetching" of data. Yet in reality, it introduced a whole new set of problems and complexities that were not present in traditional REST APIs.&lt;/p&gt;

&lt;p&gt;Or perhaps the whole microservices and microfrontend craze where a lot of projects were abandoned or rewritten to be split into smaller and smaller pieces,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Was it all bad? Should we always just stick to what works and only ever maintain legacy projects and systems? Heck no!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is definitely a place for innovation and new ideas in software development. New languages, frameworks, and tools can bring fresh perspectives and approaches to problem-solving. However, it's important to evaluate these new technologies based on their merits and how they fit into our existing systems and workflows, rather than simply jumping on the bandwagon because everyone else is doing it.&lt;/p&gt;

&lt;p&gt;We need to be more critical and thoughtful about the technologies we adopt and the projects we undertake. We need to ask ourselves whether a new language or framework actually solves a real problem or improves our workflows, or if we're just chasing novelty for its own sake.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Final Thought
&lt;/h2&gt;

&lt;p&gt;At the end of the day, it’s not about Rust, React, GraphQL, or the latest microservices fad. It’s about solving real problems. Every line of code, every framework, every rewrite should have a purpose beyond curiosity or hype.&lt;/p&gt;

&lt;p&gt;We live in a culture that rewards novelty, celebrates “cool” tech, and often mistakes excitement for progress. But progress isn’t measured in how many new languages you touch, or how many shiny rewrites you ship, it’s measured in impact, in the problems you actually solve for users, teams, and systems.&lt;/p&gt;

&lt;p&gt;So next time you feel the pull of the newest hot language, framework, or tool, pause. Ask yourself: “Am I solving a real problem here, or just chasing excitement?”&lt;/p&gt;

&lt;p&gt;Because at the end of the day, engineering isn’t about what’s trendy, it’s about what works, what matters, and what actually makes a difference. And that, my friends, is the craft we should all be sharpening.&lt;/p&gt;

</description>
      <category>software</category>
      <category>programming</category>
      <category>discuss</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
