<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>Hacker News Personal Blogs 2023 | Top 100 Blogs</title><link>https://hn-blogs.kronis.dev/feed-top100.xml</link><description>A collection of blog posts from users of Hacker News, based on RSS feeds.</description><language>en-US</language><lastBuildDate>Sat, 07 Mar 2026 15:11:22 GMT</lastBuildDate><generator>rfeed v1.1.1</generator><docs>https://github.com/svpino/rfeed/blob/master/README.md</docs><item><title>2023 in Retrospective</title><link>https://blog.bayindirh.io/blog/2023-in-retrospective/</link><description>&lt;p&gt;The year is ending in a couple of days, and I wanted to leave a public note about how it went for me.&lt;/p&gt;
&lt;p&gt;2023 was an interesting year. I spent a lot of time getting used to my new life, and to be honest, it went well. I’m happier than ever, and learning to handle my life in this new chapter.&lt;/p&gt;
&lt;p&gt;The biggest change was how I allocate my time during the day. This nudges me to learn to downscale and manage my time better or be more efficient in how I accomplish things in general. This part of my transition is not over yet, I’m actively downscaling in both physical and digital ways. For example, I don’t have a personal desktop computer anymore, but living with a single laptop. I have almost completed my move to cloud storage, converging and simplifying it in the process. I’m trying to reduce the number of my physical belongings, however that part is going slower than I dreamt of. 2023 brought minimalism with all its peculiarities, but it’s not a failure by any means.&lt;/p&gt;
&lt;p&gt;Another small yet important change was finishing transitioning to fountain pens for note-taking. I have disposable pens, but new ones are not being added much. In the end, I’ll need to have a couple because I use rulers sometimes, and there are special-purpose ones, like pigment markers, that I’ll need to stock, but all the grunt work is handled by fountain pens, which bring joy and do not create waste except the glass bottles you empty, which can be recycled. I can say that I’m more mindful of the amount of plastic I consume, and I try to actively recycle it (whether it’s done or not is another issue). I’ll be talking about note-taking and fountain pens in the soonish future.&lt;/p&gt;
&lt;p&gt;I continued learning Go. I wanted to be able to have something between Python and C++ and Go filled that gap perfectly. Simpler than C++, compiled, garbage collected, and fast enough. It’s open source and has a GCC implementation, which is crucial for me. All in all, I love the language and its ergonomics. It proved itself to be a great companion for my automation and downscaling efforts. I may write what I think about Go in a future blog post.&lt;/p&gt;
&lt;p&gt;I continued implementing and almost finished one of the tools I wanted to have with my newly acquired Go knowledge: Nudge, a small command line tool to interface with Pushover for sending push notifications to devices from the Linux command line, which is a crucial requirement for “lights-out operations” which I want to move towards for the mundane things I routinely do in front of a computer because I want to have more time for myself and people I care.&lt;/p&gt;
&lt;p&gt;Lastly, with the AI revolution, I became way more mindful of how these systems are trained, and its effects on our privacy and free software. As a result, changed my trajectory towards services and systems which do not devour your things for their profit without your consent. During that transition, I also left Google search for Kagi, which is something you pay for to use. However, this payment makes sure that you don’t become the product during the process, at least for now. I’m pretty happy about this decision, and I recommend you to give Kagi a try.&lt;/p&gt;
&lt;p&gt;2024 will bring the next step in many of these endeavors and will add new ones on top of it. I’m hopeful that the new year will bring better things upon us since 2023 was enough both for me and our planet as a whole.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Fri, 29 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/2023-in-retrospective/</guid></item><item><title>A Hobby Coding Biography</title><link>https://www.marginalia.nu/log/97_projects/</link><description>This is a bit of a retrospective of every project I&amp;rsquo;ve worked on, as far as I remember them. I&amp;rsquo;ve tried to unearth any artifacts that remain.
Far from everything is flattering and resounding success, but then again, maybe that&amp;rsquo;s good. There are definitely patterns in the things that didn&amp;rsquo;t pan out.
Earliest Traces I was definitely programming stuff, but I don&amp;rsquo;t think it ever amounted to anything tangible. It was more like playing house, I built GUIs that looked like real applications but didn&amp;rsquo;t really do anything but look cool to me.</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 28 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/97_projects/</guid></item><item><title>What I learned during Advent of Code 2023</title><link>https://anisse.astier.eu/aoc-2023-lessons.html</link><description>&lt;p&gt;&lt;a href="https://adventofcode.com"&gt;Advent of Code&lt;/a&gt; is an Advent calendar of small programming puzzles. I participated in &lt;a href="https://adventofcode.com/2023/"&gt;this year's edition&lt;/a&gt;, finishing it for the second time in row. The puzzles of all editions are always accessible.&lt;/p&gt;
&lt;p&gt;The principle is to read the problem, get a puzzle input (more or less tailored to your …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Wed, 27 Dec 2023 21:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/aoc-2023-lessons.html</guid></item><item><title>Make your own way</title><link>http://notes.eatonphil.com/2023-12-26-make-your-own-way.html</link><description>&lt;p&gt;Over the years, I have repeatedly felt like I missed the timing for a
meetup or an IRC group or social media in general. I'd go to a meetup
every so often but I'd never make a meaningful connection with people,
whereas everyone else knew each other. I'd join an IRC group and have
difficulty catching up with what seemed to be the flow of
conversation.&lt;/p&gt;
&lt;p&gt;I hadn't thought much about this until the pandemic when I started a
&lt;a href="https://eatonphil.com/discord.html"&gt;Discord group for software
internals&lt;/a&gt; and a virtual tech talk
series called Hacker Nights. Since 2021 the Discord reached around
1,500 members and ~20 fairly active members. And the Meetup peaked at
about 300 members with about 10-20 showing up each Meetup.&lt;/p&gt;
&lt;p&gt;After the pandemic receded I started an &lt;a href="https://eatonphil.com/2023-ddia.html"&gt;NYC-based book
club&lt;/a&gt; over 2 months with about
5-8 active attendees. I ran a &lt;a href="https://eatonphil.com/2023-10-wehack-postgres.html"&gt;virtual hack week on
Discord&lt;/a&gt; where I
got ~100 devs into a temporary Discord server and we talked about
Postgres internals and shared resources. Ultimately around 5 of us wrote blog
posts and built new projects to explore Postgres.&lt;/p&gt;
&lt;p&gt;I started a &lt;a href="https://eatonphil.com/2023-database-internals.html"&gt;virtual, async email book
club&lt;/a&gt; (that is
still ongoing) with 300 devs from November 2023 to Feb 2024. There
have been around 20 active members of the club. And each week the
discussion is kicked off by one of the members, not myself.&lt;/p&gt;
&lt;p&gt;And I felt like there wasn't enough community opportunity for folks in
systems programming in NYC so I started an &lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;Manhattan-based Systems
Coffee
Club&lt;/a&gt;. Around 15
people showed up to the first meeting and seemed even more excited
about it than I was. (And I was excited!)  We'll see where it goes
from here.&lt;/p&gt;
&lt;p&gt;Organizing people to do this stuff doesn't come easy to me. I enjoy
doing it to a degree, but every night before an event I have trouble
sleeping. Worried about embarrassing myself. When the event happens
though, and people are happy to be there to chat with everyone else,
as they invariably have been, it makes it worthwhile.&lt;/p&gt;
&lt;h3 id="everyone-want-community"&gt;Everyone want community&lt;/h3&gt;&lt;p&gt;Something I realized along the way is that people (maybe devs
especially, I don't know) are looking for community. And when I have
noticed there seems to be a missing flashpoint (a topic, a career
focus, a book, etc.) for community, it's been pretty easy to get
people together around it.&lt;/p&gt;
&lt;h3 id="the-lifecycle-of-groups"&gt;The lifecycle of groups&lt;/h3&gt;&lt;p&gt;Groups, meetups, naturally live and die. Organizers get burnt out. I
don't see this as a problem. It's just the way it is.&lt;/p&gt;
&lt;p&gt;At some point I'll get burnt out too. Or I'll get pickier. For
example, I've been avoiding starting a systems programming meetup in
NYC because I know it will be a big effort. So I've done lower effort
groups like book clubs and coffee clubs.&lt;/p&gt;
&lt;p&gt;Don't worry about signing yourself up for indefinite work. Just do
whatever you'd like to and don't feel bad if you have to stop. Someone
else will eventually start the next great group, even if it comes in a
different medium or flavor.&lt;/p&gt;
&lt;h3 id="community-is-contagious"&gt;Community is contagious&lt;/h3&gt;&lt;p&gt;There are great communities out there that have inspired me.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Aleksey Charapko's and Murat Demirbas's virtual
&lt;a href="https://charap.co/reading-group/"&gt;Distributed Systems Reading Group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Alex Petrov's &lt;a href="https://twitter.com/ifesdjeen"&gt;database paper reading group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Andy Pavlo's &lt;a href="https://db.cs.cmu.edu/seminar2023/"&gt;database interview series&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Paul Butler's &lt;a href="https://browsertech.com/nyc"&gt;BrowserTech meetup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Eric Zhang's &lt;a href="https://twitter.com/ekzhang1/status/1700993939841716254"&gt;New York Systems Reading Group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And this year I've been hearing about more.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TU Munich students &lt;a href="https://www.tumuchdata.club/"&gt;started a Student Database Group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A group of developers &lt;a href="https://twitter.com/Keleesssss/status/1720466270032691460"&gt;starting a Türkiye-language CS reading group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are yet a few more systems programming groups I've heard rumors
about being started on the US West Coast and Stockholm.&lt;/p&gt;
&lt;h3 id="do-whatever-you-want!"&gt;Do whatever you want!&lt;/h3&gt;&lt;p&gt;If you feel like you can't find the right group or that you don't fit
in with existing groups or that you're missing a moment, there are
surely other folks in the same boat. Waiting for a new group to
join. You may be the catalyst.&lt;/p&gt;
&lt;p&gt;There's enormous potential for getting people together and doing
something interesting and there isn't necessarily anyone telling you
you should. Things you try may work and they may not. The more you try
the more you'll learn what works and what doesn't. I've had a few
years of &lt;a href="https://notes.eatonphil.com/eight-years-of-tech-meetups.html"&gt;making mistakes
organizing&lt;/a&gt;
to hone the sense.&lt;/p&gt;
&lt;p&gt;The only boring thing to do is to necessarily limit yourself to the
sort of thing others have done before! Run a browser meetup instead of
a React meetup. Interview hardware developers to teach software
developers something. Get software developers with 20 years of
experience in niche fields to teach the rest of us something. Read
books beyond SICP or Clean Code. Try difficult programming projects.&lt;/p&gt;
&lt;p&gt;Whatever you want though, don't let me deter you. If you think
something should exist, give it a shot!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I used to struggle to get much out of meetups, couldn't pick up the flow of IRC. Some point I stopped trying solely to fit in. Instead to do what I thought was interesting. And to my surprise, folks were interested in coming along too!&lt;br /&gt;&lt;br /&gt;Make your own way&lt;a href="https://t.co/tVEa2ndiZm"&gt;https://t.co/tVEa2ndiZm&lt;/a&gt; &lt;a href="https://t.co/piWSsv14lj"&gt;pic.twitter.com/piWSsv14lj&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1740150745931149471?ref_src=twsrc%5Etfw"&gt;December 27, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 27 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-12-26-make-your-own-way.html</guid></item><item><title>A Frivolous Feature</title><link>https://www.marginalia.nu/log/96_frivolous_asn/</link><description>Marginalia Search very recently gained the ability to filter results by Autonomous System, not only searching by ASN but by the organization information for that AS. At a glance this seems like a somewhat frivolous feature, but it has interesting effects.
Autonomous Systems are part of the Internet&amp;rsquo;s routing infrastructure. If your mental model of an IP number is that they are the phone number of the computer, this is something akin to a postal code.</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 22 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/96_frivolous_asn/</guid></item><item><title>Podcast: IaC in depth with Robert Hafner</title><link>https://blog.tedivm.com/appearances/2023/12/podcast-iac-in-depth-with-robert-hafner/</link><description>Robert Hafner on The IaC Podcast.</description><author>tedious ramblings</author><pubDate>Thu, 21 Dec 2023 16:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.tedivm.com/appearances/2023/12/podcast-iac-in-depth-with-robert-hafner/</guid></item><item><title>How To Read An Article On The Internet</title><link>https://www.marginalia.nu/log/95_how_to_read/</link><description>A simple guide to reading in 9 simple steps Navigate to the desired article. Dismiss the GDPR banner It may seem safe to start reading, but you need to wait about 10 seconds as the various ad auctions resolve and scripts load in Wait while the article is populated with ads. While the article is in front of you, there is no point to starting to read yet, as the minute&amp;rsquo;s worth of layout shift will make you lose your place.</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 20 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/95_how_to_read/</guid></item><item><title>WARC'in the crawler</title><link>https://www.marginalia.nu/log/94_warc_warc/</link><description>The Marginalia Crawler has seen improvements! A long term problem with the crawler design is that if for whatever reason the crawler shuts down, then it needs to re-start fetching whatever domains it was currently traversing during the termination from zero.
This isn&amp;rsquo;t fantastic, since not only does crawling a website take a fair bit of time, it&amp;rsquo;s a nuisance for the server admins to re-crawl stuff that was already fetched, and a real liability for ending up in robots.</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 20 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/94_warc_warc/</guid></item><item><title>Dwell and the Tools for Thought Hackathon</title><link>https://www.swyx.io/dwell-and-the-tools-for-thought-hackathon</link><description>&lt;p&gt;I worked on a little hackathon project at AGI House with Sasha.  We won!&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Wed, 13 Dec 2023 22:47:31 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/dwell-and-the-tools-for-thought-hackathon</guid></item><item><title>Go Libraries/Packages I Like</title><link>https://boyter.org/posts/go-libraries-i-like/</link><description>&lt;p&gt;In no particular order a list of Go libraries/packages I really like and some reasons why.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rs/zerolog/"&gt;zerolog&lt;/a&gt; I know that the changes to slog make it far closer to being what zero log is now, but I still prefer it for my own use.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/carlmjohnson/requests"&gt;requests&lt;/a&gt; Anyone who works with python for a while learns about the requests library for making HTTP calls. This is similar but for Go. It has a few sharp edges when dealing with websites that don&amp;rsquo;t follow standards, but otherwise is amazing. Coupled with the next library in the list you end up with a pretty solid way to call endpoints.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/philippta/trip"&gt;trip&lt;/a&gt; HTTP client middleware that allows automated retries, with backoff&amp;rsquo;s among other things such as authorization. Coupled with requests it really cleans up your HTTP calls.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/samber/lo"&gt;lo&lt;/a&gt; If you know of the wonderful underscore.js library then you probably have an idea of what this is. Similar idea, providing a lot of helper functions that allow dealing with some of Go&amp;rsquo;s sharp edges more easily. It does abstract away things that you might need to know about if you need absolute performance, but for most things it just makes life easier.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/a-h/templ"&gt;templ&lt;/a&gt; HTML template language for Go. There is nothing you can do in this that you cannot in Go templates, but you get compile time checks of the code rather than actually having to run it. It also means you don&amp;rsquo;t need to worry about embedding template (although this is fairly easy these days anyway). Works really well with HTMX.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tidwall/gjson"&gt;gjson&lt;/a&gt; A simple way to get values out of JSON without needing to unmarshall into a struct. Really useful for tests, as well as annoying API&amp;rsquo;s that have mixed data types.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gorilla/mux"&gt;mux&lt;/a&gt; The inbuilt Go router isn&amp;rsquo;t horrible, but is painful to use. This one supports path variables, and you can specify which http methods it uses.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sqlc-dev/sqlc"&gt;sqlc&lt;/a&gt; Generate type safe Go code to access your database. Of all the DB layer abstractions I have used this is best. Its similar to something I wrote in PHP over a decade ago, although much better.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mvdan/xurls"&gt;xurls&lt;/a&gt; The number of times I have gone looking for a URL regex only to find it misses some edge case&amp;hellip; This solves a lot of that pain, especially when it comes to extracting them out of text.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The below are things I am exploring, but not sold on yet.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Wed, 13 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/go-libraries-i-like/</guid></item><item><title>Using the BlueSky API to change your Handle to your Domain Name</title><link>http://pxtl.ca/2023/12/13/bluesky-api/</link><description>&lt;p&gt;Personally in the post-Muskening twitter world, I'm pulling for
&lt;a href="https://mastodon.social/@pxtl"&gt;Mastodon&lt;/a&gt;, but
&lt;a href="https://bsky.app/profile/pxtl.ca"&gt;Bluesky&lt;/a&gt; is a somewhat interesting platform
too.  One feature of Bluesky that stands out is that you can set your name to be
based on a domain-name you own.  I own Pxtl.ca, so I thought I'd take a crack at
leveraging that feature.&lt;/p&gt;

&lt;p&gt;What follows is a guide on how to do this in Powershell, which is a programming
language you &lt;em&gt;already have installed&lt;/em&gt; if you use Windows.&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Wed, 13 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/12/13/bluesky-api/</guid></item><item><title>Optimizing Ship Load-outs for Highfleet: Problem Setup</title><link>https://jodavaho.io/posts/highfleet-ship-optimization-with-scip.html</link><description>&lt;h2 id="over-engineering-highfleet-load-outs-using-integer-programming"&gt;Over-engineering Highfleet load-outs using Integer Programming&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I built a highfleet ship optimizer by bundling a fast C optimizer, binding to
a higher level language, and providing a web API. Then,
&lt;a href="https://github.com/altho"&gt;Altho&lt;/a&gt; built a front-end. Try it at
&lt;a href="https://hfopt.jodavaho.io/optimizer"&gt;hfopt.jodavaho.io/optimizer&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This post is about setting up the problem. See &lt;a href="https://jodavaho.io/tags/highfleet.html"&gt;related posts&lt;/a&gt;.
&lt;figure&gt;&lt;img alt="Dieselpunk warship schematic. Made with Midjourney." src="https://jodavaho.io/img/brownprint.png" /&gt;&lt;figcaption&gt;
      &lt;p&gt;Dieselpunk warship schematic. Made with Midjourney.&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/HighFleet" title="Wikipedia: Highfleet"&gt;Highfleet&lt;/a&gt; is an incredibly fun game. You take control of a small set of
ships, raid and pillage and revolt your way to glory. Along the way you
must carefully manage your resources to keep your ships repaired, and customize
them to the situation at hand. Lovely time.&lt;/p&gt;
&lt;p&gt;The most addicting part, as is common in this genre&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;, is the ship design and
customization. You can fully tailor your fleet to your desired play style!
Instead of rave about the gameplay, let&amp;rsquo;s talk about how you design ships.&lt;/p&gt;
&lt;h3 id="ship-design-problem-statement"&gt;Ship Design Problem statement&lt;/h3&gt;
&lt;p&gt;In Highfleet, you can &amp;ldquo;pre-design&amp;rdquo; ships before starting a campaign. You have a
limited starting budget, so you need to balance power, weight, engines, guns,
ammo types, layout, crew capacity, sensing, etc, to get the desired range,
speed, firepower, survivability, and combat time, while minimizing cost. This
problem is masterfully posed by the developers, and has hooked a small
community for years.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m interested in automating this, to a certain extent. Can we at &lt;em&gt;least&lt;/em&gt; come
up with an optimal module set - and leave the layout / cosmetics to the
creative user? Staring at the above paragraph, one might see this is a classic
optimization problem&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;. Words like &amp;ldquo;minimize&amp;rdquo;, &amp;ldquo;select&amp;rdquo;, &amp;ldquo;all while &amp;hellip;&amp;rdquo;,
&amp;ldquo;such that&amp;rdquo;, etc are clues.&lt;/p&gt;
&lt;p&gt;We can re-state above as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Select&lt;/em&gt; zero or more whole modules from a list&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Such that&lt;/em&gt; the modules have all their requirements satisfied (e.g., engines have fuel, guns have ammo)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;all while&lt;/em&gt; &lt;strong&gt;minimizing&lt;/strong&gt; cost&lt;/li&gt;
&lt;li&gt;and &lt;em&gt;satisfying&lt;/em&gt; some desired final ship stats&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Easy. Formulating the problem is half the battle.&lt;/p&gt;
&lt;h3 id="solving"&gt;Solving&lt;/h3&gt;
&lt;p&gt;To solve a problem like this you need the defining equations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &amp;ldquo;cost&amp;rdquo; function. In this case, it&amp;rsquo;s literally the dollar value of each module - easily obtained by writing down the values from the game. This is also called the &amp;ldquo;Objective Function&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;feasibility constraints&amp;rdquo;, which essentially answer: Is this a valid ship at all? Without these, we could choose modules that &lt;em&gt;seem&lt;/em&gt; to satisfy constraints, but would never fly in-game. For example: &amp;ldquo;Do we have enough ammo feeds to keep the guns working&amp;rdquo;, this again we can copy down from in-game stats&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Tradespace" title="Wikipedia: Trade Space"&gt;trade space&lt;/a&gt; equations&amp;rdquo;. In this case, we want to know the following: How does the selection of modules affect the desired final ship stats like Range, Speed, Firepower, etc. This turns out to be very hard, but doable with help.&lt;/li&gt;
&lt;li&gt;We also want the &amp;ldquo;desired stats&amp;rdquo;, which are just &amp;ldquo;constraints&amp;rdquo; we impose on the trade space. We&amp;rsquo;ll get this from the users as a set of valid &lt;em&gt;ranges&lt;/em&gt; (e.g., speed is greater than X, weight is less than Y, blah blah)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Great - all we need to do is reverse-engineer how the in-game stats are calculated. &lt;em&gt;easy right&lt;/em&gt;? Instead, I reached out to the game developer, and &lt;a href="https://reddit.com/r/highfleet"&gt;r/highfleet&lt;/a&gt;, to see what they could share. Everyone had some models, and Koshutin didn&amp;rsquo;t reply. So, I had to do it myself for the most part.&lt;/p&gt;
&lt;h3 id="reverse-engineering-the-game-stats"&gt;Reverse Engineering the Game Stats&lt;/h3&gt;
&lt;p&gt;For completeness, here is the list of relevant stats, obtained by parsing the ship files (which deserves its own post).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aavalue, airspeed, ammobox_need, ammobox_total, at_left, at_right, b.x, b.y, combatvalue, crew_capacity, crew_need, dynamic, dynamic_landing, dynamic_map, dynamic_map_repaired, firepower_guns, fss_capacity, fss_total, fuel, fuel_capacity, fuel_need, fuel_need_cr, fuel_on, hp, hp_integral, hp_max, mass, parts, parts_integral, power, power_need, power_real, power_total, power_total_repaired, price, r, signature, signature_ir, signature_rd, thrust_left, thrust_left_landing, thrust_left_map, thrust_left_map_repaired, thrust_right, thrust_right_landing, thrust_right_map, thrust_right_map_repaired, weapons_on
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Some we can throw right out. &lt;code&gt;b.x, b.y&lt;/code&gt; for example make &lt;em&gt;no sense&lt;/em&gt; to me. Some (&lt;code&gt;weapons_on&lt;/code&gt;) probably have more to do with the campaign mode than the performance. Some (&lt;code&gt;airspeed, r&lt;/code&gt;) are &lt;em&gt;exactly&lt;/em&gt; what we want to reverse engineer and optimize for.&lt;/p&gt;
&lt;p&gt;My explorations were fairly crude. I&amp;rsquo;d download many of the fan-made ships from reddit or discord, check the stats, make a spreadsheet, and start plotting to see if the relationships were obvious. Some guesses were obvious (&lt;code&gt;airspeed&lt;/code&gt; should be a function of &lt;code&gt;thrust/weight&lt;/code&gt;, which it was). Some were not (range (&lt;code&gt;r&lt;/code&gt;?), in particular).&lt;/p&gt;
&lt;p&gt;For forming the equations, some &lt;a href="https://en.wikipedia.org/wiki/Dimensional_analysis" title="Wikipedia: Dimensional Analysis"&gt;unit analysis&lt;/a&gt; really paid off. For example, range ultimately (sensibly) was essentially:&lt;/p&gt;
$$ r \approx C_1 \cdot \frac{ \text{fuel_capacity} }{ \text{fuel_consumption} } \cdot \text{airspeed} + C_2 $$&lt;p&gt; which has units &lt;code&gt;meters&lt;/code&gt;- note the unknown constants though&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;. Determining the constants is a job for &lt;a href="https://en.wikipedia.org/wiki/Linear_regression" title="Wikipedia: Linear Regression"&gt;linear regression&lt;/a&gt;! I won&amp;rsquo;t plot out the details, you can see them &lt;a href="https://github.com/jodavaho/hf-study/blob/master/Regression.ipynb" title="Github: Jodavaho/HF-Study"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At &lt;a href="https://github.com/jodavaho/hf-study/blob/master/Regression.ipynb" title="Github: Jodavaho/HF-Study"&gt;this repo&lt;/a&gt; you can see how it worked. Note &lt;a href="https://github.com/jodavaho/hf-study/blob/master/ships-data.csv" title="Github: Jodavaho/HF-Study/csv"&gt;this file&lt;/a&gt; with ship data, hand crafted! It has a sparses set of stats manually pulled from ships. Just enough to confirm the relationships. Once I had those I could pull up any ship from reddit or discord, and verify it. It was always close enough. Of course, I have to re-do this every time the game balances, but soon, I&amp;rsquo;ll automate this - another post to write.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="from study notebook, showing combat time regression" src="https://jodavaho.io/img/regression-study-1.png" /&gt;&lt;figcaption&gt;
      &lt;p&gt;from study notebook, showing combat time regression&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I did discover some hidden features this way, did you know generators require fuel? Makes sense, but at the time this was undocumented.&lt;/p&gt;
&lt;p&gt;So, that&amp;rsquo;s how I was able to pin down the relationshps for the &amp;ldquo;desired stats&amp;rdquo; from the game files. Next, we can formulate the optimizer.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mr. Koshutin, if you are reading this, &lt;em&gt;thank you&lt;/em&gt; for letting the community enjoy your game in this way. You certainly didn&amp;rsquo;t have to!&lt;/em&gt;&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;There is a &lt;a href="https://www.navalgazing.net/Naval-Video-Games" title="Naval Gazing: Naval Video Games"&gt;pseudo-genre&lt;/a&gt; of such games, and highfleet stands alone for its unique setting and fun combat.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;There are some softer constraints, too: Does it look cool? Does it land well? These are subjective and not well posed for optimization, so I ignore those (for now!).&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;One might imagine that the constants fall out of the in-game stats, but they do not. I suspect this is because &amp;ldquo;fudge factors&amp;rdquo; are applied.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Wed, 13 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/highfleet-ship-optimization-with-scip.html</guid></item><item><title>We don't need no stinking bastion host!</title><link>https://boyter.org/posts/who-needs-bastion-hosts/</link><description>&lt;p&gt;So on occasion, I find myself wanting to run SQL against customer databases. Where this gets annoying is that because we are often in AWS using lambda I have no ability to connect to them. Occasionally im able to bully the friendly devops&amp;rsquo; and security people to allow me access to a bastion host, which I can then connect to the SQL database.&lt;/p&gt;
&lt;p&gt;However this is often a painful process if allowed at all, and so with permission I sometimes add a special route into the application allowing me to run arbitrary SQL as needed.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Sun, 10 Dec 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/who-needs-bastion-hosts/</guid></item><item><title>Cleaning Up Speech Recognition with GPT</title><link>https://blog.nawaz.org/posts/2023/Dec/cleaning-up-speech-recognition-with-gpt/</link><description>&lt;p&gt;My friends often ask for use cases for &lt;span class="caps"&gt;GPT&lt;/span&gt;. Below I provide one recent&amp;nbsp;example.&lt;/p&gt;
&lt;p&gt;I attend a weekly educational seminar where real estate professionals
present the work they do. I take notes on paper, and later transcribe
them to my computer, which is a pain. As a result, I …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Thu, 07 Dec 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2023/Dec/cleaning-up-speech-recognition-with-gpt/</guid></item><item><title>What I'm up to - December 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-december-2023</link><description>&lt;div class="prose"&gt;
  &lt;div&gt;This is my monthly newsletter about what I'm up to, which &lt;a href="https://www.philipithomas.com/posts/how-to-replace-social-media-with-a-personal-newsletter"&gt;I send in place of social media&lt;/a&gt;.&lt;/div&gt;&lt;h2&gt;What I did in November&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Started writing some essays about online work, dependability, tools, and craft on the Contraption Company site. &lt;a href="https://www.contraption.co/essays/"&gt;Subscribe there&lt;/a&gt; if you want to follow them. First essays: &lt;a href="https://www.contraption.co/essays/booklet-architecture/"&gt;Booklet's Architecture&lt;/a&gt;, &lt;a href="https://www.contraption.co/essays/one-year-of-dependable-software/"&gt;One Year of Dependable Software&lt;/a&gt;, and &lt;a href="https://www.contraption.co/essays/indie-to-micro/"&gt;The Transition from Indiehacking to Micro Companies&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Had a great food-focused trip through Europe with a friend. I started up north in Oslo, and ended south in Barcelona. I went to some old favorites such as &lt;a href="https://noma.dk/"&gt;Noma&lt;/a&gt;, &lt;a href="https://www.aprilcoffeeroasters.com/"&gt;April&lt;/a&gt;, &lt;a href="https://poplburger.com/"&gt;Popl&lt;/a&gt;, and &lt;a href="https://www.dishoom.com/"&gt;Dishoom&lt;/a&gt;, and found some new favorites such as &lt;a href="https://www.disfrutarbarcelona.com/?lang=en"&gt;Disfrutar&lt;/a&gt; and &lt;a href="https://www.silolondon.com/"&gt;Silo&lt;/a&gt; (whose&lt;a href="https://www.philipithomas.com/posts/what-i-m-up-to-april-2023"&gt; book I read in April&lt;/a&gt;). Highlights included jumping in the Oslofjord from a floating sauna, eating a food that's legal in only three countries, walking an F1 track, and seeing the &lt;a href="https://philips-photography.postcard.page/posts/palais-des-papes"&gt;Pope's old residence&lt;/a&gt;. I also got to meet customers of Contraption Company products along the way.&lt;/li&gt;
&lt;li&gt;Started a &lt;a href="https://philips-photography.postcard.page/posts"&gt;Postcard for some photography experiments&lt;/a&gt; - &lt;a href="https://philips-photography.postcard.page/posts/ostmarka"&gt;here's my favorite&lt;/a&gt; so far. &lt;/li&gt;
&lt;li&gt;Continuing to work hard on &lt;a href="https://booklet.group"&gt;Booklet&lt;/a&gt;. A lot of new communities and members joined in November, and I'm working hard on some new features. &lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;Things to share&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles&lt;/strong&gt;: &lt;a href="https://www.theatlantic.com/ideas/archive/2023/11/city-traffic-congestion-pricing-costs/675923/?utm_campaign=work-in-progress&amp;amp;utm_source=newsletter&amp;amp;utm_medium=email&amp;amp;utm_content=20231108&amp;amp;lctg=61f81f71c8dd630912732441&amp;amp;utm_term=Work%20in%20Progress"&gt;Maybe don't drive into Manhattan&lt;/a&gt;. &lt;a href="https://www.newyorker.com/magazine/2023/11/20/a-coder-considers-the-waning-days-of-the-craft"&gt;A Coder Considers the Waning Days of the Craft&lt;/a&gt;. &lt;a href="https://www.avascent.com/news-insights/perspectives/sharpe-focus-the-recent-emergence-of-defense-unicorns/"&gt;SHARPE&lt;/a&gt; is FAANG for defense. &lt;a href="https://en.wikipedia.org/wiki/Vespa_150_TAP"&gt;Anti-tank Vespa&lt;/a&gt;. &lt;a href="https://bryce.medium.com/the-indie-era-of-startups-c92704a75ed2"&gt;The Indie Era of Startups&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Podcasts&lt;/strong&gt;: &lt;a href="https://www.bbc.co.uk/sounds/play/p0gt1jy1"&gt;Tim Cook on Dua Lipa's podcast&lt;/a&gt;. &lt;a href="https://www.lennyspodcast.com/brian-cheskys-new-playbook/"&gt;Brian Chesky's new playbook&lt;/a&gt;. &lt;a href="https://longform.org/posts/longform-podcast-558-craig-mod"&gt;Craig Mod on Longform&lt;/a&gt;.  &lt;a href="https://overcast.fm/+BDSS6KPt0o"&gt;Lenny Rachitsky on How I Write&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Music&lt;/strong&gt;: &lt;a href="https://open.spotify.com/artist/0bdfiayQAKewqEvaU6rXCv"&gt;MØ&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps&lt;/strong&gt;: &lt;a href="https://ruter.no/en/"&gt;Ruter&lt;/a&gt;, the official transportation app for Oslo, is fantastic. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffee I'm drinking: &lt;/strong&gt;&lt;a href="https://timwendelboe.no/product/los-pirineos-pacamara/"&gt;Los Pirineos Pacamara – Christmas Edition&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;Plans for December&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Rolling out some new &lt;a href="https://booklet.group"&gt;Booklet&lt;/a&gt; features.&lt;/li&gt;
&lt;li&gt;Working on more &lt;a href="https://contraption.co/essays"&gt;essays&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Thawing in California.&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;Where I'll be &lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;22-23 Dec: Cleveland, OH&lt;/li&gt;
&lt;li&gt;23-27 Dec: Joshua Tree, CA&lt;/li&gt;
&lt;li&gt;27 Dec - 3 Jan: Los Angeles&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;If you're reading this, let me know if we overlap!&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Mon, 04 Dec 2023 17:17:11 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-december-2023</guid></item><item><title>Bouldering</title><link>https://www.swyx.io/bouldering</link><description>&lt;p&gt;My first introduction to bouldering was in 2017, in DUMBO Boulders in Brooklyn Bridge Park (&lt;a href="https://www.brooklynbridgepark.org/about/press-releases/bbp-selects-dumbo-boulders-to-operate-bouldering-wall-at-main-street/"&gt;now closed&lt;/a&gt;):&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Wed, 29 Nov 2023 10:16:46 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/bouldering</guid></item><item><title>Festive offers for books on Python, Linux, Regular Expressions and more</title><link>https://learnbyexample.github.io/programming-deals-2023/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;Here are some exciting deals for my programming ebooks as well as from other creators.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="my-ebooks"&gt;My ebooks&lt;a class="zola-anchor" href="#my-ebooks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Offers valid till 30-Nov-2023:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"&gt;All 13 Books Bundle&lt;/a&gt; — $10 (normal price $32)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/python-bundle/FestiveOffer"&gt;Learn by example Python bundle&lt;/a&gt; — $4 (normal price $15)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/py_regex/FestiveOffer"&gt;Understanding Python re(gex)?&lt;/a&gt; — FREE (normal price $10)&lt;/li&gt;
&lt;/ul&gt;
&lt;p align="center"&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"&gt;&lt;img alt="All books bundle" src="/images/books/all_books_bundle.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="indie-creators"&gt;Indie creators&lt;a class="zola-anchor" href="#indie-creators"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mathspp.gumroad.com/l/pythonbootcamp/bootcampbf23"&gt;Python Problem-Solving Bootcamp&lt;/a&gt; — 40% off or purchasing power parity discount whichever is greater&lt;/li&gt;
&lt;li&gt;&lt;a href="https://driscollis.gumroad.com/"&gt;Python books by Michael Driscoll&lt;/a&gt; and &lt;a href="https://www.teachmepython.com/"&gt;Teach Me Python Membership&lt;/a&gt; — 33% off with &lt;code&gt;black23&lt;/code&gt; discount code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://adamchainz.gumroad.com/"&gt;Ebooks on Django and Git&lt;/a&gt; — 50% off, plus purchasing power parity if applicable
&lt;ul&gt;
&lt;li&gt;see also author's &lt;a href="https://adamj.eu/tech/2023/11/20/django-black-friday-deals-2023/"&gt;blog post&lt;/a&gt; for links to other Django-related deals&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thepythoncodingplace.thinkific.com/bundles/the-python-coding-place-membership"&gt;The Python Coding Place Membership&lt;/a&gt; — 70% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonmorsels.com/lifetime-access-sale/"&gt;Python Morsels Membership&lt;/a&gt; — lifetime access for the price of 2 years
&lt;ul&gt;
&lt;li&gt;see also author's &lt;a href="https://treyhunner.com/2023/11/python-black-friday-and-cyber-monday-sales-2023/"&gt;blog post&lt;/a&gt; for links to other Python deals&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bhavaniravi.gumroad.com/l/LaFSj/P2PBLACK2023"&gt;Python To Projects - 5 Week Online Course&lt;/a&gt; — 25% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lernerpython.com/bfcm-2023/"&gt;Python books by Reuven Lerner&lt;/a&gt; — 40% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wizardzines.com/"&gt;wizard zines&lt;/a&gt; — 50% off on PDFs, 30% off on print versions&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shrutibalasa.gumroad.com/l/level-up-with-tailwind-css/blackfriday23"&gt;Level up with Tailwind CSS&lt;/a&gt; and &lt;a href="https://shrutibalasa.gumroad.com/l/css-flex-and-grid/blackfriday23"&gt;Complete Guide to CSS Flex and Grid&lt;/a&gt; — 50% off&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="miscellaneous"&gt;Miscellaneous&lt;a class="zola-anchor" href="#miscellaneous"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nostarch.com/blog/2023-holiday-gift-guide"&gt;NoStarch Press&lt;/a&gt; — 35% off with &lt;code&gt;DEALS4DAYS&lt;/code&gt; code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://media.pragprog.com/newsletters/2023-11-17.html"&gt;The Pragmatic Bookshelf&lt;/a&gt; — 40% off on all ebooks and audio books&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deals.manning.com/buy-2-save-50/"&gt;Manning Publications&lt;/a&gt; — save 50% when you buy 2 or more MEAPs, eBooks, pBooks, liveProjects, or liveVideos&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0x90n/InfoSec-Black-Friday"&gt;InfoSec Hack Friday&lt;/a&gt; — InfoSec related software/tools&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opsdisk.gumroad.com/l/cphlab/blackfriday2023"&gt;The Cyber Plumber's Lab Guide and Interactive Access&lt;/a&gt; — 33% OFF&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mailchi.mp/leanpub/monthly-sale-2023-black-friday"&gt;Leanpub Monthly Sale&lt;/a&gt; and &lt;a href="https://mailchi.mp/leanpub/weekly-sale-2023-black-friday"&gt;Leanpub Weekly Sale&lt;/a&gt; — offers for programming books, bundles and courses&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/trungdq88/Awesome-Black-Friday-Cyber-Monday"&gt;Huge list of awesome deals&lt;/a&gt; — tools, productivity, books, courses, etc&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blackfridaydeals.dev/"&gt;blackfridaydeals.dev&lt;/a&gt; — Hottest Black Friday Deals for Developers&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.diversifytech.com/black-friday-deals-2023-savings-on-tech-books-and-courses/"&gt;Black Friday Deals&lt;/a&gt; — Savings on Tech Books and Courses&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Sat, 25 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/programming-deals-2023/</guid></item><item><title>(fake) Football Leagues</title><link>https://rjp.is/blogging/posts/2023/11/football-leagues/</link><description>In which we fake some leagues for fun.</description><author>infrequent oscillations</author><pubDate>Thu, 23 Nov 2023 14:01:32 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/11/football-leagues/</guid></item><item><title>Lua Carousel</title><link>http://akkartik.name/post/carousel</link><description>&lt;p&gt;
I finally decided to hang up a shingle on itch.io. My first app there is not a
game. &lt;a href="https://akkartik.itch.io/carousel"&gt;Lua Carousel&lt;/a&gt; is a
lightweight environment for writing small, throwaway Lua and LÖVE programs.
With many thanks to Mike Stein who helped me figure out how to get it working
on iOS, this is my first truly cross-platform app, working on Windows, Mac,
Linux, iOS and Android.

&lt;p&gt;
&lt;img alt="screenshot" src="https://akkartik.name/images/20231123-lua-carousel.png" style="width: 100%;" /&gt;

&lt;!-- more --&gt;

&lt;p&gt;
&lt;a href="https://git.sr.ht/~akkartik/carousel.love"&gt;repo&lt;/a&gt;

&lt;p&gt;
Carousel has its own devlog/notebook. I try to post little scripts that are
easy for someone to copy to their clipboard, paste into Carousel and run. Some
examples:
&lt;ul&gt;
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/645189/version-av-one-bugfix-support-for-lve-115"&gt;Hilbert curve and Lindenmayer tree fractal&lt;/a&gt; (70 lines each)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/647054/2-days-um-no-0-days-since-last-bug"&gt;rectangle art&lt;/a&gt; (40 lines)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/647726/playing-with-lves-physics-engine-for-the-first-time"&gt;simple example using the physics engine.&lt;/a&gt; (100 lines)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/651711/new-version-after-9-days"&gt;Chromacline: a war between colors in Game of Life world&lt;/a&gt; (100 lines)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/653245/the-lua-carousel-productivity-suite"&gt;simple UI examples; simple voice recorder&lt;/a&gt; (&lt;50 lines each)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/656473/building-an-equation-plotter"&gt;sprite editor&lt;/a&gt; (100 lines)
&lt;li&gt; &lt;b&gt;&lt;a href="https://akkartik.itch.io/carousel/devlog/673935/pong-wars"&gt;Pong wars&lt;/a&gt;&lt;/b&gt; (70 lines)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/678890/new-version-after-51-days"&gt;equation plotting over an infinite surface with pan and pinch-zoom&lt;/a&gt; (150 lines)
&lt;li&gt; &lt;a href="https://akkartik.itch.io/carousel/devlog/686788/lots-of-charts"&gt;Lots of different kinds of charts&lt;/a&gt; (200 lines)
&lt;/ul&gt;</description><author>Kartik Agaram</author><pubDate>Thu, 23 Nov 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/carousel</guid></item><item><title>Starfield review</title><link>https://burakku.com/blog/starfield-review/</link><description>&lt;p&gt;&lt;img alt="Starfield" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Mr. Howard's Mild Ride.&lt;/p&gt;
&lt;p&gt;The very first impression I had with Starfield was that the world design looked really good. The art direction of Starfield is great. I'm a big fan of the "NASA punk". However, the further I progressed in the game, the less impressed I got with the world. While the individual aspects of it might look good, it's way too repetitive. You land on a single wasteland planet and you've really landed on all of them. And as far as I can tell, the few points of interest in them are repeated throughout the known universe.&lt;/p&gt;
&lt;p&gt;The cities are pathetic too. There's a couple of different cities with vastly different designs. They do however have one thing in common: the scale. All of the cities feel woefully small. It feels like I'm walking through a movie set than an actual city. And despite the lack of scale, there's loading screens absolutely everywhere. Get out of your space ship – loading screen. Go to a small residential area – loading screen. Go to the weapon store inside the small residential area – loading screen. Thankfully the missions aren't as full of loading screens but it seems strange that these small cities are in a big-budget 2023 release.&lt;/p&gt;
&lt;p&gt;Traversal between places is also deeply unsatisfying. I had to go from a city on a planet, to a bandit camp on the same planet. I opened the extremely poor planet map, selected the bandit camp, and tried to transport there. Sorry, the game said, you can't fast travel to a place you haven't discovered yet. Then it forced me to fast travel to my spaceship and then land my spaceship at the bandit camp. And what does "landing your spaceship" actually entail? A loading screen and a jump cut to the spaceship landing, which all takes around 10 seconds. Almost all traversal in this game is fast travel and yet it still does this idiotic gatekeeping for what it thinks "fast travel" is.&lt;/p&gt;
&lt;p&gt;The space bit in Starfield doesn't really improve the situation at all. You can either talk to whatever NPCs happen to be around you, or engage them in battle. That's it. Despite the lack of anything, you'll still be going to space very often since you need to spend at least a few seconds there between fast travels. Actually, the NPCs either reaching out or attacking got very tiresome since I just wanted to travel to my destination and not play tag with pirates.&lt;/p&gt;
&lt;p&gt;The repetition in the world design also comes across in the story campaign. The story had me go look for Andreja, and I found her in an enemy-infested mine, and at the end of the mine, I found an artefact. Then later in the story, I was tasked to go look for more artefacts, one of which was at the end of an enemy-infested mine. A mine with the exact same layout, except this one was on a different planet and lacked one Andreja. Both of these mines also had a locked locker room, with contraband for the taking after you picked open the lock. While I can understand that the planets look similar when you have such a vast number of them, I expected a lot more from the main story locations in a AAA game.&lt;/p&gt;
&lt;p&gt;Really feels like Bethesda is just taking the piss with the repetition considering missions are actually named "Into the Unknown" and "Further Into the Unknown". A bit on the nose there Todd.&lt;/p&gt;
&lt;p&gt;As for the story itself, I didn't really care for it. First of all, I found the premise of how you became an adventurer quite contrived. And while I think they want to make Constellation feel like some sort of an elite gentleman explorer's club, most of the story missions made me feel like their glorified errand girl instead. For every interesting mission, like Unearthed or Entangled, there were two or three missions where I just had to fly from one remote planet to another, literally digging for quest items. Surely there must've been a more interesting story to write in the Starfield universe, right? And despite the fact that the story involves life and death situations, the writing is just so devoid of any edge or character. The whole thing just feels like such a lifeless and watered down experience.&lt;/p&gt;
&lt;p&gt;&lt;img alt="I love you too, Vasco" src="vasco.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Guess it didn't help that I couldn't care for any of the characters, including my own. The only (human) member of Constellation that I didn't have any active contempt towards was Walter. Vasco was cool too, and got promoted to my primary companion after Sarah got very angry at me for a heinous murder where I shot a man after he and his friends unloaded every bullet they had towards our direction. I guess Sarah was looking to end her miserable existence and I rudely interrupted it by defending myself.&lt;/p&gt;
&lt;p&gt;I've heard that some of the side content is much superior but unfortunately I seem to have never encountered it. I guess I did mainly focus on the main quest so maybe that's on me. However, I did encounter side content that I didn't care for.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Defiant settler captain" src="settler.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;One example is a mission where settlers task me to drive out a corp from a planet that they consider their birthright and one that they seem willing to fight for. I go talk to the corp and they want me to either blow up the settlers or convince them to become serfs on their luxury resort planet. I decided to not blow up these poor settlers and instead opt to tell them about this indentured servitude proposal. Considering how defiant the settler captain acted, I expected this to lead into a war where they take the planet by force. Nope, the settlers turn heel and agree to become slaves. They they asked me to bring them a bunch of materials, presumably so they can make their own leg irons to really enjoy their bondage. I considered blowing them up for their own good but settled to just leave instead.&lt;/p&gt;
&lt;p&gt;I did enjoy the ending though, at least on a conceptual and storytelling level. Unfortunately the gameplay side of it is extremely herky-jerky, and is not at all enhanced by the space combat or waves of trash mobs you also have to deal with. And right at the last second, the story really just raises more questions than it answers. At least the ending was pretty.&lt;/p&gt;
&lt;p&gt;As a game, Starfield is a rather unfocused affair. It's so chock-full of different systems that it feels very overwhelming when you get started. Maybe veterans to Bethesda RPGs have already learned to juggle these systems in prior games, but as a newcomer, you need to ingest a lot of (unhelpful) tutorials at the beginning. You have ground traversal, space traversal, ground combat, ship combat, ship building, outpost building, cooking, crafting, spacesuit upgrading, weapon customisation, mystical powers, lockpicking, persuasion, scanning, mining, stealth, encumbrance, commerce, status effects and so on. "A mile wide and an inch deep" is the best quote I have heard to describe this game. Many of these systems are very badly taught to you, and even when you learn them, they're still not that great. Quite possibly why it also took me around ten hours to actually get into the game properly.&lt;/p&gt;
&lt;p&gt;The ground combat feels fine. There's a decent selection of firearms available to you and they do feel pretty distinct from each other. It's definitely not very flashy, and there's very limited amount of specialisation you can do with the combat but I did generally enjoy engaging the enemy. The boost pack also livens up the movement for the combat sections too.&lt;/p&gt;
&lt;p&gt;The mystical space powers feel like a complete waste of time, at least for enhancing the combat. I really only ever used the unlimited stamina power and otherwise ignored them. And the way that you obtain all of these various powers is so needlessly repetitive. You have to &lt;em&gt;literally&lt;/em&gt; jump through hoops for them, again and again and again. It feels more like an elaborate prank than game design.&lt;/p&gt;
&lt;p&gt;Space combat is less fine. First of all, whenever you engage in ship combat, you were most likely just on your way to do something completely different until someone decided to harass you. And when they do, they usually do it with their pals. In three-dimensional space. I feel like most of the time in combat is spent around doing loops in order to just find one of the three hostile ships so you can blast them, while trying to keep their two friends from doing the same to you. Even though I rarely struggled, I still did not enjoy it.&lt;/p&gt;
&lt;p&gt;Ship building seemed pretty cool, at least for someone that enjoys that kind of a thing. I mainly used it pretty pragmatically, so instead of trying to replicate famous designs or come up with my own, I just used it to upgrade an existing design. It does take some getting used to, since the game doesn't really teach you how to do it and I had to get supplementary lessons from Google on how I can get ship pieces to connect how I want. And unfortunately changing your ship in any way completely destroys any interior decor you might have going on (for understandable technical reasons), so it definitely kept me from doing it just for fun. Maybe people that are heavily into this would find more to criticise about it but I thought that the ship building was cool overall.&lt;/p&gt;
&lt;p&gt;Outpost building is also something that is not extremely well taught to you. And it doesn't really feel like you would ever need to do it. However, once I learned a bit on how it works, I got a frenzy of finding resource-heavy planets, building outposts on them all, and connecting them all together to a master outpost. I got pretty absorbed into it until I had all of my outposts connected. Then I realised that I didn't really have any kind of use for it. It just kinda is there, filling up my inventory and few resource crates with a bunch of minerals that I don't really have an use for. I did my outpost fairly early in my run and had no reason to touch it afterwards.&lt;/p&gt;
&lt;p&gt;Inventory management is also subpar. For some reason Bethesda decided that the majority of the screen should be taken by 3D representations of your items instead of useful information. While the gun models look nice, I don't find myself that interested in knowing what copper ore looks like. I'd much rather have a side-by-side view of my inventory and a vendor's inventory instead of having to flip between two or three different inventories as I unload my stuff. The vendors will also instantly run out of money when you go sell things, so you need to go do your tedious inventory management with every single vendor in the game. At least I did get used to the inventory, so the user interface is not a constant source of pain, but I still pine for something better.&lt;/p&gt;
&lt;p&gt;The lockpicking minigame is actually rather fun but it takes home the prize for the worst explained system in the game. I had no clue on how to do it and was just randomly trying to get through it. Then I watched a short tutorial video on YouTube and got it. Now I'm pretty good at picking open the locks and can actually enjoy some momentary tinkering with locks. What a shame the game doesn't want to teach you about it.&lt;/p&gt;
&lt;p&gt;Not a big fan of character creation either. It feels like some of the aspects of it are very varied and some are barely varied at all. I would've also enjoyed having a voice for my character. There's one part in the story that actually makes me feel like it would've been very reasonable and feasible thing even. And I really don't understand the point of the traits. I remember people criticising the lifepaths in Cyberpunk 2077 for having little impact on the game, but I feel like the traits in Starfield have even less of an impact. I was a Neon Street Rat and it just gave me a couple of new choices in one particular part of the game.&lt;/p&gt;
&lt;p&gt;Don't really like the skill trees either, especially how it locks some basic abilities in the game behind a skill point. Or many skill points for things in the advanced tiers of the skill tree. I'd expect to be able to do at least some stealth without investing points to a stealth skill but alas. You also need to level up your skills, so don't even think about reaching rank 4 in Medicine without spending time harming yourself and then ingesting health kits by the dozen.&lt;/p&gt;
&lt;p&gt;Gatekeeping parts of the game by hiding them in the skill tree are also why I never really got into the crafting and upgrading aspect of the game. You have a few things open at the start of the game but anything beyond that requires investing your few skill points. A shame, since I would've liked to upgrade my weapons more, but Weapon Engineering is an advanced Science skill, so I'd need to invest and level up my science skills before I even got a single point in.&lt;/p&gt;
&lt;p&gt;Technical aspects of the game are a bit of a mixed bag. The game was surprisingly stable and bug-free for a new AAA release, although neither aspect was perfect. My crew sank into my spaceship more times than I remember to count and when the game decided to crash, it screwed me out of a lot of progress. At least Cyberpunk 2077 was polite enough autosave very frequently to make up for its lack of stability, and wouldn't make me lose 30 minutes of gameplay with a single hard crash. The performance seems relatively poor considering the lack of ray tracing. Considering how Cyberpunk 2077 looks a lot better with crazy ray tracing, Starfield doesn't deliver that much of a higher frame rate.&lt;/p&gt;
&lt;p&gt;It does seem like they did manage to improve it a bit with the recent patch though, but I've yet to clock any serious amount of time in it to make a judgement. Fingers crossed!&lt;/p&gt;
&lt;p&gt;I tend to avoid pre-release marketing since I want to temper my expectations for new stuff, but I'm still left feeling disappointed. It's not that Starfield is an awful game. I did manage to beat it after all. It just feels really mediocre and undaring. Perhaps it's worth taking a stab at if you were already paying for Game Pass, or found it in a bargain bin, but for its pain points and overwhelming sense of mediocrity, it's easy to skip and hard to recommend.&lt;/p&gt;</description><author>ブラック</author><pubDate>Wed, 22 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/starfield-review/</guid></item><item><title>The Knight Capital Disaster</title><link>https://specbranch.com/posts/knight-capital/</link><description>&lt;p&gt;&lt;em&gt;This account comes from several publicly available sources as well as accounts from insiders who worked at
Knight Capital Group at the time of the issue. I am telling it second- or third-hand.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On August 1, 2012, Knight Capital fell on its sword. It experienced a software glitch that literally bankrupted the
company.  Between 9:30 am and 10:15 am EST, the employees of Knight capital watched in disbelief and scrambled to
figure out what went wrong as the company acquired massive long and short positions, largely concentrated in 154
stocks, totaling 397 million shares and $7.65 billion.  At 10:15, the kill switch was flipped, stopping the
company's trading operations for the day.  By early afternoon, many of Knight Capital's employees had already
sent out resumes, expecting to be unemployed by the end of the week.&lt;/p&gt;</description><author>Speculative Branches</author><pubDate>Wed, 22 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://specbranch.com/posts/knight-capital/</guid></item><item><title>Wild Speculation Regarding the OpenAI Fiasco</title><link>https://tiltingatwindmills.dev/wild-speculation-regarding-the-openai-fiasco/</link><description>First, I feel I need to make one thing clear: I'm nobody and have no special inside information. I'm just a random terminally-online guy who…</description><author>Tilting at Windmills</author><pubDate>Tue, 21 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://tiltingatwindmills.dev/wild-speculation-regarding-the-openai-fiasco/</guid></item><item><title>Open source, locally hosted AI powered Siri replacement</title><link>https://smcleod.net/2023/11/open-source-locally-hosted-ai-powered-siri-replacement/</link><description>Using an ESP32 S3 Box 3 with Willow, Home Assistant and Large Language Models (LLMs) create a locally hosted, offline, AI powered Siri / Alexa replacement.</description><author>smcleod.net</author><pubDate>Mon, 20 Nov 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/11/open-source-locally-hosted-ai-powered-siri-replacement/</guid></item><item><title>Exploring a Postgres query plan</title><link>http://notes.eatonphil.com/2023-11-19-exploring-a-postgres-query-plan.html</link><description>&lt;p&gt;&lt;!-- -*- mode: markdown -*- --&gt;&lt;/p&gt;
&lt;p&gt;I learned this week that you can intercept and redirect Postgres query
execution. You can hook into the execution layer so you're given a
query plan and you get to decide what to do with it. What rows to
return, if any, and where they come from.&lt;/p&gt;
&lt;p&gt;That's very interesting. So I started writing code to explore execution
hooks. However, I got stuck interpreting the query plan. Either
there's no query plan walking infrastructure or I just didn't find it.&lt;/p&gt;
&lt;p&gt;So this post is a digression into walking a Postgres query plan. By
the end we'll be able to run &lt;code&gt;psql -c 'SELECT a FROM x WHERE a &amp;gt; 1'&lt;/code&gt;
and reconstruct the entire SQL string from a Postgres
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/executor/execdesc.h#L33"&gt;&lt;code&gt;QueryDesc&lt;/code&gt;&lt;/a&gt;
object, the query plan object Postgres builds.&lt;/p&gt;
&lt;p&gt;With that query plan walking infrastructure in place, we'll be in a
good state to not just print out the query plan while walking it but
instead to translate the query plan or evaluate it in our own way
(e.g. over column-wise data, or &lt;a href="https://github.com/citusdata/postgres_vectorization_test"&gt;vectorized execution over row-wise
data&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Code for this project is &lt;a href="https://github.com/eatonphil/pgexec"&gt;available on
Github&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="what-is-a-query-plan?"&gt;What is a query plan?&lt;/h3&gt;&lt;p&gt;If you're familiar with parsers and compilers, a query plan is like an
intermediate representation (IR) of a program. It is not as raw as an
abstract syntax tree (AST); it has already been optimized.&lt;/p&gt;
&lt;p&gt;If that doesn't mean anything to you, think of a query plan as a
structured and optimized version of the SQL query you submit to your
database. It isn't text anymore. It is &lt;a href="https://buttondown.email/jaffray/archive/why-are-query-plans-trees/"&gt;probably a
tree&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check out another Justin Jaffray &lt;a href="https://justinjaffray.com/what-is-a-query-optimizer-for/"&gt;article on the
subject&lt;/a&gt; for
more detail.&lt;/p&gt;
&lt;h3 id="development-environment"&gt;Development environment&lt;/h3&gt;&lt;p&gt;Before we get to walking the query plan, let's set up the
infrastructure to intercept query execution where we can eventually
add in our print debugging of the query plan reconstructed as a SQL
string.&lt;/p&gt;
&lt;p&gt;Once you've got &lt;a href="https://wiki.postgresql.org/wiki/Compile_and_Install_from_source_code"&gt;Postgres build
dependencies&lt;/a&gt;,
build and install a debug version of Postgres:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/postgres/postgres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;postgres
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Make sure you're on the same commit I'm on, just to be safe.&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;checkout&lt;span class="w"&gt; &lt;/span&gt;b218fbb7a35fcf31539bfad12732038fe082a2eb
$&lt;span class="w"&gt; &lt;/span&gt;./configure&lt;span class="w"&gt; &lt;/span&gt;--enable-cassert&lt;span class="w"&gt; &lt;/span&gt;--enable-debug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-ggdb -Og -g3 -fno-omit-frame-pointer&amp;quot;&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j8
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Installs to to /usr/local/pgsql/bin.&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I'm not going to cover Postgres extension infrastructure in detail. I
wrote a bit about it in &lt;a href="https://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html"&gt;my last
post&lt;/a&gt;.
You need only read the first half, if at all; not the actual Table
Access Method implementation.&lt;/p&gt;
&lt;p&gt;It will be even simpler in this post because Postgres hooks are
extensions but not extensions you install with &lt;code&gt;CREATE EXTENSION&lt;/code&gt;. If
you want to read about the different kinds of Postgres extensions,
check out &lt;a href="https://tembo.io/blog/four-types-of-extensions/"&gt;this
article&lt;/a&gt; by Steven
Miller.&lt;/p&gt;
&lt;p&gt;The minimum we need, aside from the hook code itself, is a Makefile
that uses
&lt;a href="https://www.postgresql.org/docs/current/extend-pgxs.html"&gt;PGXS&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;MODULES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pgexec

&lt;span class="nv"&gt;PG_CONFIG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/local/pgsql/bin/pg_config
&lt;span class="nv"&gt;PGXS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;PG_CONFIG&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--pgxs&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="cp"&gt;include $(PGXS)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;MODULES&lt;/code&gt; value there corresponds to the C file we'll create
shortly, &lt;code&gt;pgexec.c&lt;/code&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  This &lt;code&gt;pg_config&lt;/code&gt; binary path is important because you
  might have different versions of Postgres installed, for example by
  your package manager. It is important that the extension is built
  against the same version of Postgres which will load the extension.
&lt;/p&gt;&lt;p&gt;Now we're ready for some hook code.&lt;/p&gt;
&lt;h3 id="intercepting-query-execution"&gt;Intercepting query execution&lt;/h3&gt;&lt;p&gt;You can find the basic structure of a hook (and which hooks are
available) in Tamika Nomara's &lt;a href="https://github.com/taminomara/psql-hooks"&gt;unofficial Postgres hooks
docs&lt;/a&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  There is no official central place describing all hooks I could find
  in Postgres docs. Some hooks are described in various places
  throughout the docs though.
&lt;/p&gt;&lt;p&gt;Based on that page, we can write a bare minimum hook that will
intercept queries, log when we've done so, and pass control back
to the standard execution path for the actual query. In &lt;code&gt;pgexec.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;postgres.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;fmgr.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;executor/executor.h&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;PG_MODULE_MAGIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] HOOKED SUCCESSFULLY!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pgexec_run_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ScanDirection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execute_once&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execute_once&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_PG_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;standard_ExecutorRun&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgexec_run_hook&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_PG_fini&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev_executor_run_hook&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can discover the &lt;code&gt;standard_ExectutorRun&lt;/code&gt; function from a quick
&lt;code&gt;git grep ExecutorRun_hook&lt;/code&gt; in the Postgres source which leads to
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/backend/executor/execMain.c#L306"&gt;src/backend/executor/execMain.c#L306&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="nf"&gt;ExecutorRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;ScanDirection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execute_once&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ExecutorRun_hook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execute_once&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;standard_ExecutorRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;execute_once&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So our hook will just log and pass back execution to the existing
execution hook. Let's build and install the extension.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we'll create a new database and tell it to load the extension.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/initdb&lt;span class="w"&gt; &lt;/span&gt;test-db
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shared_preload_libraries = 'pgexec'&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;test-db/postgresql.conf
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  Remember, hooks are not &lt;code&gt;CREATE EXTENSION&lt;/code&gt; extensions. As
  far as I can tell they can't be dynamically loaded (without some
  additional dynamic loading infrastructure one could potentially
  write). So every time you make a change you need to rebuild the
  extension, reinstall it, and restart the Postgres server.
&lt;/p&gt;&lt;p&gt;And start the server in the foreground:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/postgres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--config-file&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/test-db/postgresql.conf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/test-db
&lt;span class="go"&gt;  -k $(pwd)/test-db&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.680 GMT [3215547] LOG:  starting PostgreSQL 17devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 13.2.1 20230728 (Red Hat 13.2.1-1), 64-bit&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on IPv6 address &amp;quot;::1&amp;quot;, port 5432&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on IPv4 address &amp;quot;127.0.0.1&amp;quot;, port 5432&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on Unix socket &amp;quot;/tmp/.s.PGSQL.5432&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.682 GMT [3215550] LOG:  database system was shut down at 2023-11-18 19:20:16 GMT&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.684 GMT [3215547] LOG:  database system is ready to accept connections&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Keep an eye on this foreground process since this is where &lt;code&gt;elog(LOG,
...)&lt;/code&gt; calls will show up.&lt;/p&gt;
&lt;p&gt;Now in a new window, create a &lt;code&gt;test.sql&lt;/code&gt; script that we can use to
exercise the hook:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;309&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run &lt;code&gt;psql&lt;/code&gt; so we can trigger the hook:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;DROP TABLE&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;INSERT 0 1&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt; 309&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in the &lt;code&gt;postgres&lt;/code&gt; foreground process you should see a log:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 17:42:03.045 GMT [3242321] LOG:  [pgexec] HOOKED SUCCESSFULLY!&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 17:42:03.045 GMT [3242321] STATEMENT:  INSERT INTO x VALUES (309);&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 17:42:03.045 GMT [3242321] LOG:  [pgexec] HOOKED SUCCESSFULLY!&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 17:42:03.045 GMT [3242321] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's our hook! Interestingly only the &lt;code&gt;INSERT&lt;/code&gt; and &lt;code&gt;SELECT&lt;/code&gt;
statements show up, not the &lt;code&gt;DROP&lt;/code&gt; and &lt;code&gt;CREATE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now let's see if we can reconstruct the query text from that first
argument, the &lt;code&gt;QueryDesc*&lt;/code&gt; that &lt;code&gt;pgexec_run_hook&lt;/code&gt; receives. And let's
simplify things for ourselves and only worry about reconstructing a
&lt;code&gt;SELECT&lt;/code&gt; query.&lt;/p&gt;
&lt;h3 id="&amp;lt;code&amp;gt;node&amp;lt;/code&amp;gt;s-and-&amp;lt;code&amp;gt;datum&amp;lt;/code&amp;gt;s"&gt;&lt;code&gt;Node&lt;/code&gt;s and &lt;code&gt;Datum&lt;/code&gt;s&lt;/h3&gt;&lt;p&gt;But first, let's talk about two fundemental ways data in Postgres
(code) is organized.&lt;/p&gt;
&lt;p&gt;Postgres code is extremely dynamic and, maybe relatedly,
fairly object-oriented. Almost every entity in Postgres is a
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/nodes.h#L128"&gt;&lt;code&gt;Node&lt;/code&gt;&lt;/a&gt;. While
values in Postgres that are exposed to users of Postgres are
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/postgres.h#L64"&gt;&lt;code&gt;Datum&lt;/code&gt;&lt;/a&gt;s.&lt;/p&gt;
&lt;p&gt;Each node has a type,
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/nodes.h#L26"&gt;&lt;code&gt;NodeTag&lt;/code&gt;&lt;/a&gt;,
that we can switch on to decide what to do. In contrast, &lt;code&gt;Datum&lt;/code&gt; has
no type. The type of the &lt;code&gt;Datum&lt;/code&gt; must be known by context before using
one of the transform functions like
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/postgres.h#L90"&gt;&lt;code&gt;DatumGetBool&lt;/code&gt;&lt;/a&gt;
to retrieve a C value from a &lt;code&gt;Datum&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A table is a &lt;code&gt;Node&lt;/code&gt;. A query plan is a &lt;code&gt;Node&lt;/code&gt;. A sequential scan is a
&lt;code&gt;Node&lt;/code&gt;. A join is a &lt;code&gt;Node&lt;/code&gt;. A literal in a query is a &lt;code&gt;Node&lt;/code&gt;. The
value for the literal in a query is a &lt;code&gt;Datum&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is how The Internals of PostgreSQL book
&lt;a href="https://www.interdb.jp/pg/pgsql03.html"&gt;visualizes&lt;/a&gt; a query plan for
example:&lt;/p&gt;
&lt;p&gt;&lt;img alt="https://www.interdb.jp/pg/img/fig-3-04.png" src="https://www.interdb.jp/pg/img/fig-3-04.png" /&gt;&lt;/p&gt;
&lt;p&gt;Every box in that image is a &lt;code&gt;Node&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And all &lt;code&gt;Node&lt;/code&gt;s in code I've seen share a common definition prefix
like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;SomeThing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// If the node is indeed abstract in the OOP sense.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;NodeTag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Many &lt;code&gt;Node&lt;/code&gt;s you'll see are abstract, like &lt;code&gt;Plan&lt;/code&gt;. But by printing out
&lt;code&gt;NodeTag&lt;/code&gt; and checking the value printed in
&lt;code&gt;src/include/nodes/nodetags.h&lt;/code&gt;, you can find the concrete type of the
&lt;code&gt;Node&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/include/nodes/nodetags.h&lt;/code&gt; is generated during a preprocessing
step. (Don't look if &lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/backend/nodes/gen_node_support.pl"&gt;regex in
Perl&lt;/a&gt;
worries you).&lt;/p&gt;
&lt;p&gt;We'll get back to &lt;code&gt;Node&lt;/code&gt;s later.&lt;/p&gt;
&lt;h3 id="what's-in-a-&amp;lt;code&amp;gt;querydesc&amp;lt;/code&amp;gt;?"&gt;What's in a &lt;code&gt;QueryDesc&lt;/code&gt;?&lt;/h3&gt;&lt;p&gt;Let's take a look at the
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/executor/execdesc.h#L33"&gt;&lt;code&gt;QueryDesc&lt;/code&gt;&lt;/a&gt;
struct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;QueryDesc&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* These fields are provided by CreateQueryDesc */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;CmdType&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* CMD_SELECT, CMD_UPDATE, etc. */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* planner's output (could be utility, too) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sourceText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* source text of the query */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* snapshot to use for query */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;crosscheck_snapshot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* crosscheck for RI update/delete */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;DestReceiver&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="cm"&gt;/* the destination for tuple output */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ParamListInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* param values being passed in */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;QueryEnvironment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;queryEnv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* query environment passed in */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;instrument_options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* OR of InstrumentOption flags */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* These fields are set by ExecutorStart */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;TupleDesc&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;tupDesc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* descriptor for result tuples */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;EState&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;estate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="cm"&gt;/* executor's query-wide state */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;PlanState&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;planstate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* tree of per-plan-node state */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* This field is set by ExecutorRun */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;already_executed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* true if previously executed */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* This is always set NULL by the core system, but plugins can change it */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Instrumentation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;totaltime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* total time spent in ExecutorRun */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/plannodes.h#L46"&gt;&lt;code&gt;PlannedStmt&lt;/code&gt;&lt;/a&gt;
field looks interesting. Let's take a look:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;PlannedStmt&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;no_equal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no_query_jumble&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NodeTag&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;CmdType&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;commandType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* select|insert|update|delete|merge|utility */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;queryId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* query identifier (copied from Query) */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;hasReturning&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* is it insert|update|delete RETURNING? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;hasModifyingCTE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* has insert|update|delete in WITH? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;canSetTag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* do I set the command result tag? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;transientPlan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* redo plan when TransactionXmin changes? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dependsOnRole&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* is plan specific to current role? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;parallelModeNeeded&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* parallel mode required to execute? */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;jitFlags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* which forms of JIT should be performed */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;planTree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* tree of Plan nodes */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="cm"&gt;/* list of RangeTblEntry nodes */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;permInfos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* list of RTEPermissionInfo nodes for rtable&lt;/span&gt;
&lt;span class="cm"&gt;                                 * entries needing one */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* rtable indexes of target relations for INSERT/UPDATE/DELETE/MERGE */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;resultRelations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* integer list of RT indexes, or NIL */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;appendRelations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* list of AppendRelInfo nodes */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;subplans&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* Plan trees for SubPlan expressions; note&lt;/span&gt;
&lt;span class="cm"&gt;                                 * that some could be NULL */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Bitmapset&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rewindPlanIDs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* indices of subplans that require REWIND */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rowMarks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* a list of PlanRowMark's */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;relationOids&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* OIDs of relations the plan depends on */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;invalItems&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* other dependencies, as PlanInvalItems */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;paramExecTypes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* type OIDs for PARAM_EXEC Params */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;utilityStmt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* non-null if this is utility stmt */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* statement location in source string (copied from Query) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;stmt_location&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* start location, or -1 if unknown */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;stmt_len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* length in bytes; 0 means &amp;quot;rest of string&amp;quot; */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;struct Plan* planTree&lt;/code&gt; field in there looks like what we'd want. But
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/plannodes.h#L119"&gt;&lt;code&gt;Plan&lt;/code&gt;&lt;/a&gt;
is abstract:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Plan&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no_equal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no_query_jumble&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NodeTag&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So let's try printing out the &lt;code&gt;planTree-&amp;gt;type&lt;/code&gt; field and find the
&lt;code&gt;Node&lt;/code&gt; it is concretely. In &lt;code&gt;pgexec.c&lt;/code&gt; change the definition of
&lt;code&gt;print_plan&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] HOOKED SUCCESSFULLY! %d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;planTree&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/postgres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--config-file&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/test-db/postgresql.conf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/test-db
&lt;span class="go"&gt;  -k $(pwd)/test-db&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.680 GMT [3215547] LOG:  starting PostgreSQL 17devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 13.2.1 20230728 (Red Hat 13.2.1-1), 64-bit&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on IPv6 address &amp;quot;::1&amp;quot;, port 5432&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on IPv4 address &amp;quot;127.0.0.1&amp;quot;, port 5432&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.681 GMT [3215547] LOG:  listening on Unix socket &amp;quot;/tmp/.s.PGSQL.5432&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.682 GMT [3215550] LOG:  database system was shut down at 2023-11-18 19:20:16 GMT&lt;/span&gt;
&lt;span class="go"&gt;2023-11-18 19:35:16.684 GMT [3215547] LOG:  database system is ready to accept connections&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in another window run &lt;code&gt;psql&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And check the logs from the &lt;code&gt;postgres&lt;/code&gt; process we just started and you
should notice:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 17:46:18.834 GMT [3242495] LOG:  [pgexec] HOOKED SUCCESSFULLY! 322&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 17:46:18.834 GMT [3242495] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So &lt;code&gt;322&lt;/code&gt; is the &lt;code&gt;NodeTag&lt;/code&gt; for the &lt;code&gt;Plan&lt;/code&gt;. If we look that up in
Postgres's &lt;code&gt;src/include/nodes/nodetags.h&lt;/code&gt; (remember, this is generated
after &lt;code&gt;./configure &amp;amp;&amp;amp; make&lt;/code&gt; so I can't link you to it):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;' = 322'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/include/nodes/nodetags.h
&lt;span class="go"&gt;        T_SeqScan = 322,&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hey, that makes sense! A &lt;code&gt;SELECT&lt;/code&gt; without any indexes definitely
sounds like a sequential scan!&lt;/p&gt;
&lt;h3 id="walking-a-sequential-scan"&gt;Walking a sequential scan&lt;/h3&gt;&lt;p&gt;Let's take a look at the
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/plannodes.h#L394"&gt;&lt;code&gt;SeqScan&lt;/code&gt;&lt;/a&gt;
struct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;SeqScan&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok, that's not very interesting. Let's look at
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/plannodes.h#L382"&gt;&lt;code&gt;Scan&lt;/code&gt;&lt;/a&gt;
then:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Scan&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;scanrelid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* relid is index into the range table */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's interesting! &lt;code&gt;scanrelid&lt;/code&gt; represents the table we're scanning. I
don't know what "range table" means exactly. But there was a field on
the &lt;code&gt;PlannedStmt&lt;/code&gt; called &lt;code&gt;rtable&lt;/code&gt; that seems relevant.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rtable&lt;/code&gt; was described as a
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/pg_list.h#L53"&gt;&lt;code&gt;List&lt;/code&gt;&lt;/a&gt;
of
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/parsenodes.h#L1019"&gt;&lt;code&gt;RangeTblEntry&lt;/code&gt;&lt;/a&gt;
nodes. And browsing around the file where &lt;code&gt;List&lt;/code&gt; is defined we can see
some nice methods for working with &lt;code&gt;List&lt;/code&gt;s, like &lt;code&gt;list_length()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's print out the &lt;code&gt;scanrelid&lt;/code&gt; and let's check out the length of the
&lt;code&gt;rtable&lt;/code&gt; and see if it's filled out. Let's also restrict our
&lt;code&gt;print_plan&lt;/code&gt; code to only look at &lt;code&gt;SeqScan&lt;/code&gt; nodes. In &lt;code&gt;pgexec.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;planTree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T_SeqScan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] Unsupported plan type.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] relid: %d, rtable length: %d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scanrelid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. (You can
find the instructions for this above if you've forgotten.) Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. And check the Postgres server logs. You should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 18:00:34.184 GMT [3244438] LOG:  [pgexec] relid: 1, rtable length: 1&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 18:00:34.184 GMT [3244438] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Awesome! So &lt;code&gt;rtable&lt;/code&gt; does have data in it. There's only one table in
this query so its length makes sense to be &lt;code&gt;1&lt;/code&gt;. The &lt;code&gt;scanrelid&lt;/code&gt; being
&lt;code&gt;1&lt;/code&gt; also though is weird. Let's fetch the nth value from the &lt;code&gt;rtable&lt;/code&gt;
list using &lt;code&gt;scanrelid-1&lt;/code&gt; as the index.&lt;/p&gt;
&lt;p&gt;For the
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/parsenodes.h#L1019"&gt;&lt;code&gt;RangeTblEntry&lt;/code&gt;&lt;/a&gt;
itself, let's take a look:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTEKind&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_RELATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="cm"&gt;/* ordinary relation reference */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_SUBQUERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="cm"&gt;/* subquery in FROM */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_JOIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="cm"&gt;/* join */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_FUNCTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="cm"&gt;/* function in FROM */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_TABLEFUNC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="cm"&gt;/* TableFunc(.., column list) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_VALUES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="cm"&gt;/* VALUES (&amp;lt;exprlist&amp;gt;), (&amp;lt;exprlist&amp;gt;), ... */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_CTE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="cm"&gt;/* common table expr (WITH list element) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_NAMEDTUPLESTORE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* tuplestore, e.g. for AFTER triggers */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTE_RESULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="cm"&gt;/* RTE represents an empty FROM clause; such&lt;/span&gt;
&lt;span class="cm"&gt;                                 * RTEs are added by the planner, they're not&lt;/span&gt;
&lt;span class="cm"&gt;                                 * present during parsing or rewriting */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTEKind&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;RangeTblEntry&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_read_write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;custom_query_jumble&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NodeTag&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RTEKind&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* see above */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * XXX the fields applicable to only some rte kinds should be merged into&lt;/span&gt;
&lt;span class="cm"&gt;     * a union.  I didn't do this yet because the diffs would impact a lot of&lt;/span&gt;
&lt;span class="cm"&gt;     * code that is being actively worked on.  FIXME someday.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * Fields valid for a plain relation RTE (else zero):&lt;/span&gt;
&lt;span class="cm"&gt;     *&lt;/span&gt;
&lt;span class="cm"&gt;     * rellockmode is really LOCKMODE, but it's declared int to avoid having&lt;/span&gt;
&lt;span class="cm"&gt;     * to include lock-related headers here.  It must be RowExclusiveLock if&lt;/span&gt;
&lt;span class="cm"&gt;     * the RTE is an INSERT/UPDATE/DELETE/MERGE target, else RowShareLock if&lt;/span&gt;
&lt;span class="cm"&gt;     * the RTE is a SELECT FOR UPDATE/FOR SHARE target, else AccessShareLock.&lt;/span&gt;
&lt;span class="cm"&gt;     *&lt;/span&gt;
&lt;span class="cm"&gt;     * Note: in some cases, rule expansion may result in RTEs that are marked&lt;/span&gt;
&lt;span class="cm"&gt;     * with RowExclusiveLock even though they are not the target of the&lt;/span&gt;
&lt;span class="cm"&gt;     * current query; this happens if a DO ALSO rule simply scans the original&lt;/span&gt;
&lt;span class="cm"&gt;     * target table.  We leave such RTEs with their original lockmode so as to&lt;/span&gt;
&lt;span class="cm"&gt;     * avoid getting an additional, lesser lock.&lt;/span&gt;
&lt;span class="cm"&gt;     *&lt;/span&gt;
&lt;span class="cm"&gt;     * perminfoindex is 1-based index of the RTEPermissionInfo belonging to&lt;/span&gt;
&lt;span class="cm"&gt;     * this RTE in the containing struct's list of same; 0 if permissions need&lt;/span&gt;
&lt;span class="cm"&gt;     * not be checked for this RTE.&lt;/span&gt;
&lt;span class="cm"&gt;     *&lt;/span&gt;
&lt;span class="cm"&gt;     * As a special case, relid, relkind, rellockmode, and perminfoindex can&lt;/span&gt;
&lt;span class="cm"&gt;     * also be set (nonzero) in an RTE_SUBQUERY RTE.  This occurs when we&lt;/span&gt;
&lt;span class="cm"&gt;     * convert an RTE_RELATION RTE naming a view into an RTE_SUBQUERY&lt;/span&gt;
&lt;span class="cm"&gt;     * containing the view's query.  We still need to perform run-time locking&lt;/span&gt;
&lt;span class="cm"&gt;     * and permission checks on the view, even though it's not directly used&lt;/span&gt;
&lt;span class="cm"&gt;     * in the query anymore, and the most expedient way to do that is to&lt;/span&gt;
&lt;span class="cm"&gt;     * retain these fields from the old state of the RTE.&lt;/span&gt;
&lt;span class="cm"&gt;     *&lt;/span&gt;
&lt;span class="cm"&gt;     * As a special case, RTE_NAMEDTUPLESTORE can also set relid to indicate&lt;/span&gt;
&lt;span class="cm"&gt;     * that the tuple format of the tuplestore is the same as the referenced&lt;/span&gt;
&lt;span class="cm"&gt;     * relation.  This allows plans referencing AFTER trigger transition&lt;/span&gt;
&lt;span class="cm"&gt;     * tables to be invalidated if the underlying table is altered.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="cm"&gt;/* OID of the relation */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* relation kind (see pg_class.relkind) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;rellockmode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* lock level that query requires on the rel */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;TableSampleClause&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tablesample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="cm"&gt;/* sampling info, or NULL */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;perminfoindex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In &lt;code&gt;SELECT a FROM x&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt; should be a plain relation RTE (to use the
terminology there). So we can add a guard that validates that. But we
don't get a &lt;code&gt;Relation&lt;/code&gt;. (You might remember from my &lt;a href="https://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html"&gt;previous
post&lt;/a&gt;
that &lt;code&gt;Relation&lt;/code&gt; is where we can finally see the table name.)&lt;/p&gt;
&lt;p&gt;We get an &lt;code&gt;Oid&lt;/code&gt; for the &lt;code&gt;Relation&lt;/code&gt;. So we need to find a way to lookup
a &lt;code&gt;Relation&lt;/code&gt; from an &lt;code&gt;Oid&lt;/code&gt;. And by grepping around in Postgres (or via
judicious use of ChatGPT, I confess), we can notice
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/backend/utils/cache/relcache.c#L2056"&gt;&lt;code&gt;RelationIdGetRelation&lt;/code&gt;&lt;/a&gt;
that takes an &lt;code&gt;Oid&lt;/code&gt; and returns a &lt;code&gt;Relation&lt;/code&gt;. Notice also that the
comment says we should close the relation when we're done with
&lt;code&gt;RelationClose&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So putting it altogether (and again, reusing some code from that
previous post), we can print out the table name.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RangeTblEntry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;planTree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T_SeqScan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] Unsupported plan type.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scanrelid&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTE_RELATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] Unsupported FROM type: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RelationIdGetRelation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NameStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rd_rel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] SELECT [todo] FROM %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RelationClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You'll also need to add a new &lt;code&gt;#include&lt;/code&gt; for
&lt;code&gt;utils/rel.h&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 18:36:03.986 GMT [3246777] LOG:  [pgexec] SELECT [todo] FROM x&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 18:36:03.986 GMT [3246777] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Fantastic! Before we get into walking the &lt;code&gt;SELECT&lt;/code&gt; columns and the
(optional) &lt;code&gt;WHERE&lt;/code&gt; clause, let's do some quick refactoring.&lt;/p&gt;
&lt;h3 id="a-string-builder"&gt;A string builder&lt;/h3&gt;&lt;p&gt;Let's add a little string builder library so we can emit a single
string we build up to a single &lt;code&gt;elog()&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;I wrote this ahead of time and won't explain it here since the details
aren't relevant.&lt;/p&gt;
&lt;p&gt;Just copy this and paste near the top of &lt;code&gt;pgexec.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_resize_to_fit_additional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;additional&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newsize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;additional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;additional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;newsize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;additional&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newsize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newsize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_resize_to_fit_additional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;restrict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__attribute__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gnu_printf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;restrict&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// First figure out how long the result will be.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;va_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;va_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vsnprintf&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// TODO: error handling.&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Resize to fit result.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_resize_to_fit_additional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Actually do the printf into buf.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;va_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;va_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vsprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// TODO: error handling.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;va_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arglist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_cstring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev_offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Offset should stay the same. This is a fake NULL.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev_offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next we'll modify &lt;code&gt;print_plan()&lt;/code&gt; in &lt;code&gt;pgexec.c&lt;/code&gt; to use it, and add stubs
for printing the &lt;code&gt;SELECT&lt;/code&gt; columns and &lt;code&gt;WHERE&lt;/code&gt; clauses.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; [where todo]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_select_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[columns todo]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RangeTblEntry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;planTree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T_SeqScan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] Unsupported plan type.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SeqScan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scanrelid&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTE_RELATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] Unsupported FROM type: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RelationIdGetRelation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NameStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rd_rel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SELECT &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_select_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; FROM %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[pgexec] %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buffer_cstring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RelationClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we just need to implement the &lt;code&gt;buffer_print_where&lt;/code&gt; and
&lt;code&gt;buffer_print_select_columns&lt;/code&gt; functions and our walking infrastructure
will be done! For now. :)&lt;/p&gt;
&lt;h3 id="walking-the-&amp;lt;code&amp;gt;where&amp;lt;/code&amp;gt;-clause"&gt;Walking the &lt;code&gt;WHERE&lt;/code&gt; clause&lt;/h3&gt;&lt;p&gt;If you remember back to the &lt;code&gt;SeqScan&lt;/code&gt; and &lt;code&gt;Scan&lt;/code&gt; nodes, they were both
basically empty. They had a &lt;code&gt;Plan&lt;/code&gt; and a &lt;code&gt;scanrelid&lt;/code&gt;. So the rest of
the &lt;code&gt;SELECT&lt;/code&gt; info must be in the &lt;code&gt;Plan&lt;/code&gt; since it wasn't in the &lt;code&gt;Scan&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's look at
&lt;a href="https://github.com/postgres/postgres/blob/master/src/include/nodes/plannodes.h#L119"&gt;&lt;code&gt;Plan&lt;/code&gt;&lt;/a&gt;
again. One part that stands out is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * Common structural data for all Plan types.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;plan_node_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* unique across entire final plan tree */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;targetlist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* target list to be computed at this node */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="cm"&gt;/* implicitly-ANDed qual conditions */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lefttree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* input plan tree(s) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;righttree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;initPlan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="cm"&gt;/* Init Plan nodes (un-correlated expr&lt;/span&gt;
&lt;span class="cm"&gt;                                 * subselects) */&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;qual&lt;/code&gt; kinda looks like a &lt;code&gt;WHERE&lt;/code&gt; clause. (And &lt;code&gt;targetlist&lt;/code&gt; kinda
looks like the columns the &lt;code&gt;SELECT&lt;/code&gt; pulls).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/pg_list.h#L53"&gt;&lt;code&gt;List&lt;/code&gt;&lt;/a&gt;s
just contain void pointers, so we can't tell what the type of &lt;code&gt;qual&lt;/code&gt;
or &lt;code&gt;targetlist&lt;/code&gt; children are. But I'm going to make a wild guess they
are &lt;code&gt;Node&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;There's even a nice helper that casts void pointers to &lt;code&gt;Node*&lt;/code&gt; and
pulls out the type,
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/nodes.h#L133"&gt;&lt;code&gt;nodeTag()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And reading around &lt;code&gt;pg_list.h&lt;/code&gt; shows some interesting helper utilities
like
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/pg_list.h#L373"&gt;&lt;code&gt;foreach&lt;/code&gt;&lt;/a&gt;
that we can use to iterate the list.&lt;/p&gt;
&lt;p&gt;Let's try printing out the type of &lt;code&gt;qual&lt;/code&gt;'s members.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ListCell&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; WHERE &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; AND &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[node: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  Notice any &lt;a href="https://twitter.com/eatonphil/status/1726265982094819631"&gt;vestiges
  of LISP&lt;/a&gt;?
&lt;/p&gt;&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 19:17:00.879 GMT [3250850] LOG:  [pgexec] SELECT [columns todo] FROM x WHERE [node: 15]&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 19:17:00.879 GMT [3250850] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Well, our code didn't crash! So the guess about &lt;code&gt;qual&lt;/code&gt; &lt;code&gt;List&lt;/code&gt; entries
being &lt;code&gt;Node&lt;/code&gt;s seems right. Let's look up that node type in the
Postgres repo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;' = 15,'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/include/nodes/nodetags.h
&lt;span class="go"&gt;        T_OpExpr = 15,&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Woot! That is exactly what I'd expect the &lt;code&gt;WHERE&lt;/code&gt; clause here to be.&lt;/p&gt;
&lt;p&gt;Now that we know &lt;code&gt;qual&lt;/code&gt; is a &lt;code&gt;List&lt;/code&gt; of &lt;code&gt;Node&lt;/code&gt;s, let's do a bit of
refactoring since &lt;code&gt;targetlist&lt;/code&gt; will probably also be a &lt;code&gt;List&lt;/code&gt; of
&lt;code&gt;Node&lt;/code&gt;s. Back in &lt;code&gt;pgexec.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[opexpr: todo]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_OpExpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ListCell&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; WHERE &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; AND &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And let's check out &lt;code&gt;OpExpr&lt;/code&gt;!&lt;/p&gt;
&lt;h3 id="walking-&amp;lt;code&amp;gt;opexpr&amp;lt;/code&amp;gt;"&gt;Walking &lt;code&gt;OpExpr&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Take a look at the definition of
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/primnodes.h#L748"&gt;&lt;code&gt;OpExpr&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;OpExpr&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Expr&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;xpr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* PG_OPERATOR OID of the operator */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;opno&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* PG_PROC OID of underlying function */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;opfuncid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;equal_ignore_if_zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* PG_TYPE OID of result value */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;opresulttype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* true if operator returns set */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;opretset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* OID of collation of result */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;opcollid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* OID of collation that operator should use */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;inputcollid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* arguments to the operator (1 or 2) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* token location, or -1 if unknown */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The important fields are &lt;code&gt;opno&lt;/code&gt;, the &lt;code&gt;Oid&lt;/code&gt; of the operator, and
&lt;code&gt;args&lt;/code&gt;. &lt;code&gt;args&lt;/code&gt; looks like another &lt;code&gt;List&lt;/code&gt; of &lt;code&gt;Node&lt;/code&gt;s so we already know
how to handle that.&lt;/p&gt;
&lt;p&gt;But how do we find the string name of the operator? Presumably there's
infrastructure like &lt;code&gt;RelationIdGetRelation&lt;/code&gt; that takes an &lt;code&gt;Oid&lt;/code&gt; and
gets us an operator object.&lt;/p&gt;
&lt;p&gt;Well I got stuck here as well. Again, thankfully, ChatGPT gave me some
suggestions. There's no great story for how I got it working. So here's
&lt;code&gt;buffer_print_opexpr&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_op&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;HeapTuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SearchSysCache1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OPEROID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ObjectIdGetDatum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;opno&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_nth_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HeapTupleIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Form_pg_operator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Form_pg_operator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;GETSTRUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; %s &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NameStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;oprname&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ReleaseSysCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown operation: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;opno&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// TODO: Support single operand operations.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_nth_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;/pre&gt;&lt;/div&gt;
&lt;p&gt;And add the following two includes to the top of &lt;code&gt;pgexec.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;catalog/pg_operator.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;utils/syscache.h&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 19:42:52.916 GMT [3252974] LOG:  [pgexec] SELECT [columns todo] FROM x WHERE [Unknown: 6] &amp;gt; [Unknown: 7]&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 19:42:52.916 GMT [3252974] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we continue to make progress! Let's look up the type of these two
unknown nodes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;' = 6,'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/include/nodes/nodetags.h
&lt;span class="go"&gt;        T_Var = 6,&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;' = 7,'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/include/nodes/nodetags.h
&lt;span class="go"&gt;        T_Const = 7,&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's deal with &lt;code&gt;Const&lt;/code&gt; first.&lt;/p&gt;
&lt;h3 id="walking-&amp;lt;code&amp;gt;const&amp;lt;/code&amp;gt;"&gt;Walking &lt;code&gt;Const&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;If we take a look at the
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/primnodes.h#L292"&gt;&lt;code&gt;Const&lt;/code&gt;&lt;/a&gt;
definition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Const&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_copy_equal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;custom_read_write&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Expr&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;xpr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* pg_type OID of the constant's datatype */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;consttype&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* typmod value, if any */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;consttypmod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* OID of collation, or InvalidOid if none */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;constcollid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* typlen of the constant's datatype */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;constlen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* the constant's value */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Datum&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;constvalue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* whether the constant is null (if true, constvalue is undefined) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;constisnull&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * Whether this datatype is passed by value.  If true, then all the&lt;/span&gt;
&lt;span class="cm"&gt;     * information is stored in the Datum.  If false, then the Datum contains&lt;/span&gt;
&lt;span class="cm"&gt;     * a pointer to the information.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;constbyval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * token location, or -1 if unknown.  All constants are tracked as&lt;/span&gt;
&lt;span class="cm"&gt;     * locations in query jumbling, to be marked as parameters.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_location&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It looks like we need to switch on the &lt;code&gt;consttype&lt;/code&gt; (an &lt;code&gt;Oid&lt;/code&gt;) to
figure out how to interpret the &lt;code&gt;constvalue&lt;/code&gt; (a &lt;code&gt;Datum&lt;/code&gt;). Remember I
mentioned earlier that how to interpret a &lt;code&gt;Datum&lt;/code&gt; is dependent on
context. &lt;code&gt;consttype&lt;/code&gt; is the context here.&lt;/p&gt;
&lt;p&gt;In this case, although &lt;code&gt;consttype&lt;/code&gt; is an &lt;code&gt;Oid&lt;/code&gt; and we had to use
Postgres infrastructure to look up the &lt;code&gt;Oid&lt;/code&gt;'s corresponding object,
there are some builtin types and the literals we've queried with are
among them.&lt;/p&gt;
&lt;p&gt;We can simply check if &lt;code&gt;consttype == INT4OID&lt;/code&gt; and the interpret the
&lt;code&gt;Datum&lt;/code&gt; as an &lt;code&gt;int32&lt;/code&gt; if so. &lt;code&gt;DatumGetInt32&lt;/code&gt; will get us that &lt;code&gt;int32&lt;/code&gt;
in that case.&lt;/p&gt;
&lt;p&gt;To support the new &lt;code&gt;Const&lt;/code&gt; type, we'll add a case in
&lt;code&gt;buffer_print_expr&lt;/code&gt; to look for a &lt;code&gt;T_Const&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_Const&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_OpExpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And add a new function, &lt;code&gt;buffer_print_const&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;consttype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;INT4OID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DatumGetInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;constvalue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown consttype oid: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;consttype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 19:53:47.922 GMT [3253746] LOG:  [pgexec] SELECT [columns todo] FROM x WHERE [Unknown: 6] &amp;gt; 1&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 19:53:47.922 GMT [3253746] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Great! Now we just have to tackle &lt;code&gt;T_Var&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="walking-&amp;lt;code&amp;gt;var&amp;lt;/code&amp;gt;"&gt;Walking &lt;code&gt;Var&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Let's take a look at the definition of &lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/primnodes.h#L233"&gt;&lt;code&gt;Var&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Var&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Expr&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;xpr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * index of this var's relation in the range table, or&lt;/span&gt;
&lt;span class="cm"&gt;     * INNER_VAR/OUTER_VAR/etc&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;varno&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * attribute number of this var, or zero for all attrs (&amp;quot;whole-row Var&amp;quot;)&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AttrNumber&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;varattno&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* pg_type OID for the type of this var */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;vartype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* pg_attribute typmod value */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;vartypmod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* OID of collation, or InvalidOid if none */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;varcollid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * RT indexes of outer joins that can replace the Var's value with null.&lt;/span&gt;
&lt;span class="cm"&gt;     * We can omit varnullingrels in the query jumble, because it's fully&lt;/span&gt;
&lt;span class="cm"&gt;     * determined by varno/varlevelsup plus the Var's query location.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Bitmapset&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;varnullingrels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * for subquery variables referencing outer relations; 0 in a normal var,&lt;/span&gt;
&lt;span class="cm"&gt;     * &amp;gt;0 means N levels up&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;varlevelsup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * varnosyn/varattnosyn are ignored for equality, because Vars with&lt;/span&gt;
&lt;span class="cm"&gt;     * different syntactic identifiers are semantically the same as long as&lt;/span&gt;
&lt;span class="cm"&gt;     * their varno/varattno match.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* syntactic relation index (0 if unknown) */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;varnosyn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;equal_ignore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* syntactic attribute number */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AttrNumber&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;varattnosyn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_node_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;equal_ignore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;query_jumble_ignore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* token location, or -1 if unknown */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Var&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It looks like this refers to a relation in the range table list
again. So this means we need to have access to the full &lt;code&gt;PlannedStmt&lt;/code&gt;
so we can read its &lt;code&gt;rtable&lt;/code&gt; field again to find the table. Then we
need to look up the &lt;code&gt;Relation&lt;/code&gt; for the table and then we can use the
&lt;code&gt;Var&lt;/code&gt;'s &lt;code&gt;varattno&lt;/code&gt; field to pick the nth attribute from the relation
and get its string representation.&lt;/p&gt;
&lt;p&gt;However, ChatGPT found a slightly higher-level function:
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/backend/utils/cache/lsyscache.c#L826"&gt;&lt;code&gt;get_attname()&lt;/code&gt;&lt;/a&gt;
that takes a relation &lt;code&gt;Oid&lt;/code&gt; and an attribute index and returns the
string name of the column.&lt;/p&gt;
&lt;p&gt;So here's what &lt;code&gt;buffer_print_var&lt;/code&gt; looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Var&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RangeTblEntry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;varno&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTE_RELATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unsupported relation type for var: %d].&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_attname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;varattno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;pfree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You'll also need to add another &lt;code&gt;#include&lt;/code&gt; for &lt;code&gt;utils/lsyscache.h&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's add the &lt;code&gt;case T_Var:&lt;/code&gt; check in &lt;code&gt;buffer_print_expr&lt;/code&gt;, and also
feed the &lt;code&gt;PlannedStmt*&lt;/code&gt; through all the necessary &lt;code&gt;buffer_print_X&lt;/code&gt;
functions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;HeapTuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SearchSysCache1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OPEROID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ObjectIdGetDatum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;opno&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_nth_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HeapTupleIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Form_pg_operator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Form_pg_operator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;GETSTRUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; %s &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NameStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;oprname&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ReleaseSysCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opertup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown operation: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;opno&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// TODO: Support single operand operations.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_nth_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;consttype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;INT4OID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DatumGetInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;constvalue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown consttype oid: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;consttype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Var&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;RangeTblEntry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list_nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;varno&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTE_RELATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;elog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unsupported relation type for var: %d].&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtekind&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_attname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rte&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;varattno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;pfree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_Const&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_Var&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Var&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_OpExpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ListCell&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;lfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_appendz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; WHERE &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;qual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; AND &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 20:03:14.351 GMT [3254458] LOG:  [pgexec] SELECT [columns todo] FROM x WHERE a &amp;gt; 1&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:03:14.351 GMT [3254458] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Huzzah!&lt;/p&gt;
&lt;h3 id="walking-the-column-list"&gt;Walking the column list&lt;/h3&gt;&lt;p&gt;Let's get rid of &lt;code&gt;[columns todo]&lt;/code&gt;. We already had the idea that &lt;code&gt;List*
targetlist&lt;/code&gt; on the &lt;code&gt;Plan&lt;/code&gt; struct was a list of expression
&lt;code&gt;Node&lt;/code&gt;s. Let's try it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_select_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QueryDesc&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Plan&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;targetlist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;buffer_print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryDesc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plannedstmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;targetlist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 20:12:48.091 GMT [3255398] LOG:  [pgexec] SELECT [Unknown: 53] FROM x WHERE a &amp;gt; 1&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:12:48.091 GMT [3255398] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hmm. Let's look up &lt;code&gt;Node&lt;/code&gt; &lt;code&gt;53&lt;/code&gt; in Postgres:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;' = 53,'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/include/nodes/nodetags.h
&lt;span class="go"&gt;        T_TargetEntry = 53,&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Based on the definition of
&lt;a href="https://github.com/postgres/postgres/blob/b218fbb7a35fcf31539bfad12732038fe082a2eb/src/include/nodes/primnodes.h#L1918"&gt;&lt;code&gt;TargetEntry&lt;/code&gt;&lt;/a&gt;,
it looks like we can ignore most of the fields (because we don't need
to handle &lt;code&gt;SELECT a AS b&lt;/code&gt; yet) and just proxy the child &lt;code&gt;expr&lt;/code&gt; field.&lt;/p&gt;
&lt;p&gt;Let's add a &lt;code&gt;case T_TargetEntry&lt;/code&gt; to &lt;code&gt;buffer_print_expr&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PGExec_Buffer&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PlannedStmt&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_Const&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_Var&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Var&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_TargetEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;TargetEntry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;T_OpExpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_print_opexpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpExpr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;buffer_appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[Unknown: %d]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild and reinstall the extension, and restart Postgres. Re-run the
&lt;code&gt;test.sql&lt;/code&gt; script. Check the Postgres server logs and:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 20:17:51.114 GMT [3257827] LOG:  [pgexec] SELECT a FROM x WHERE a &amp;gt; 1&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:17:51.114 GMT [3257827] STATEMENT:  SELECT a FROM x WHERE a &amp;gt; 1;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We did it!&lt;/p&gt;
&lt;h3 id="variations"&gt;Variations&lt;/h3&gt;&lt;p&gt;Let's try out some other queries to make sure this wasn't just luck.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'SELECT a + 1 FROM x'&lt;/span&gt;
&lt;span class="go"&gt; ?column?&lt;/span&gt;
&lt;span class="go"&gt;----------&lt;/span&gt;
&lt;span class="go"&gt;      310&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;

&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'SELECT a + 1 FROM x WHERE 2 &amp;gt; a'&lt;/span&gt;
&lt;span class="go"&gt; ?column?&lt;/span&gt;
&lt;span class="go"&gt;----------&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(0 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And back in the Postgres server logs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;2023-11-19 20:19:28.057 GMT [3257874] LOG:  [pgexec] SELECT a + 1 FROM x&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:19:28.057 GMT [3257874] STATEMENT:  SELECT a + 1 FROM x&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:19:30.474 GMT [3257878] LOG:  [pgexec] SELECT a + 1 FROM x WHERE 2 &amp;gt; a&lt;/span&gt;
&lt;span class="go"&gt;2023-11-19 20:19:30.474 GMT [3257878] STATEMENT:  SELECT a + 1 FROM x WHERE 2 &amp;gt; a&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not bad!&lt;/p&gt;
&lt;h3 id="next-steps"&gt;Next steps&lt;/h3&gt;&lt;p&gt;Printing out the statement here isn't incredibly useful. But it
establishes a basis for future work that might avoid Postgres's query
execution engine and do the execution ourselves, or to proxy execution
to another system.&lt;/p&gt;
&lt;h3 id="postscript:-on-chatgpt"&gt;Postscript: On ChatGPT&lt;/h3&gt;&lt;p&gt;My recent Postgres explorations would have been basically impossible
if it weren't for being able to ask ChatGPT simple, stupid questions
like "How do I get from a Postgres &lt;code&gt;Var&lt;/code&gt; to a column name".&lt;/p&gt;
&lt;p&gt;It isn't always right. It doesn't always give great code. Actually, it
normally gives pretty weird code. But it's been extremely useful for
quick iteration when I get stuck.&lt;/p&gt;
&lt;p&gt;The only other place the information exists is in small blog posts
around the internet, the Postgres mailing lists (that so far for me
hasn't been super responsive), and the code itself.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I've been on a Postgres roll. Let's dig into interpreting a Postgres query plan in preparation for future work on completely diverting the flow of Postgres query execution using execution hooks!&lt;a href="https://t.co/EZrgoIiTuX"&gt;https://t.co/EZrgoIiTuX&lt;/a&gt; &lt;a href="https://t.co/7S6d6olPX8"&gt;pic.twitter.com/7S6d6olPX8&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1726336428626587710?ref_src=twsrc%5Etfw"&gt;November 19, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sun, 19 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-11-19-exploring-a-postgres-query-plan.html</guid></item><item><title>Github CI</title><link>http://pxtl.ca/2023/11/17/github-ci/</link><description>&lt;p&gt;Well, it took longer than I would've liked but I got Github CI working with this
blog! Finally learned how to use Github workflows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Pxtl/Sandra.Snow/tree/master/.github/workflows"&gt;Workflow files are
here&lt;/a&gt;&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Fri, 17 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/11/17/github-ci/</guid></item><item><title>sum-grid.love</title><link>http://akkartik.name/post/sum-grid</link><description>&lt;p&gt;
&lt;a href="https://git.sr.ht/~akkartik/sum-grid.love"&gt;A little sudoku-like app
for helping first-graders practice addition.&lt;/a&gt; This attempt at situated
software for schooling got a little more use than &lt;a href="https://akkartik.name/post/spell-cards"&gt;spell-cards.love&lt;/a&gt;.

&lt;p&gt;
&lt;a href="https://akkartik.name/images/20231116-sum-grid-fixed.webm"&gt;
  &lt;video style="width: 80%; margin-left: 10%; border: 2px solid #aaaaff;"&gt;
    &lt;source src="https://akkartik.name/images/20231116-sum-grid-fixed.webm" /&gt;
  &lt;/video&gt;
&lt;/a&gt;
&lt;div class="btw" style="text-align: right;"&gt;
  video; 25 seconds
&lt;/div&gt;</description><author>Kartik Agaram</author><pubDate>Thu, 16 Nov 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/sum-grid</guid></item><item><title>rustcred, a git credentials helper</title><link>https://rjp.is/blogging/posts/2023/11/rustcred/</link><description>In which we store some credentials with example code.</description><author>infrequent oscillations</author><pubDate>Wed, 15 Nov 2023 15:54:31 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/11/rustcred/</guid></item><item><title>CHAOS;HEAD NOAH review</title><link>https://burakku.com/blog/chaos-head-noah-review/</link><description>&lt;p&gt;&lt;img alt="CHAOS;HEAD NOAH" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Press any key for schizophrenia.&lt;/p&gt;
&lt;p&gt;CHAOS;HEAD is a really interesting story. On the surface it appears to be a murder mystery story but it's not really that. There are murders and mysteries, but they don't meet in the same way that they would in a whodunit. I guess I'd rather call it a psychological horror, even if it's not really outright horror. And much like STEINS;GATE, the entry in the series that people are probably the most familiar with, it is rife with conspiracies and larger implications. Really enjoyed myself as I read through it for the first ending.&lt;/p&gt;
&lt;p&gt;The meat of the story is really in the common route, which then unlocks the other routes and endings after having completed it once. The character-specific routes aren't that special even if they do flesh out some of the characters a bit more and have some good elements. The most outstanding one of them is the Rimi route, which gives the most exposition for the common route, although I did also enjoy the Sena one too as a Sena fan. The endings also felt a bit lacking compared to the emotions I felt from the common route. Even the true ending didn't really change things up that much or be more detailed than the base ending. Thankfully the common route was interesting so I don't feel soured by these issues.&lt;/p&gt;
&lt;p&gt;The protagonist Takumi is a flawed but interesting character. An anti-social, delusional otaku hiding from the world in his shipping container home. And because of those delusions, he becomes an unreliable narrator for the story, making the reader unsure what parts of the story are real and what are delusions. He enters a delusion, then something unexpected happens and you can't know if it's still a delusion or not. Or he encounters a stranger that supposedly knows him and you can't know which is true. The unreliability of his delusions really fits the whole mysterious and disturbing theme of the story very well.&lt;/p&gt;
&lt;p&gt;The delusion triggers are pretty interesting system that allow having a positive delusion, a negative delusion, or no delusion. The delusions will then trigger either a small scene with no relevance to the plot, or change the actual routing (at least if you answer a quiz correctly). While the game does have an explanation as to why this would happen, it's still pretty hard to figure out why having a negative delusion in a given scenario would result in a different ending. Not quite as opaque of a routing system as the text message system in STEINS;GATE was, but still mixed up a bit. You'll probably want to look up a route guide for this one.&lt;/p&gt;
&lt;p&gt;Art in CHAOS;HEAD is just about alright. Some of the character sprites and CG images have a derpy feeling to them, with the characters looking like their facial features are trying to escape their faces. I didn't find the art fundamentally flawed but it did definitely stick out, even in some quite pivotal parts of the story. The characters also have animated mouths but there's not much animation beyond that and some camera pans. And while the CG did have some quality issues, the amount of it was just fine. I guess this isn't really the most visual of packages.&lt;/p&gt;
&lt;p&gt;But what was actually superb was the sound design. The sound design in this game is fantastic. Takumi is fully voiced and Yoshino does a superb job at voicing his uncertainties, fears, delusions, paranoia and other ailments. Maybe his performance was actually too good, since it overshadowed some of the side characters, even though there were also good performances there. Like for example Takahashi Chiaki's performance as Yua. The music and sound effects are also to the point when it comes to producing a creepy, supernatural and mysterious atmosphere. There's very few times when I'm impressed to this point by just the sound design in a visual novel. You'll definitely want to have audio on for this experience. Bonus points for an excellent insert song.&lt;/p&gt;
&lt;p&gt;You’ll also want to have audio on because it’s very hard to figure out who’s talking without it. The user interface has no indication on who the speaker at the moment is, or if the text is just narration. If you’re deaf, you can go get fucked as far as CHAOS;HEAD is concerned. The interface is also kinda ugly in general and the text display is kinda rough. This is probably a function of the game’s age, as it’s already 15 years old. Not a dealbreaker but something I did definitely note during it.&lt;/p&gt;
&lt;p&gt;For what it's worth, installing the Committee of Zero patch does clean up the text display so maybe consider doing that.&lt;/p&gt;
&lt;p&gt;Whilst it's largely overshadowed by its excellent younger sibling STEINS;GATE, CHAOS;HEAD is still an interesting story and recommended reading despite some of its flaws.&lt;/p&gt;
&lt;p&gt;Maybe I should now check why everyone hates the anime adaptation…&lt;/p&gt;</description><author>ブラック</author><pubDate>Wed, 15 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/chaos-head-noah-review/</guid></item><item><title>Fun with Makefiles - Dynamic Menu Generation</title><link>https://smcleod.net/2023/11/fun-with-makefiles-dynamic-menu-generation/</link><description>This reusable snippet will generate a menu of targets from the Makefile. It will use `fzf` if it is installed, otherwise it will use a numbered menu.</description><author>smcleod.net</author><pubDate>Tue, 14 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/11/fun-with-makefiles-dynamic-menu-generation/</guid></item><item><title>MBA Washing</title><link>https://smcleod.net/2023/11/mba-washing/</link><description>"MBA Washing" refers to the phenomenon where individuals or companies, often with a strong theoretical or academic background but limited recent practical experience, adopt and reinterpret industry-specific terminology and cultural movements.</description><author>smcleod.net</author><pubDate>Mon, 13 Nov 2023 11:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/11/mba-washing/</guid></item><item><title>Turning a project into a product</title><link>https://sschueller.github.io/posts/turning-a-project-into-a-product/</link><description>&lt;div class="featured-image"&gt;
                &lt;img src="/posts/turning-a-project-into-a-product/Display26-small.jpg" /&gt;
            &lt;/div&gt;&lt;p&gt;&lt;a href="https://www.foto-press.ch/" rel="noopener noreferrer" target="_blank"&gt;Foto by Foto-Press&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A step by step account of how I turned a &lt;a href="/posts/vbz-fahrgastinformation/" rel=""&gt;project&lt;/a&gt; of mine into a polished product that will be available for purchase over at &lt;a href="https://www.stationdisplay.com/" rel="noopener noreferrer" target="_blank"&gt;stationdisplay.com&lt;/a&gt;.&lt;/p&gt;</description><author>Stefan Schüller</author><pubDate>Sat, 11 Nov 2023 10:37:16 GMT</pubDate><guid isPermaLink="true">https://sschueller.github.io/posts/turning-a-project-into-a-product/</guid></item><item><title>GopherConSyd 2023 - All slides, notes and links</title><link>https://boyter.org/posts/gopherconsyd-2023/</link><description>&lt;p&gt;Welcome. If you are reading this you either follow me on RSS, watch my blog or were directed here at the start of my GopherConSyd 2023 talk about &amp;ldquo;Bloom Filters: Building a Cutting Edge Go Search Engine to Explore the World&amp;rsquo;s Source Code&amp;rdquo;. Whatever the case may be, welcome!&lt;/p&gt;
&lt;p&gt;For the slides visit &lt;a href="https://boyter.org/static/gophercon-syd-presentation-2023/"&gt;https://boyter.org/static/gophercon-syd-presentation-2023/&lt;/a&gt; then just use the space bar to advance, the arrow keys to move around or the ESC key to see everything and then enter to select a slide. Press S to see the speaker notes, which are more or less the script I will be sticking to.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 09 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/gopherconsyd-2023/</guid></item><item><title>My go-to 'make code go faster' ideas</title><link>https://jodavaho.io/posts/dev-optimization-tricks.html</link><description>&lt;p&gt;I&amp;rsquo;m not an optimization guru by any means. It&amp;rsquo;s never been something I&amp;rsquo;ve been
&lt;em&gt;allowed&lt;/em&gt; to focus on at work, sadly. At some jobs, performance is secondary to
correctness and robustness, and at others, it&amp;rsquo;s secondary to flashy features.&lt;/p&gt;
&lt;p&gt;But, I&amp;rsquo;ve used the following tricks in hotloops&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dimension reduction (esp via convolution)&lt;/li&gt;
&lt;li&gt;Branchless calculation&lt;/li&gt;
&lt;li&gt;SIMD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SIMD gets a lot of love, but it&amp;rsquo;s a constant-factor improvement and can be
tough to coax out of the compiler (unless you use a library).&lt;/p&gt;
&lt;p&gt;Branchless calculation is getting less relevant as a &amp;ldquo;trick&amp;rdquo;. A really dumb example is if you have to do this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c++"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Example 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (x&lt;span style="color: #f92672;"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; x&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;y;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;} &lt;span style="color: #66d9ef;"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; x&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;b;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then you can speed it up by doing this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c++"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Example 2:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;int&lt;/span&gt; c &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; (x&lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; x&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;(c&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;y &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; (&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;&lt;span style="color: #f92672;"&gt;-&lt;/span&gt;c)&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;b); &lt;span style="color: #75715e;"&gt;// either x*y or x*b;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;//better: x*(c*(y-b) + (b));
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And if you can combine this with unrolling and SIMD, then you&amp;rsquo;re really well
off. The second example works just as well if those are &lt;code&gt;Eigen::VectorXd&lt;/code&gt;
length 1000, and now you&amp;rsquo;re really cranking out the speed.&lt;/p&gt;
&lt;p&gt;Something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c++"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Example 3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;for&lt;/span&gt; ( i .. N )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (x[i]&lt;span style="color: #f92672;"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      out[i] &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; x[i]&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;y[i];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  } &lt;span style="color: #66d9ef;"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      out[i] x[i]&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;b[i];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Becomes trivial, and looks exactly like &lt;code&gt;//Example 2&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;But even more fundamentally, those usually need to come &lt;em&gt;after&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the right data structures&lt;/li&gt;
&lt;li&gt;Using the right algorithms&lt;/li&gt;
&lt;li&gt;Reframing the problem for speed, e.g., giving up a little to go a lot faster&lt;/li&gt;
&lt;li&gt;Memoization/caching/pre-calculating&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These can usually get me 80% of the way there. When they all come together,
you&amp;rsquo;re staring at your machine wondering how it is done already.&lt;/p&gt;
&lt;p&gt;I rarely see great returns on multi-threaded / multi-process, unless you&amp;rsquo;re
doing rote calculations on a large dataset in a batch.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Thu, 09 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dev-optimization-tricks.html</guid></item><item><title>Anchor Tags</title><link>https://www.marginalia.nu/log/93_atags/</link><description>I&amp;rsquo;ve been working on getting anchor tag keywords into the search engine, basically using link texts to complement the keywords on a webpage.
The problem I&amp;rsquo;m attempting to address is that many websites don&amp;rsquo;t really describe themselves particularly well. As Steve Ballmer&amp;rsquo;s stage performance once illustrated, merely repeating a word doesn&amp;rsquo;t on its own make what you&amp;rsquo;re saying relevant to the term.
Another good example of how it falls short is PuTTY&amp;rsquo;s website, which will be used as a pilot case to improve.</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 07 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/93_atags/</guid></item><item><title>What I'm up to - November 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-november-2023</link><description>&lt;div class="prose"&gt;
  &lt;div&gt;
&lt;br /&gt;This is my monthly newsletter about what I'm up to, which &lt;a href="https://www.philipithomas.com/posts/how-to-replace-social-media-with-a-personal-newsletter"&gt;I send in place of social media&lt;/a&gt;.&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;What I did in October&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Switched my daily driver Aeropress coffee recipe to &lt;a href="https://coffeeadastra.com/2021/09/07/reaching-fuller-flavor-profiles-with-the-aeropress/"&gt;this one by Jonathan Gagné&lt;/a&gt;. It produces delicious results. (His book &lt;a href="https://www.amazon.com/Physics-Filter-Coffee-Jonathan-Gagne/dp/0578246082/ref=sr_1_1?crid=3CK5BF0HWJ5A3&amp;amp;keywords=physics+of+coffee&amp;amp;qid=1699050325&amp;amp;sprefix=physics+of+coffee%2Caps%2C83&amp;amp;sr=8-1"&gt;Physics of Filter Coffee&lt;/a&gt; is excellent.)&lt;/li&gt;
&lt;li&gt;Witnessed the aftermath of a bike crash, did some activism, and got &lt;a href="https://www.curbed.com/2023/10/bike-lane-manhattan-queensboro-bridge-cyclists-crashes.html"&gt;featured in an article on Curbed&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://booklet.group"&gt;Booklet&lt;/a&gt;, the modern email software I make, as a &lt;a href="https://hq.booklet.group"&gt;bunch of new features&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://postcard.page"&gt;Postcard&lt;/a&gt;, the personal website software I make, continues to grow as people leave the site formerly known as Twitter.&lt;/li&gt;
&lt;li&gt;Made some light updates to the &lt;a href="https://contraption.co"&gt;Contraption Company&lt;/a&gt; website. &lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;&lt;strong&gt;Things to share&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles&lt;/strong&gt;: &lt;a href="https://www.lennysnewsletter.com/p/how-linear-builds-product"&gt;How Linear builds products&lt;/a&gt;. "&lt;a href="https://henrysward.medium.com/what-i-tell-employees-about-negative-press-7c134e7a601c"&gt;What I tell employees about negative press&lt;/a&gt;." How &lt;a href="https://www.evanmiller.org/how-not-to-sort-by-average-rating.html"&gt;Not To Sort By Average Rating&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Music&lt;/strong&gt;: &lt;a href="https://open.spotify.com/album/4FftCsAcXXD1nFO9RFUNFO?si=r_QzEDHSSvWIMq7MnWJgTw"&gt;nadie sabe lo que va a pasar mañana&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Podcasts&lt;/strong&gt;: &lt;a href="https://www.acquired.fm/episodes/charlie-munger"&gt;Charlie Munger on Acquired&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps&lt;/strong&gt;: &lt;a href="https://cursor.sh/"&gt;Cursor&lt;/a&gt;, an AI-first code editor, has replaced VSCode as my daily development tool. &lt;a href="https://dutchcyclinglifestyle.com/"&gt;Dutch Cycling Lifestyle&lt;/a&gt;  is a fun use of AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trend&lt;/strong&gt;: While chat is the best-known product from OpenAI, the &lt;a href="https://platform.openai.com/docs/guides/embeddings"&gt;Embeddings&lt;/a&gt; feature is powerful and being incorporated into many products in ways that are not obviously "AI." &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Videos: &lt;/strong&gt;Magnus Midtbø did some &lt;a href="https://youtu.be/Sw_bkFVTHdw?si=4760uLWenEBhXHRl"&gt;videos&lt;/a&gt; &lt;a href="https://youtu.be/ofI5RV7fpT4?si=Qn3ms7ThtuN0bcIX"&gt;with&lt;/a&gt; &lt;a href="https://youtu.be/9bDPoBbdsJQ?si=g6HiLV1BUhDw8svi"&gt;Norwegian&lt;/a&gt; &lt;a href="https://youtu.be/81Dz26FboU4?si=l3TGDSbHcLdvOGnl"&gt;military&lt;/a&gt; that gave a documentary-like glimpse into their lives. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffees I'm drinking: &lt;/strong&gt;&lt;a href="https://timwendelboe.no/product/geisha-light-roast-coffee-caballero/"&gt;Tim Wendelboe Caballero Geisha&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;&lt;strong&gt;Plans for November&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Continuing to build &lt;a href="https://booklet.group"&gt;Booklet&lt;/a&gt; features.&lt;/li&gt;
&lt;li&gt;Spending a couple of weeks in Europe for a mix of work and fun. &lt;/li&gt;
&lt;li&gt;Working on essays for &lt;a href="https://contraption.co"&gt;Contraption Company&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;&lt;strong&gt;Where I'll be &lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;12-14 Nov: Oslo&lt;/li&gt;
&lt;li&gt;14-16 Nov: Copenhagen&lt;/li&gt;
&lt;li&gt;16-19 Nov: London&lt;/li&gt;
&lt;li&gt;19-24 Nov: Nice, FR&lt;/li&gt;
&lt;li&gt;24-26 Nov: Barcelona&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;Let me know if we overlap.&lt;/div&gt;&lt;div&gt;
&lt;br /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Mon, 06 Nov 2023 15:32:33 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-november-2023</guid></item><item><title>Being a minor AI public figure</title><link>https://www.swyx.io/being-a-minor-ai-public-figure</link><description>&lt;p&gt;I was recently involved in moderating a chat with Kanjun Qiu of Imbue at the MIT AI conf: https://photos.google.com/share/AF1QipNJ9i78ICeg2YuemyAXmtStKvqr9l0Tao3xQWxmeAVjBALHn_NnFvPXFlNSkdMfqA?pli=1&amp;#x26;key=dTFRRHBTLVRZTEVCem0zal8tNVkxblh0V3k4VXhR&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Fri, 03 Nov 2023 22:21:53 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/being-a-minor-ai-public-figure</guid></item><item><title>Writing a storage engine for Postgres: an in-memory Table Access Method</title><link>http://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html</link><description>&lt;p&gt;With &lt;a href="https://www.postgresql.org/docs/release/12.0/"&gt;Postgres 12&lt;/a&gt;,
released in 2019, it became possible to &lt;a href="https://www.pgcon.org/2019/schedule/attachments/536_pgcon2019_pluggable_table_AM_V1.3.pdf"&gt;swap out Postgres's storage
engine&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is a feature MySQL has supported for a long time. There are at
least &lt;a href="https://github.com/eatonphil/pgtam"&gt;8 different&lt;/a&gt; &lt;em&gt;built-in&lt;/em&gt;
engines you can pick from. &lt;a href="https://myrocks.io/"&gt;MyRocks&lt;/a&gt;, MySQL on
RocksDB, is another popular third-party distribution.&lt;/p&gt;
&lt;p&gt;I assume there will be a renaissance of Postgres storage engines. To
date, the efforts are
nascent. &lt;a href="https://github.com/orioledb/orioledb"&gt;OrioleDB&lt;/a&gt; and &lt;a href="https://github.com/citusdata/citus/blob/main/src/backend/columnar/README.md"&gt;Citus
Columnar&lt;/a&gt;
are two promising third-party table access methods being actively
developed.&lt;/p&gt;
&lt;h3 id="why-alternative-storage-engines?"&gt;Why alternative storage engines?&lt;/h3&gt;&lt;p&gt;The ability to swap storage engines is useful because different
workloads sometimes benefit from different storage
approaches. Analytics workloads and columnar storage layouts &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/c_columnar_storage_disk_mem_mgmnt.html"&gt;go well
together&lt;/a&gt;. Write-heavy
workloads and LSM trees &lt;a href="https://github.com/wiredtiger/wiredtiger/wiki/Btree-vs-LSM"&gt;go well
together&lt;/a&gt;. And
some people like in-memory storage for running integration tests.&lt;/p&gt;
&lt;p&gt;By swapping out only the storage engine, you get the benefit of the
rest of the Postgres or MySQL infrastructure. The query language, the
wire protocol, the ecosystem, etc.&lt;/p&gt;
&lt;h3 id="why-not-foreign-data-wrappers?"&gt;Why not foreign data wrappers?&lt;/h3&gt;&lt;p&gt;Very little has been written about the difference between foreign data
wrappers (FDWs) and table access methods. Table access methods seems
to be the lower-level layer where presumably you get better
performance and cleaner integration. But there is clearly overlap
between these two extension options.&lt;/p&gt;
&lt;p&gt;For example there is a &lt;a href="https://github.com/ildus/clickhouse_fdw"&gt;FDW for
ClickHouse&lt;/a&gt; so when you
create tables and rows and query the tables you are really creating
and querying rows in a ClickHouse server. Similarly there's a &lt;a href="https://github.com/vidardb/pgrocks-fdw"&gt;FDW for
RocksDB&lt;/a&gt;. And Citus's columnar
engine works
&lt;a href="https://www.citusdata.com/blog/2021/03/06/citus-10-columnar-compression-for-postgres/#:~:text=What%20About%20cstore_fdw%3F"&gt;either&lt;/a&gt;
as a foreign data wrapper or a table access method.&lt;/p&gt;
&lt;p&gt;The Citus page draws the clearest distinction between FDWs and table
access methods, but even that page is vague. Performance doesn't seem
to be the main difference. Closer integration, and thus the ability to
look more like vanilla Postgres from the outside, seems to be the
gist.&lt;/p&gt;
&lt;p&gt;In any case, I wanted to explore the table access method API.&lt;/p&gt;
&lt;h3 id="digging-in"&gt;Digging in&lt;/h3&gt;&lt;p&gt;I haven't written Postgres extensions before and I've never written C
professionally. If you're familiar with Postgres internals or C and
notice something funky, please &lt;a href="mailto:me@eatonphil.com"&gt;let me know&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;It turns out that almost no one has written how to implement the
minimal table access methods for various storage engine operations. So
after quite a bit of stumbling to get the basics of an in-memory
storage engine working, I'm going to walk you through my approach.&lt;/p&gt;
&lt;p&gt;This is prototype-quality code which hopefully will be a useful base
for further exploration.&lt;/p&gt;
&lt;p&gt;All code for this post is &lt;a href="https://github.com/eatonphil/pgtam"&gt;available on
GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="a-debug-postgres-build"&gt;A debug Postgres build&lt;/h3&gt;&lt;p&gt;First off, let's make a &lt;a href="https://wiki.postgresql.org/wiki/Developer_FAQ#Compile-time"&gt;debug
build&lt;/a&gt; of
Postgres.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# An arbitrary commit from `master` after Postgres 16 I am on&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;849172ff4883d44168f96f39d3fde96d0aa34c99&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="k"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cassert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="k"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-ggdb -Og -g3 -fno-omit-frame-pointer&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;j8&lt;/span&gt;
&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;install&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will install Postgres binaries (e.g. &lt;code&gt;psql&lt;/code&gt;, &lt;code&gt;pg_ctl&lt;/code&gt;, &lt;code&gt;initdb&lt;/code&gt;,
&lt;code&gt;pg_config&lt;/code&gt;) into &lt;code&gt;/usr/local/pgsql/bin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I'm going to reference those absolute paths throughout this post
because you might have a system (package manager) install of Postgres
already.&lt;/p&gt;
&lt;p&gt;Let's create a database and start up this debug build:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/initdb&lt;span class="w"&gt; &lt;/span&gt;test-db
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/pg_ctl&lt;span class="w"&gt; &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;test-db&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;logfile&lt;span class="w"&gt; &lt;/span&gt;start
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="extension-infrastructure"&gt;Extension infrastructure&lt;/h3&gt;&lt;p&gt;Since we installed Postgres from scratch,
&lt;code&gt;/usr/local/pgsql/bin/pg_config&lt;/code&gt; will supply all of the infrastructure
we need.&lt;/p&gt;
&lt;p&gt;The "infrastructure" is basically just
&lt;a href="https://www.postgresql.org/docs/current/extend-pgxs.html"&gt;PGXS&lt;/a&gt;:
Postgres Makefile utilities.&lt;/p&gt;
&lt;p&gt;It's convention-heavy. So in a new &lt;code&gt;Makefile&lt;/code&gt; for this project we'll
specify:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;MODULES&lt;/code&gt;: Any C sources to build, without the &lt;code&gt;.c&lt;/code&gt; file extension&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EXTENSION&lt;/code&gt;: Extension metadata file, without the &lt;code&gt;.control&lt;/code&gt; file extension&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATA&lt;/code&gt;: A SQL file that is executed when the extension is loaded, this time with the &lt;code&gt;.sql&lt;/code&gt; extension&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;MODULES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pgtam
&lt;span class="nv"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pgtam
&lt;span class="nv"&gt;DATA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pgtam--0.0.1.sql

&lt;span class="nv"&gt;PG_CONFIG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/local/pgsql/bin/pg_config
&lt;span class="nv"&gt;PGXS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;PG_CONFIG&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--pgxs&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="cp"&gt;include $(PGXS)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The final three lines set up the PGXS Makefile library based on the
particular installed Postgres build we want to build the extension
against and install the extension to.&lt;/p&gt;
&lt;p&gt;PGXS gives us a few important targets like &lt;code&gt;make distclean&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;,
and &lt;code&gt;make install&lt;/code&gt; we'll use later on.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;pgtam.c&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;pgtam.c&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;A minimal C file that registers a function capable of serving as a
table access method is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;postgres.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;fmgr.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;access/tableam.h&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;PG_MODULE_MAGIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TableAmRoutine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_methods&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T_TableAmRoutine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;PG_FUNCTION_INFO_V1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Datum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PG_FUNCTION_ARGS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PG_RETURN_POINTER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;memam_methods&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  If you want to read about extension basics without the complexity of
  table access methods, you can find a complete, minimal Postgres
  extension I wrote to validate the
  infrastructure &lt;a href="https://github.com/eatonphil/pgext-101"&gt;here&lt;/a&gt;. Or
  you can follow a
  &lt;a href="https://github.com/IshaanAdarsh/Postgres-extension-tutorial/blob/main/SGML/intro_and_toc.md"&gt;larger
    tutorial&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;The workflow for registering a table access method is to first run
&lt;code&gt;CREATE EXTENSION pgtam&lt;/code&gt;. This assumes &lt;code&gt;pgtam&lt;/code&gt; is an extension that
has a function that returns a &lt;code&gt;TableAmRoutine&lt;/code&gt; struct instance, a
table of table access methods.&lt;/p&gt;
&lt;p&gt;Then you must run &lt;code&gt;CREATE ACCESS METHOD mem TYPE TABLE HANDLER
mem_tableam_handler&lt;/code&gt;. And finally you can use the access method when
creating a table with &lt;code&gt;USING mem&lt;/code&gt;: &lt;code&gt;CREATE TABLE x(a INT) USING mem&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;pgtam.control&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;pgtam.control&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;This file contains extension metadata. At a minimum, the version of
the extension and the filename for the extension where it should be
installed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;default_version = '0.0.1'
module_pathname = '$libdir/pgtam'
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="&amp;lt;code&amp;gt;pgtam--0.0.1.sql&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;pgtam--0.0.1.sql&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;Finally, in &lt;code&gt;pgtam--0.0.1.sql&lt;/code&gt; (which is executed when we call &lt;code&gt;CREATE
EXTENSION pgtam&lt;/code&gt;), we register the handler function as a foreign
function, and then we register the function as an access method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FUNCTION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_am_handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'pgtam'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'mem_tableam_handler'&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;STRICT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ACCESS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;METHOD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TYPE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;HANDLER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="build"&gt;Build&lt;/h4&gt;&lt;p&gt;Now that we've got all the pieces in place, we can build and install
the extension.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;make
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's add a &lt;code&gt;test.sql&lt;/code&gt; script to exercise the extension:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And run it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:3: server closed the connection unexpectedly&lt;/span&gt;
&lt;span class="go"&gt;        This probably means the server terminated abnormally&lt;/span&gt;
&lt;span class="go"&gt;        before or while processing the request.&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:3: error: connection to server was lost&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok, so &lt;code&gt;psql&lt;/code&gt; crashed! Let's look at the server logs. When we started
Postgres with &lt;code&gt;pg_ctl&lt;/code&gt; we specified the log file as &lt;code&gt;logfile&lt;/code&gt; in the
directory where we ran &lt;code&gt;pg_ctl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If we look through it we'll spot an assertion failure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;Assert&lt;span class="w"&gt; &lt;/span&gt;logfile
&lt;span class="go"&gt;TRAP: failed Assert(&amp;quot;routine-&amp;gt;scan_begin != NULL&amp;quot;), File: &amp;quot;tableamapi.c&amp;quot;, Line: 52, PID: 2906922&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's a great sign! This is Postgres's debug infrastructure helping
to make sure the table access method is correctly implemented.&lt;/p&gt;
&lt;h3 id="table-access-method-stubs"&gt;Table access method stubs&lt;/h3&gt;&lt;p&gt;The next step is to add function stubs for all the non-optional
methods of the &lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/include/access/tableam.h#L282"&gt;&lt;code&gt;TableAmRoutine&lt;/code&gt;
struct&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've done all the work for you already so you can just copy this over
the existing &lt;code&gt;pgtam.c&lt;/code&gt;. It's a big file, but don't worry. There's
nothing to explain. Just a bunch of blank functions returning default
values when required.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;postgres.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;fmgr.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;access/tableam.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;access/heapam.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;nodes/execnodes.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;catalog/index.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;commands/vacuum.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;utils/builtins.h&amp;quot;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;executor/tuptable.h&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;PG_MODULE_MAGIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TableAmRoutine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_methods&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlotOps&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_slot_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_beginscan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nkeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ScanKeyData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ParallelTableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parallel_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_rescan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ScanKeyData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;set_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow_strat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow_sync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow_pagemode&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_endscan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sscan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_getnextslot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ScanDirection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IndexFetchTableData&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_fetch_begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_fetch_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IndexFetchTableData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_fetch_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IndexFetchTableData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_fetch_tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;IndexFetchTableData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;call_again&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;all_dead&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BulkInsertState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bistate&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_insert_speculative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BulkInsertState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bistate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;specToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_complete_speculative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;specToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_multi_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ntuples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BulkInsertState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bistate&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crosscheck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_FailureData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tmfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;changingPart&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;otid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crosscheck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_FailureData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tmfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;LockTupleMode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lockmode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TU_UpdateIndexes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;update_indexes&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;LockTupleMode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;LockWaitPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait_policy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_FailureData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tmfd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_fetch_row_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_get_latest_tid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_tid_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ItemPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_satisfies_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_delete_tuples&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TM_IndexDeleteOp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;delstate&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_set_new_filelocator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RelFileLocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newrlocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;persistence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;freezeXid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;MultiXactId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;minmulti&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_nontransactional_truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_copy_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RelFileLocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newrlocator&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_copy_for_cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OldHeap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NewHeap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OldIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;use_sort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OldestXmin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;xid_cutoff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;MultiXactId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;multi_cutoff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;num_tuples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tups_vacuumed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tups_recently_dead&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_vacuum_rel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;VacuumParams&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BufferAccessStrategy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bstrategy&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_scan_analyze_next_block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BlockNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blockno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BufferAccessStrategy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bstrategy&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_scan_analyze_next_tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OldestXmin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;liverows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;deadrows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_build_range_scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heapRelation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indexRelation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;IndexInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;indexInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow_sync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;anyvisible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BlockNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;start_blockno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BlockNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numblocks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;IndexBuildCallback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;callback_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&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="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_index_validate_scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heapRelation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indexRelation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;IndexInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;indexInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ValidateIndexState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_needs_toast_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_relation_toast_am&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_fetch_toast_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;toastrel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;valueid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;attrsize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sliceoffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;slicelength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;varlena&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_estimate_rel_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;attr_widths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BlockNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tuples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;allvisfrac&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_scan_sample_next_block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SampleScanState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scanstate&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_scan_sample_next_tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SampleScanState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scanstate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TableAmRoutine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_methods&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T_TableAmRoutine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slot_callbacks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_slot_callbacks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_begin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_beginscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_endscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_rescan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_rescan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_getnextslot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_getnextslot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parallelscan_estimate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_block_parallelscan_estimate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parallelscan_initialize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_block_parallelscan_initialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parallelscan_reinitialize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_block_parallelscan_reinitialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_fetch_begin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_fetch_begin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_fetch_reset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_fetch_reset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_fetch_end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_fetch_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_fetch_tuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_fetch_tuple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_insert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_insert_speculative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_insert_speculative&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_complete_speculative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_complete_speculative&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multi_insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_multi_insert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_delete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_lock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_fetch_row_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_fetch_row_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_get_latest_tid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_get_latest_tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_tid_valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_tid_valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tuple_satisfies_snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_tuple_satisfies_snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_delete_tuples&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_delete_tuples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_set_new_filelocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_set_new_filelocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_nontransactional_truncate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_nontransactional_truncate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_copy_data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_copy_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_copy_for_cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_copy_for_cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_vacuum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_vacuum_rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_analyze_next_block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_scan_analyze_next_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_analyze_next_tuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_scan_analyze_next_tuple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_build_range_scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_build_range_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_validate_scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_index_validate_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_block_relation_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_needs_toast_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_needs_toast_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_toast_am&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_toast_am&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_fetch_toast_slice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_fetch_toast_slice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relation_estimate_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_estimate_rel_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_sample_next_block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_scan_sample_next_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_sample_next_tuple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_scan_sample_next_tuple&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;PG_FUNCTION_INFO_V1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Datum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;mem_tableam_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PG_FUNCTION_ARGS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PG_RETURN_POINTER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;memam_methods&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's build and test it!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hey we're getting somewhere! It successfully created the table with
our custom table access method.&lt;/p&gt;
&lt;h3 id="querying-rows"&gt;Querying rows&lt;/h3&gt;&lt;p&gt;Next, let's try querying the table by adding a &lt;code&gt;SELECT a FROM x&lt;/code&gt; to
&lt;code&gt;test.sql&lt;/code&gt; and running it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: server closed the connection unexpectedly&lt;/span&gt;
&lt;span class="go"&gt;        This probably means the server terminated abnormally&lt;/span&gt;
&lt;span class="go"&gt;        before or while processing the request.&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: error: connection to server was lost&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This time there's nothing in &lt;code&gt;logfile&lt;/code&gt; that helps:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-n15&lt;span class="w"&gt; &lt;/span&gt;logfile
&lt;span class="go"&gt;2023-11-01 18:43:32.449 UTC [2906199] LOG:  database system is ready to accept connections&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 18:58:32.572 UTC [2907997] LOG:  checkpoint starting: time&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 18:58:35.305 UTC [2907997] LOG:  checkpoint complete: wrote 28 buffers (0.2%); 0 WAL file(s) added, 0 removed, 0 recycled; write=2.712 s, sync=0.015 s, total=2.733 s; sync files=23, longest=0.004 s, average=0.001 s; distance=128 kB, estimate=150 kB; lsn=0/15F88E0, redo lsn=0/15F8888&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.485 UTC [2906199] LOG:  server process (PID 2908242) was terminated by signal 11: Segmentation fault&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.485 UTC [2906199] DETAIL:  Failed process was running: SELECT a FROM x;&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.485 UTC [2906199] LOG:  terminating any other active server processes&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.486 UTC [2906199] LOG:  all server processes terminated; reinitializing&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.508 UTC [2908253] LOG:  database system was interrupted; last known up at 2023-11-01 18:58:35 UTC&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.518 UTC [2908253] LOG:  database system was not properly shut down; automatic recovery in progress&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.519 UTC [2908253] LOG:  redo starts at 0/15F8888&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.520 UTC [2908253] LOG:  invalid record length at 0/161DE70: expected at least 24, got 0&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.520 UTC [2908253] LOG:  redo done at 0/161DE38 system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.521 UTC [2908254] LOG:  checkpoint starting: end-of-recovery immediate wait&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.532 UTC [2908254] LOG:  checkpoint complete: wrote 35 buffers (0.2%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.001 s, sync=0.010 s, total=0.012 s; sync files=27, longest=0.003 s, average=0.001 s; distance=149 kB, estimate=149 kB; lsn=0/161DE70, redo lsn=0/161DE70&lt;/span&gt;
&lt;span class="go"&gt;2023-11-01 19:08:14.533 UTC [2906199] LOG:  database system is ready to accept connections&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This was the first place I got stuck. How on earth do I figure out
what methods to implement? I mean, it's clearly one or more of these
methods from the struct. But there are so many methods.&lt;/p&gt;
&lt;p&gt;I tried setting a breakpoint in &lt;code&gt;gdb&lt;/code&gt; on the process returned by
&lt;code&gt;SELECT pg_backend_pid()&lt;/code&gt; for a &lt;code&gt;psql&lt;/code&gt; session, but the breakpoint
never seemed to be hit for any of my methods.&lt;/p&gt;
&lt;p&gt;So I did the low-tech solution and opened a file, &lt;code&gt;/tmp/pgtam.log&lt;/code&gt;,
turned off buffering on it, and added a log to every method on the
&lt;code&gt;TableAmRoutine&lt;/code&gt; struct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -12,9 +12,13 @@&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;const TableAmRoutine memam_methods;

&lt;span class="gi"&gt;+FILE* fd;&lt;/span&gt;
&lt;span class="gi"&gt;+#define DEBUG_FUNC() fprintf(fd, &amp;quot;in %s\n&amp;quot;, __func__);&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;static const TupleTableSlotOps* memam_slot_callbacks(
&lt;span class="w"&gt; &lt;/span&gt;  Relation relation
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return NULL;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -26,6 +30,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  ParallelTableScanDesc parallel_scan,
&lt;span class="w"&gt; &lt;/span&gt;  uint32 flags
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return NULL;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -37,9 +42,11 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  bool allow_sync,
&lt;span class="w"&gt; &lt;/span&gt;  bool allow_pagemode
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_endscan(TableScanDesc sscan) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_getnextslot(
&lt;span class="gu"&gt;@@ -47,17 +54,21 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  ScanDirection direction,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static IndexFetchTableData* memam_index_fetch_begin(Relation rel) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return NULL;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_index_fetch_reset(IndexFetchTableData *scan) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_index_fetch_end(IndexFetchTableData *scan) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_index_fetch_tuple(
&lt;span class="gu"&gt;@@ -68,6 +79,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  bool *call_again,
&lt;span class="w"&gt; &lt;/span&gt;  bool *all_dead
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -78,6 +90,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  int options,
&lt;span class="w"&gt; &lt;/span&gt;  BulkInsertState bistate
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_tuple_insert_speculative(
&lt;span class="gu"&gt;@@ -87,6 +100,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  int options,
&lt;span class="w"&gt; &lt;/span&gt;  BulkInsertState bistate,
&lt;span class="w"&gt; &lt;/span&gt;  uint32 specToken) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_tuple_complete_speculative(
&lt;span class="gu"&gt;@@ -94,6 +108,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot,
&lt;span class="w"&gt; &lt;/span&gt;  uint32 specToken,
&lt;span class="w"&gt; &lt;/span&gt;  bool succeeded) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_multi_insert(
&lt;span class="gu"&gt;@@ -104,6 +119,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  int options,
&lt;span class="w"&gt; &lt;/span&gt;  BulkInsertState bistate
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static TM_Result memam_tuple_delete(
&lt;span class="gu"&gt;@@ -117,6 +133,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  bool changingPart
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="w"&gt; &lt;/span&gt;  TM_Result result = {};
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return result;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -133,6 +150,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TU_UpdateIndexes *update_indexes
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="w"&gt; &lt;/span&gt;  TM_Result result = {};
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return result;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -148,6 +166,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TM_FailureData *tmfd)
&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt; &lt;/span&gt;  TM_Result result = {};
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return result;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -157,6 +176,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  Snapshot snapshot,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -164,9 +184,11 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TableScanDesc sscan,
&lt;span class="w"&gt; &lt;/span&gt;  ItemPointer tid
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_tuple_tid_valid(TableScanDesc scan, ItemPointer tid) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -175,6 +197,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot,
&lt;span class="w"&gt; &lt;/span&gt;  Snapshot snapshot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -183,6 +206,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TM_IndexDeleteOp *delstate
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="w"&gt; &lt;/span&gt;  TransactionId id = {};
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return id;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -193,17 +217,20 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TransactionId *freezeXid,
&lt;span class="w"&gt; &lt;/span&gt;  MultiXactId *minmulti
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_relation_nontransactional_truncate(
&lt;span class="w"&gt; &lt;/span&gt;  Relation rel
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_relation_copy_data(
&lt;span class="w"&gt; &lt;/span&gt;  Relation rel,
&lt;span class="w"&gt; &lt;/span&gt;  const RelFileLocator *newrlocator
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_relation_copy_for_cluster(
&lt;span class="gu"&gt;@@ -218,6 +245,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  double *tups_vacuumed,
&lt;span class="w"&gt; &lt;/span&gt;  double *tups_recently_dead
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_vacuum_rel(
&lt;span class="gu"&gt;@@ -225,6 +253,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  VacuumParams *params,
&lt;span class="w"&gt; &lt;/span&gt;  BufferAccessStrategy bstrategy
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_scan_analyze_next_block(
&lt;span class="gu"&gt;@@ -232,6 +261,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  BlockNumber blockno,
&lt;span class="w"&gt; &lt;/span&gt;  BufferAccessStrategy bstrategy
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -242,6 +272,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  double *deadrows,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -258,6 +289,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  void *callback_state,
&lt;span class="w"&gt; &lt;/span&gt;  TableScanDesc scan
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return 0;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -268,14 +300,17 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  Snapshot snapshot,
&lt;span class="w"&gt; &lt;/span&gt;  ValidateIndexState *state
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_relation_needs_toast_table(Relation rel) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static Oid memam_relation_toast_am(Relation rel) {
&lt;span class="w"&gt; &lt;/span&gt;  Oid oid = {};
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return oid;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -287,6 +322,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  int32 slicelength,
&lt;span class="w"&gt; &lt;/span&gt;  struct varlena *result
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_estimate_rel_size(
&lt;span class="gu"&gt;@@ -296,11 +332,13 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  double *tuples,
&lt;span class="w"&gt; &lt;/span&gt;  double *allvisfrac
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static bool memam_scan_sample_next_block(
&lt;span class="w"&gt; &lt;/span&gt;  TableScanDesc scan, SampleScanState *scanstate
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gu"&gt;@@ -309,6 +347,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  SampleScanState *scanstate,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return false;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And then in the entrypoint, initialize the file for logging.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -369,5 +408,9 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;PG_FUNCTION_INFO_V1(mem_tableam_handler);

&lt;span class="w"&gt; &lt;/span&gt;Datum mem_tableam_handler(PG_FUNCTION_ARGS) {
&lt;span class="gi"&gt;+  fd = fopen(&amp;quot;/tmp/pgtam.log&amp;quot;, &amp;quot;a&amp;quot;);&lt;/span&gt;
&lt;span class="gi"&gt;+  setvbuf(fd, NULL, _IONBF, 0); // Prevent buffering&lt;/span&gt;
&lt;span class="gi"&gt;+  fprintf(fd, &amp;quot;\n\nmem_tableam handler loaded\n&amp;quot;);&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  PG_RETURN_POINTER(&amp;amp;memam_methods);
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's give it a shot!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: server closed the connection unexpectedly&lt;/span&gt;
&lt;span class="go"&gt;        This probably means the server terminated abnormally&lt;/span&gt;
&lt;span class="go"&gt;        before or while processing the request.&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: error: connection to server was lost&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And let's check our log file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_set_new_filelocator&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_estimate_rel_size&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_slot_callbacks&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we're getting somewhere!&lt;/p&gt;
&lt;p class="note"&gt;
  I later realized &lt;code&gt;elog()&lt;/code&gt; is the way most people log
  within Postgres/within extensions. I didn't know that when I was
  getting started though. This separate logging was a simple way to
  get the info out.
&lt;/p&gt;&lt;h4 id="&amp;lt;code&amp;gt;slot_callbacks&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;slot_callbacks&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;Since the request crashes and the last logged function is
&lt;code&gt;memam_slot_callbacks&lt;/code&gt;, it seems like that is where we should
concentrate. The &lt;a href="https://www.postgresql.org/docs/current/tableam.html"&gt;table access method
docs&lt;/a&gt; suggest
looking at the default &lt;code&gt;heap&lt;/code&gt; access method for inspiration.&lt;/p&gt;
&lt;p&gt;Its
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/backend/access/heap/heapam_handler.c#L67"&gt;version&lt;/a&gt;
of &lt;code&gt;slot_callbacks&lt;/code&gt; returns &lt;code&gt;&amp;amp;TTSOpsBufferHeapTuple&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlotOps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;heapam_slot_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TTSOpsBufferHeapTuple&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I have no idea what that means, but since it is defined in
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/backend/executor/execTuples.c#L1080"&gt;&lt;code&gt;src/backend/executor/execTuples.c&lt;/code&gt;&lt;/a&gt;
it doesn't seem to be tied to the &lt;code&gt;heap&lt;/code&gt; access method
implementation. Let's try it.&lt;/p&gt;
&lt;p class="note"&gt;
  While it works initially, I noticed later on that
  &lt;code&gt;TTSOpsBufferHeapTuple&lt;/code&gt; turns out not to be the right
  choice here. &lt;code&gt;TTSOpsVirtual&lt;/code&gt; seems to be the right
  implementation.
&lt;/p&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -19,7 +19,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  Relation relation
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gd"&gt;-  return NULL;&lt;/span&gt;
&lt;span class="gi"&gt;+  return &amp;amp;TTSOpsVirtual;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static TableScanDesc memam_beginscan(
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: server closed the connection unexpectedly&lt;/span&gt;
&lt;span class="go"&gt;        This probably means the server terminated abnormally&lt;/span&gt;
&lt;span class="go"&gt;        before or while processing the request.&lt;/span&gt;
&lt;span class="go"&gt;psql:test.sql:6: error: connection to server was lost&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It still crashes. But this time in &lt;code&gt;/tmp/pgtam.log&lt;/code&gt; we made it into a
new method!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_set_new_filelocator&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_estimate_rel_size&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_slot_callbacks&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_beginscan&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="&amp;lt;code&amp;gt;scan_begin&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;scan_begin&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;The function signature is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;heap_beginscan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nkeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ScanKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ParallelTableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parallel_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since we just implemented stub versions of all the methods, we've been
returning &lt;code&gt;NULL&lt;/code&gt;. Since we're failing in this function, maybe we
should try returning something that isn't &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By looking at the definition of &lt;code&gt;TableScanDesc&lt;/code&gt;, we can see it is a
pointer to the &lt;code&gt;TableScanDescData&lt;/code&gt; struct defined in
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/include/access/relscan.h#L52"&gt;&lt;code&gt;src/include/access/relscan.h&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's &lt;code&gt;malloc&lt;/code&gt; a &lt;code&gt;TableScanDescData&lt;/code&gt;, free it in &lt;code&gt;endscan&lt;/code&gt;, and return
the &lt;code&gt;TableScanDescData&lt;/code&gt; instance in &lt;code&gt;beginscan&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -30,8 +30,12 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  ParallelTableScanDesc parallel_scan,
&lt;span class="w"&gt; &lt;/span&gt;  uint32 flags
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  TableScanDescData* scan = {};&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gd"&gt;-  return NULL;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  scan = (TableScanDescData*)malloc(sizeof(TableScanDescData));&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  return (TableScanDesc)scan;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_rescan(
&lt;span class="gu"&gt;@@ -87,6 +87,7 @@&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;static void memam_endscan(TableScanDesc sscan) {
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gi"&gt;+  free(sscan);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build and run (you can do it on your own). No difference.&lt;/p&gt;
&lt;p&gt;I got stuck for a while here too. Clearly something must be filled out
in this struct but it could be anything. Through trial and error I
realized the one field that must be filled out is &lt;code&gt;scan-&amp;gt;rs_rd&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -34,6 +34,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();

&lt;span class="w"&gt; &lt;/span&gt;  scan = (TableScanDescData*)malloc(sizeof(TableScanDescData));
&lt;span class="gi"&gt;+  scan-&amp;gt;rs_rd = relation;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;  return (TableScanDesc)scan;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
$&lt;span class="w"&gt; &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
psql:test.sql:1:&lt;span class="w"&gt; &lt;/span&gt;NOTICE:&lt;span class="w"&gt;  &lt;/span&gt;drop&lt;span class="w"&gt; &lt;/span&gt;cascades&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;table&lt;span class="w"&gt; &lt;/span&gt;x
DROP&lt;span class="w"&gt; &lt;/span&gt;EXTENSION
CREATE&lt;span class="w"&gt; &lt;/span&gt;EXTENSION
CREATE&lt;span class="w"&gt; &lt;/span&gt;TABLE
&lt;span class="w"&gt; &lt;/span&gt;a
---
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rows&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And it works! It doesn't return anything but that's correct. There's
nothing to return.&lt;/p&gt;
&lt;p&gt;So what if we actually want to return something? Let's check our logs
in &lt;code&gt;/tmp/pgtam.log&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_set_new_filelocator&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="n"&gt;mem_tableam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_estimate_rel_size&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_slot_callbacks&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_beginscan&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_getnextslot&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memam_endscan&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok, I'm getting the gist of the API. A full table scan (which this is,
because there are no indexes at play) starts with an initialization
for a slot, then the scan begins, then &lt;code&gt;getnextslot&lt;/code&gt; is called for
each row, and then &lt;code&gt;endscan&lt;/code&gt; is called to allow for cleanup.&lt;/p&gt;
&lt;p&gt;So let's try returning a row in &lt;code&gt;getnextslot&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;getnextslot&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;getnextslot&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;The &lt;code&gt;getnextslot&lt;/code&gt; signature is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_getnextslot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TableScanDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sscan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ScanDirection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So the &lt;code&gt;sscan&lt;/code&gt; should be what we returned from &lt;code&gt;beginscan&lt;/code&gt; and the
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/include/access/tableam.h#L341"&gt;interface
docs&lt;/a&gt;
say the current row gets stored in &lt;code&gt;slot&lt;/code&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  The return value seems to indicate whether or not we've reached the
  end of the scan. However, the scan will still end even if you
  &lt;code&gt;return true&lt;/code&gt; if the &lt;code&gt;slot&lt;/code&gt; is not filled out correctly. If the
  &lt;code&gt;slot&lt;/code&gt; is filled out correctly and you unconditionally &lt;code&gt;return
  true&lt;/code&gt;, you will crash the process.
&lt;/p&gt;&lt;p&gt;Let's take a look at the
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/include/executor/tuptable.h#L114"&gt;definition&lt;/a&gt;
of &lt;code&gt;TupleTableSlot&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;TupleTableSlot&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NodeTag&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#define FIELDNO_TUPLETABLESLOT_FLAGS 1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;uint16&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;tts_flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="cm"&gt;/* Boolean states */&lt;/span&gt;
&lt;span class="cp"&gt;#define FIELDNO_TUPLETABLESLOT_NVALID 2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AttrNumber&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;tts_nvalid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* # of valid values in tts_values */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlotOps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tts_ops&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* implementation of slot */&lt;/span&gt;
&lt;span class="cp"&gt;#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 4&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;TupleDesc&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;tts_tupleDescriptor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* slot's tuple descriptor */&lt;/span&gt;
&lt;span class="cp"&gt;#define FIELDNO_TUPLETABLESLOT_VALUES 5&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Datum&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tts_values&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* current per-attribute values */&lt;/span&gt;
&lt;span class="cp"&gt;#define FIELDNO_TUPLETABLESLOT_ISNULL 6&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tts_isnull&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* current per-attribute isnull flags */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MemoryContext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tts_mcxt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* slot itself is in this context */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ItemPointerData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tts_tid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* stored tuple's tid */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Oid&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;tts_tableOid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* table oid of tuple */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;tts_values&lt;/code&gt; is an array of &lt;code&gt;Datum&lt;/code&gt; (which is a Postgres value). So
that sounds like the actual values of the row. The &lt;code&gt;tts_isnull&lt;/code&gt; field
also looks important since that seems to be whether each value in the
row is null or not. And &lt;code&gt;tts_nvalid&lt;/code&gt; sounds important too since
presumably it's the length of the &lt;code&gt;tts_isnull&lt;/code&gt; and &lt;code&gt;tts_values&lt;/code&gt;
arrays.&lt;/p&gt;
&lt;p&gt;The rest of it may or may not be important. Let's try filling out
these three fields though and see what happens.&lt;/p&gt;
&lt;h4 id="datum"&gt;Datum&lt;/h4&gt;&lt;p&gt;Back in the &lt;a href="https://www.postgresql.org/docs/current/xfunc-c.html"&gt;Postgres C extension
documentation&lt;/a&gt;,
we can see some simple examples of converting between C types and
Postgres's Datum type.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Datum&lt;/span&gt;
&lt;span class="nf"&gt;add_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PG_FUNCTION_ARGS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PG_GETARG_INT32&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;PG_RETURN_INT32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&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;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we look at the definition of &lt;code&gt;PG_RETURN_INT32&lt;/code&gt; in
&lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/include/fmgr.h#L354"&gt;&lt;code&gt;src/include/fmgr.h&lt;/code&gt;&lt;/a&gt;,
we see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#define PG_RETURN_INT32(x)   return Int32GetDatum(x)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So &lt;code&gt;Int32GetDatum()&lt;/code&gt; is the function we'll use to set a &lt;code&gt;Datum&lt;/code&gt; for a
cell in a row.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -54,13 +54,26 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gi"&gt;+static bool done = false;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;static bool memam_getnextslot(
&lt;span class="w"&gt; &lt;/span&gt;  TableScanDesc sscan,
&lt;span class="w"&gt; &lt;/span&gt;  ScanDirection direction,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gd"&gt;-  return false;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  if (done) {&lt;/span&gt;
&lt;span class="gi"&gt;+    return false;&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_nvalid = 1;&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_values = (Datum*)malloc(sizeof(Datum) * slot-&amp;gt;tts_nvalid);&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_values[0] = Int32GetDatum(314 /* Some unique-looking value */);&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_isnull = (bool*)malloc(sizeof(bool) * slot-&amp;gt;tts_nvalid);&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_isnull[0] = false;&lt;/span&gt;
&lt;span class="gi"&gt;+  done = true;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  return true;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static IndexFetchTableData* memam_index_fetch_begin(Relation rel) {
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The goal is that we return a single row and then exit the scan. It
will have one 32-bit integer cell (remember we created the table
&lt;code&gt;CREATE TABLE x (a INT)&lt;/code&gt;; &lt;code&gt;INT&lt;/code&gt; is shorthand for &lt;code&gt;INT4&lt;/code&gt; which is a
32-bit integer) that will have the value &lt;code&gt;314&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But if we build and run this, we get no rows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt; a&lt;/span&gt;
&lt;span class="go"&gt;---&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(0 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I got stuck for a while here. Plugging my &lt;code&gt;getnextslot&lt;/code&gt; code into
ChatGPT helped. One thing it gave me to try was calling
&lt;code&gt;ExecStoreVirtualTuple&lt;/code&gt; on the &lt;code&gt;slot&lt;/code&gt;. I noticed that the built-in
&lt;code&gt;heap&lt;/code&gt; access method &lt;a href="https://github.com/postgres/postgres/blob/849172ff4883d44168f96f39d3fde96d0aa34c99/src/backend/access/heap/heapam.c#L1159"&gt;also called a function like
this&lt;/a&gt;
in &lt;code&gt;getnextslot&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And I realized that &lt;code&gt;tts_nvalid&lt;/code&gt; is already set up and the memory for
&lt;code&gt;tts_values&lt;/code&gt; and &lt;code&gt;tts_isnull&lt;/code&gt; is already allocated. So the code became
a little simpler.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -66,11 +66,9 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    return false;
&lt;span class="w"&gt; &lt;/span&gt;  }

&lt;span class="gd"&gt;-  slot-&amp;gt;tts_nvalid = 1;&lt;/span&gt;
&lt;span class="gd"&gt;-  slot-&amp;gt;tts_values = (Datum*)malloc(sizeof(Datum) * slot-&amp;gt;tts_nvalid);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  slot-&amp;gt;tts_values[0] = Int32GetDatum(314 /* Some unique-looking value */);
&lt;span class="gd"&gt;-  slot-&amp;gt;tts_isnull = (bool*)malloc(sizeof(bool) * slot-&amp;gt;tts_nvalid);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  slot-&amp;gt;tts_isnull[0] = false;
&lt;span class="gi"&gt;+  ExecStoreVirtualTuple(slot);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  done = true;

&lt;span class="w"&gt; &lt;/span&gt;  return true;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt; 314&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Fantastic!&lt;/p&gt;
&lt;h3 id="creating-a-table"&gt;Creating a table&lt;/h3&gt;&lt;p&gt;Now that we've proven we can return random data, let's set up
infrastructure for storing tables in memory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -15,6 +15,41 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;FILE* fd;
&lt;span class="w"&gt; &lt;/span&gt;#define DEBUG_FUNC() fprintf(fd, &amp;quot;in %s\n&amp;quot;, __func__);

&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+struct Column {&lt;/span&gt;
&lt;span class="gi"&gt;+  int value;&lt;/span&gt;
&lt;span class="gi"&gt;+};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+struct Row {&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Column* columns;&lt;/span&gt;
&lt;span class="gi"&gt;+  size_t ncolumns;&lt;/span&gt;
&lt;span class="gi"&gt;+};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+#define MAX_ROWS 100&lt;/span&gt;
&lt;span class="gi"&gt;+struct Table {&lt;/span&gt;
&lt;span class="gi"&gt;+  char* name;&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Row* rows;&lt;/span&gt;
&lt;span class="gi"&gt;+  size_t nrows;&lt;/span&gt;
&lt;span class="gi"&gt;+};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+#define MAX_TABLES 100&lt;/span&gt;
&lt;span class="gi"&gt;+struct Database {&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Table* tables;&lt;/span&gt;
&lt;span class="gi"&gt;+  size_t ntables;&lt;/span&gt;
&lt;span class="gi"&gt;+};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+struct Database* database;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+static void get_table(struct Table** table, Relation relation) {&lt;/span&gt;
&lt;span class="gi"&gt;+  char* this_name = NameStr(relation-&amp;gt;rd_rel-&amp;gt;relname);&lt;/span&gt;
&lt;span class="gi"&gt;+  for (size_t i = 0; i &amp;lt; database-&amp;gt;ntables; i++) {&lt;/span&gt;
&lt;span class="gi"&gt;+    if (strcmp(database-&amp;gt;tables[i].name, this_name) == 0) {&lt;/span&gt;
&lt;span class="gi"&gt;+      *table = &amp;amp;database-&amp;gt;tables[i];&lt;/span&gt;
&lt;span class="gi"&gt;+      return;&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+}&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;static const TupleTableSlotOps* memam_slot_callbacks(
&lt;span class="w"&gt; &lt;/span&gt;  Relation relation
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Based on what we logged in &lt;code&gt;/tmp/pgtam.log&lt;/code&gt; it seems like
&lt;code&gt;memam_relation_set_new_filelocator&lt;/code&gt; is called when a new table is
created. So let's handle adding a new table there.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -233,7 +268,16 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  TransactionId *freezeXid,
&lt;span class="w"&gt; &lt;/span&gt;  MultiXactId *minmulti
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  struct Table table = {};&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  table.name = strdup(NameStr(rel-&amp;gt;rd_rel-&amp;gt;relname));&lt;/span&gt;
&lt;span class="gi"&gt;+  fprintf(fd, &amp;quot;Created table: [%s]\n&amp;quot;, table.name);&lt;/span&gt;
&lt;span class="gi"&gt;+  table.rows = (struct Row*)malloc(sizeof(struct Row) * MAX_ROWS);&lt;/span&gt;
&lt;span class="gi"&gt;+  table.nrows = 0;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  database-&amp;gt;tables[database-&amp;gt;ntables] = table;&lt;/span&gt;
&lt;span class="gi"&gt;+  database-&amp;gt;ntables++;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_relation_nontransactional_truncate(
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, we'll initialize the in-memory &lt;code&gt;Database*&lt;/code&gt; when the handler is
loaded.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -428,5 +472,11 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  setvbuf(fd, NULL, _IONBF, 0); // Prevent buffering
&lt;span class="w"&gt; &lt;/span&gt;  fprintf(fd, &amp;quot;\n\nmem_tableam handler loaded\n&amp;quot;);

&lt;span class="gi"&gt;+  if (database == NULL) {&lt;/span&gt;
&lt;span class="gi"&gt;+    database = (struct Database*)malloc(sizeof(struct Database));&lt;/span&gt;
&lt;span class="gi"&gt;+    database-&amp;gt;ntables = 0;&lt;/span&gt;
&lt;span class="gi"&gt;+    database-&amp;gt;tables = (struct Table*)malloc(sizeof(struct Table) * MAX_TABLES);&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  PG_RETURN_POINTER(&amp;amp;memam_methods);
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we build and run, we won't notice anything new.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt; 314&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But we should see a message in &lt;code&gt;/tmp/pgtam.log&lt;/code&gt; about the new table
being created.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/tmp/pgtam.log


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_set_new_filelocator&lt;/span&gt;
&lt;span class="go"&gt;Created table: [x]&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_estimate_rel_size&lt;/span&gt;
&lt;span class="go"&gt;in memam_slot_callbacks&lt;/span&gt;
&lt;span class="go"&gt;in memam_beginscan&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_endscan&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And there it is! Creation looks good.&lt;/p&gt;
&lt;h3 id="inserting-rows"&gt;Inserting rows&lt;/h3&gt;&lt;p&gt;Let's add &lt;code&gt;INSERT INTO x VALUES (23), (101);&lt;/code&gt; to &lt;code&gt;test.sql&lt;/code&gt; and run
the SQL script.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;INSERT 0 2&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt; 314&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And let's check the log to see what method is called when we try to
&lt;code&gt;INSERT&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/tmp/pgtam.log


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_set_new_filelocator&lt;/span&gt;
&lt;span class="go"&gt;Created table: [x]&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_slot_callbacks&lt;/span&gt;
&lt;span class="go"&gt;in memam_tuple_insert&lt;/span&gt;
&lt;span class="go"&gt;in memam_tuple_insert&lt;/span&gt;
&lt;span class="go"&gt;in memam_estimate_rel_size&lt;/span&gt;
&lt;span class="go"&gt;in memam_slot_callbacks&lt;/span&gt;
&lt;span class="go"&gt;in memam_beginscan&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_endscan&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;tuple_insert&lt;/code&gt; seems to be the method! Looks like it gets called once
for each row to insert. Perfect.&lt;/p&gt;
&lt;p&gt;The signature for &lt;code&gt;tuple_insert&lt;/code&gt; is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;memam_tuple_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Relation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;TupleTableSlot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CommandId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;BulkInsertState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bistate&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can get the table name from &lt;code&gt;relation&lt;/code&gt;, and instead of writing to
&lt;code&gt;slot&lt;/code&gt; we can read from &lt;code&gt;slot-&amp;gt;tts_values&lt;/code&gt; instead.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -141,7 +141,38 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  int options,
&lt;span class="w"&gt; &lt;/span&gt;  BulkInsertState bistate
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  TupleDesc desc = RelationGetDescr(relation);&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Table* table = NULL;&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Column column = {};&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Row row = {};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  get_table(&amp;amp;table, relation);&lt;/span&gt;
&lt;span class="gi"&gt;+  if (table == NULL) {&lt;/span&gt;
&lt;span class="gi"&gt;+    elog(ERROR, &amp;quot;table not found&amp;quot;);&lt;/span&gt;
&lt;span class="gi"&gt;+    return;&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  if (table-&amp;gt;nrows == MAX_ROWS) {&lt;/span&gt;
&lt;span class="gi"&gt;+    elog(ERROR, &amp;quot;cannot insert more rows&amp;quot;);&lt;/span&gt;
&lt;span class="gi"&gt;+    return;&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  row.ncolumns = desc-&amp;gt;natts;&lt;/span&gt;
&lt;span class="gi"&gt;+  Assert(slot-&amp;gt;tts_nvalid == row.ncolumns);&lt;/span&gt;
&lt;span class="gi"&gt;+  Assert(row.ncolumns &amp;gt; 0);&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  row.columns = (struct Column*)malloc(sizeof(struct Column) * row.ncolumns);&lt;/span&gt;
&lt;span class="gi"&gt;+  for (size_t i = 0; i &amp;lt; row.ncolumns; i++) {&lt;/span&gt;
&lt;span class="gi"&gt;+    Assert(desc-&amp;gt;attrs[i].atttypid == INT4OID);&lt;/span&gt;
&lt;span class="gi"&gt;+    column.value = DatumGetInt32(slot-&amp;gt;tts_values[i]);&lt;/span&gt;
&lt;span class="gi"&gt;+    row.columns[i] = column;&lt;/span&gt;
&lt;span class="gi"&gt;+    fprintf(fd, &amp;quot;Got value: %d\n&amp;quot;, column.value);&lt;/span&gt;
&lt;span class="gi"&gt;+  }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  table-&amp;gt;rows[table-&amp;gt;nrows] = row;&lt;/span&gt;
&lt;span class="gi"&gt;+  table-&amp;gt;nrows++;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;static void memam_tuple_insert_speculative(
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build and run and again we won't notice anything new.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;INSERT 0 2&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt; 314&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But if we check the logs, we should see the two column values we
inserted, one for each row.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/tmp/pgtam.log


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_set_new_filelocator&lt;/span&gt;
&lt;span class="go"&gt;Created table: [x]&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_relation_needs_toast_table&lt;/span&gt;


&lt;span class="go"&gt;mem_tableam handler loaded&lt;/span&gt;
&lt;span class="go"&gt;in memam_slot_callbacks&lt;/span&gt;
&lt;span class="go"&gt;in memam_tuple_insert&lt;/span&gt;
&lt;span class="go"&gt;Got value: 23&lt;/span&gt;
&lt;span class="go"&gt;in memam_tuple_insert&lt;/span&gt;
&lt;span class="go"&gt;Got value: 101&lt;/span&gt;
&lt;span class="go"&gt;in memam_estimate_rel_size&lt;/span&gt;
&lt;span class="go"&gt;in memam_slot_callbacks&lt;/span&gt;
&lt;span class="go"&gt;in memam_beginscan&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_getnextslot&lt;/span&gt;
&lt;span class="go"&gt;in memam_endscan&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Woohoo!&lt;/p&gt;
&lt;h3 id="un-hardcoding-the-scan"&gt;Un-hardcoding the scan&lt;/h3&gt;&lt;p&gt;The final thing we need to do is drop the hardcoded &lt;code&gt;314&lt;/code&gt; we returned
from &lt;code&gt;getnextslot&lt;/code&gt; and instead we need to look up the current table
and return rows from it. This also means we need to keep track of
which row we're on. So &lt;code&gt;beginscan&lt;/code&gt; will also need to change slightly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -57,6 +56,14 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return &amp;amp;TTSOpsVirtual;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+struct MemScanDesc {&lt;/span&gt;
&lt;span class="gi"&gt;+  TableScanDescData rs_base; // Base class from access/relscan.h.&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  // Custom data.&lt;/span&gt;
&lt;span class="gi"&gt;+  uint32 cursor;&lt;/span&gt;
&lt;span class="gi"&gt;+};&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;static TableScanDesc memam_beginscan(
&lt;span class="w"&gt; &lt;/span&gt;  Relation relation,
&lt;span class="w"&gt; &lt;/span&gt;  Snapshot snapshot,
&lt;span class="gu"&gt;@@ -65,11 +72,13 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  ParallelTableScanDesc parallel_scan,
&lt;span class="w"&gt; &lt;/span&gt;  uint32 flags
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gd"&gt;-  TableScanDescData* scan = {};&lt;/span&gt;
&lt;span class="gd"&gt;-  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="gi"&gt;+  struct MemScanDesc* scan;&lt;/span&gt;

&lt;span class="gd"&gt;-  scan = (TableScanDescData*)malloc(sizeof(TableScanDescData));&lt;/span&gt;
&lt;span class="gd"&gt;-  scan-&amp;gt;rs_rd = relation;&lt;/span&gt;
&lt;span class="gi"&gt;+  DEBUG_FUNC();&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  scan = (struct MemScanDesc*)malloc(sizeof(struct MemScanDesc));&lt;/span&gt;
&lt;span class="gi"&gt;+  scan-&amp;gt;rs_base.rs_rd = relation;&lt;/span&gt;
&lt;span class="gi"&gt;+  scan-&amp;gt;cursor = 0;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;  return (TableScanDesc)scan;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;span class="gu"&gt;@@ -89,23 +97,26 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="gd"&gt;-static bool done = false;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;static bool memam_getnextslot(
&lt;span class="w"&gt; &lt;/span&gt;  TableScanDesc sscan,
&lt;span class="w"&gt; &lt;/span&gt;  ScanDirection direction,
&lt;span class="w"&gt; &lt;/span&gt;  TupleTableSlot *slot
&lt;span class="w"&gt; &lt;/span&gt;) {
&lt;span class="gi"&gt;+  struct MemScanDesc* mscan = (struct MemScanDesc*)sscan;&lt;/span&gt;
&lt;span class="gi"&gt;+  struct Table* table = NULL;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  DEBUG_FUNC();

&lt;span class="gd"&gt;-  if (done) {&lt;/span&gt;
&lt;span class="gi"&gt;+  ExecClearTuple(slot);&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+  get_table(&amp;amp;table, mscan-&amp;gt;rs_base.rs_rd);&lt;/span&gt;
&lt;span class="gi"&gt;+  if (table == NULL || mscan-&amp;gt;cursor == table-&amp;gt;nrows) {&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    return false;
&lt;span class="w"&gt; &lt;/span&gt;  }

&lt;span class="gd"&gt;-  slot-&amp;gt;tts_values[0] = Int32GetDatum(314 /* Some unique-looking value */);&lt;/span&gt;
&lt;span class="gi"&gt;+  slot-&amp;gt;tts_values[0] = Int32GetDatum(table-&amp;gt;rows[mscan-&amp;gt;cursor].columns[0].value);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  slot-&amp;gt;tts_isnull[0] = false;
&lt;span class="w"&gt; &lt;/span&gt;  ExecStoreVirtualTuple(slot);
&lt;span class="gd"&gt;-  done = true;&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gi"&gt;+  mscan-&amp;gt;cursor++;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  return true;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's try it out.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;INSERT 0 2&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt;  23&lt;/span&gt;
&lt;span class="go"&gt; 101&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(2 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And there we have it. :)&lt;/p&gt;
&lt;h3 id="awesome-sql-power"&gt;Awesome SQL power&lt;/h3&gt;&lt;p&gt;So we tried one table and we tried a &lt;code&gt;SELECT&lt;/code&gt; without anything else.&lt;/p&gt;
&lt;p&gt;What happens if we use more of SQL? Let's create another table
and try some more complex queries. Edit &lt;code&gt;test.sql&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgtam&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;/usr/local/pgsql/bin/psql&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;test.sql
&lt;span class="go"&gt;psql:test.sql:1: NOTICE:  drop cascades to 2 other objects&lt;/span&gt;
&lt;span class="go"&gt;DETAIL:  drop cascades to table x&lt;/span&gt;
&lt;span class="go"&gt;drop cascades to table y&lt;/span&gt;
&lt;span class="go"&gt;DROP EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE EXTENSION&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;CREATE TABLE&lt;/span&gt;
&lt;span class="go"&gt;INSERT 0 2&lt;/span&gt;
&lt;span class="go"&gt;  a&lt;/span&gt;
&lt;span class="go"&gt;-----&lt;/span&gt;
&lt;span class="go"&gt;  23&lt;/span&gt;
&lt;span class="go"&gt; 101&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(2 rows)&lt;/span&gt;

&lt;span class="go"&gt; ?column?&lt;/span&gt;
&lt;span class="go"&gt;----------&lt;/span&gt;
&lt;span class="go"&gt;      123&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1 row)&lt;/span&gt;

&lt;span class="go"&gt;  a  | count&lt;/span&gt;
&lt;span class="go"&gt;-----+-------&lt;/span&gt;
&lt;span class="go"&gt;  23 |     1&lt;/span&gt;
&lt;span class="go"&gt; 101 |     1&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(2 rows)&lt;/span&gt;

&lt;span class="go"&gt; b&lt;/span&gt;
&lt;span class="go"&gt;---&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(0 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pretty sweet!&lt;/p&gt;
&lt;h3 id="next-steps"&gt;Next steps&lt;/h3&gt;&lt;p&gt;It would be neat to build a storage engine that reads from and writes
to a CSV a la MySQL's CSV storage engine. Or a storage engine that
uses RocksDB.&lt;/p&gt;
&lt;p&gt;It would also be good to figure out how indexes work, how deletions
work, how updates and DDL beyond &lt;code&gt;CREATE&lt;/code&gt; works.&lt;/p&gt;
&lt;p&gt;And I should probably contribute some of this to the &lt;a href="https://www.postgresql.org/docs/current/tableam.html"&gt;table access
method&lt;/a&gt; docs
which are pretty sparse at the moment.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I've been working this week to understand Postgres Table Access Methods for alternative storage engines.&lt;br /&gt;&lt;br /&gt;Especially challenging because the documentation is pretty sparse and few minimal implementations exist.&lt;br /&gt;&lt;br /&gt;I wrote up my approach!&lt;a href="https://t.co/LQGglRkev5"&gt;https://t.co/LQGglRkev5&lt;/a&gt; &lt;a href="https://t.co/v0MeOu4Hbr"&gt;pic.twitter.com/v0MeOu4Hbr&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1719873793693221157?ref_src=twsrc%5Etfw"&gt;November 2, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 01 Nov 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-11-01-postgres-table-access-methods.html</guid></item><item><title>SDXL LoRA Training</title><link>https://smcleod.net/2023/10/sdxl-lora-training/</link><description>A talk I gave to some peers on creating your own SDXL LoRA models from my tinkering around over the last few weeks.</description><author>smcleod.net</author><pubDate>Mon, 30 Oct 2023 22:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/10/sdxl-lora-training/</guid></item><item><title>Partitioning The Index</title><link>https://www.marginalia.nu/log/92_multirack_drifting/</link><description>So a bit of an update on what I&amp;rsquo;ve been working on. This will be adapted into release notes in a while, but I haven&amp;rsquo;t quite wrapped a bow on the change set yet.
Still, it has certainly been a few weeks. Didn&amp;rsquo;t quite land how busy I&amp;rsquo;ve been until I set down to draft this post. Them&amp;rsquo;s some changes, and I&amp;rsquo;m skipping a few to keep this meandering post at a sane length.</description><author>Weblog on marginalia.nu</author><pubDate>Mon, 30 Oct 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/92_multirack_drifting/</guid></item><item><title>Hyperdimension Neptunia Re;Birth1 review</title><link>https://burakku.com/blog/hyperdimension-neptunia-rebirth1-review/</link><description>&lt;p&gt;&lt;img alt="Hyperdimension Neptunia Re;Birth1" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I can't believe it's not kusoge!&lt;/p&gt;
&lt;p&gt;The story of Hyperdimension Neptunia Re;Birth1 isn't really anything special. It's ultimately just kind of a generic save-the-world adventure story and the actual substance is in the (comedic) character interactions more than anything else. I don't think I can claim to have loved the story, but I did find it charming in its own way. At least I don't regret reading through it. The English translation seems a bit liberal, but not to a degree that it'd completely ruin the entire thing either.&lt;/p&gt;
&lt;p&gt;The combat is surprisingly good. There's kind of a good rhythm to the whole thing, where you first try to get a sneak attack in the overworld, then take turns in the battle to position yourself optimally, chip away at the enemy's shields and then deal heavy damage to them. During boss fights you would also look to optimise the mileage you get out of your powerful EX skills too. It's also fairly fast-paced, with the possibility to skip most of the animations, so you can really charge through your battles.&lt;/p&gt;
&lt;p&gt;And boy is it a good thing the combat feels as good as it does since this game leans very heavily on grinding to pad out the game. I don't think there's any way to get through the story without grinding. You just won't get the levels to clear all the level check bosses. I had trouble beating a couple of the bosses, both early in the game and late in the game, and when I searched for tips in case I missed something, most of the advice on the Internet was "grind more". So if there's a way to beat the game without grinding, it went unnoticed by me and seemingly the rest of the Internet too.&lt;/p&gt;
&lt;p&gt;The grinding also extends to the achievements. About half of the achievements for this game require grinding, be it for character levels or money. The amount of grinding is pretty mental. Really has this "low budget, must stretch out the game somehow" thing written all over it.&lt;/p&gt;
&lt;p&gt;Not to say that there's no aspect for battle tactics and gameplay. For example, I lost to a boss, restarted the fight and then won the second bout. No grinding needed in the between the attempts. However, I'm not sure if the victory game down to skills or just luck. I feel like there's a limited amount of things you can do in regards to your tactics when the boss has the ability to insta-kill two members from your three-member party. It's quite possible that the boss just unleashed less of its wave of death area-of-effect attacks on my party on the latter attempt.&lt;/p&gt;
&lt;p&gt;The rest of the game mechanics are kinda lame. The quests feel like they were added to check a checkbox, same as with the colosseum. And the share balancing act that you need to do in order to unlock the true ending or to unlock the little sisters is also not amazing, especially since it'll involve those quests and colosseum. Not massively time consuming thankfully, but still a bit of a mindless grind, doing the same things again and again to shift some percentages around.&lt;/p&gt;
&lt;p&gt;Technically the game isn't the best. While I do like the art, despite the fact that the game reuses assets like it's going out of fashion, the graphics felt so bad and blurry at the beginning that I felt the need to install the &lt;a href="https://github.com/tlaik/neptastic/"&gt;Neptastic Mod&lt;/a&gt; on my Steam Deck. That did certainly help the game look nicer, although the FPS would not necessarily always be a stable 60 FPS. It also felt like the FPS would gradually get worse as I kept the game open as the Steam Deck was asleep. I think I went from around 40 FPS to 60 FPS in the same dungeon by just simply restarting the game. Deck users beware.&lt;/p&gt;
&lt;p&gt;But in general, Hyperdimension Neptunia Re;Birth1 does run pretty well on a Steam Deck. Maybe not as well as you'd expect considering that this is originally a PlayStation Vita game, but well enough even with the modding. Combined with the fast-paced combat and heavy focus on grinding, it makes for a pretty decent portable time killer when you have a couple of minutes to do something. Not really sure if I'd ever want to actually sit in front of my gaming PC and play through this game. It just doesn't feel like that kind of a game (quite possibly due to its Vita roots), which is why I played it from start to finish on the Steam Deck. Fairly certain even that I beat most of the game while sitting on the toilet.&lt;/p&gt;
&lt;p&gt;Hyperdimension Neptunia Re;Birth1 is a bit of a messy game with good core gameplay loop and enough charm to keep you around. For the couple of euro I paid for it, it did make for some good portable entertainment. Just don't really expect much beyond that from it.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sat, 28 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/hyperdimension-neptunia-rebirth1-review/</guid></item><item><title>The contest of wills begins</title><link>https://mikewarot.blogspot.com/2023/10/the-contest-of-wills-begins.html</link><description>&lt;p&gt;&amp;nbsp;So, this morning when I click on my morning link to &lt;a href="https://twitter.com/PeterZeihan" target="_blank"&gt;Peter Zeihan&lt;/a&gt;, I've been greeted with this, instead of him talking from some new location.&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEigrcpJAHtKUdi8OQ7n3m4rEgeuOmqjhFPpOqghC1cF39pWGdRYTrpbcpoGi8GRv6U4TZxpOIXTAxohJx07_llSSLpqeq3PeVEkW8gTH47OzEWRoHzkJl7Os0H3ZxVtZfiwA9PPogx-TspyZw7sC_9zgDuu5JBec0jWTN-RxYHIZvOxYrFiUWnv" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEigrcpJAHtKUdi8OQ7n3m4rEgeuOmqjhFPpOqghC1cF39pWGdRYTrpbcpoGi8GRv6U4TZxpOIXTAxohJx07_llSSLpqeq3PeVEkW8gTH47OzEWRoHzkJl7Os0H3ZxVtZfiwA9PPogx-TspyZw7sC_9zgDuu5JBec0jWTN-RxYHIZvOxYrFiUWnv" width="246" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;We've entered the contest of wills. Google/YouTube thinks that they can force ads down my throat in service of their customers(the advertisers), and is willing to block my ad blocker in an attempt to force my compliance.&lt;br /&gt;&lt;br /&gt;I'm sure there will eventually be a different platform to watch Peter's videos on... and the wait begins. We programmers/technicians have a superpower, we're willing to put up with a ton of grief to bend things to our will, it's how we route around the normal rules and get shit done.&lt;br /&gt;&lt;br /&gt;I curate my sources, and Peter is one of them, I'm not willing to pollute that stream of information with the Ads that Google wants to force feed me. It'll be interesting to see how this all works out.&lt;p&gt;&lt;/p&gt;</description><author>--Mike--</author><pubDate>Fri, 27 Oct 2023 14:53:03 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2023/10/the-contest-of-wills-begins.html</guid></item><item><title>JavaScript regular expressions cheatsheet and examples</title><link>https://learnbyexample.github.io/javascript-regexp-cheatsheet/</link><description>&lt;p align="center"&gt;&lt;img alt="sample railroad diagram of a regexp" src="/images/books/js_regexp_example.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Above diagram created using &lt;a href="https://jex.im/regulex"&gt;Regulex&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;p&gt;This blog post gives an overview of regular expression syntax and features supported by JavaScript. Examples have been tested on the Chrome/Chromium console and includes features not available in other browsers and platforms. This post is an excerpt from my &lt;a href="https://github.com/learnbyexample/learn_js_regexp"&gt;Understanding JavaScript RegExp&lt;/a&gt; book.&lt;/p&gt;
&lt;h2 id="elements-that-define-a-regular-expression"&gt;Elements that define a regular expression&lt;a class="zola-anchor" href="#elements-that-define-a-regular-expression"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Note&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions"&gt;MDN: Regular Expressions&lt;/a&gt;&lt;/td&gt;&lt;td&gt;MDN reference for JavaScript regular expressions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/pat/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;a RegExp object&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;const pet = /dog/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;save regexp in a variable for reuse, clarity, etc&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/pat/.test(s)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;check if the pattern is present anywhere in the input string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;returns &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;i&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to ignore case when matching alphabets&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;g&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to match all occurrences&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;new RegExp('pat', 'i')&lt;/code&gt;&lt;/td&gt;&lt;td&gt;construct RegExp from a string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;optional second argument specifies flags&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use backtick strings with &lt;code&gt;${}&lt;/code&gt; for interpolation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;source&lt;/code&gt;&lt;/td&gt;&lt;td&gt;property to convert a RegExp object to a string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;helps to insert a RegExp inside another RegExp&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;flags&lt;/code&gt;&lt;/td&gt;&lt;td&gt;property to get flags of a RegExp object&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s.replace(/pat/, 'repl')&lt;/code&gt;&lt;/td&gt;&lt;td&gt;method for search and replace&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s.search(/pat/)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;gives the starting location of the match or &lt;code&gt;-1&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s.split(/pat/)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;split a string based on regexp&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Anchors&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;^&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the start of string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;$&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the end of string&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to match the start/end of line with &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; anchors&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;\r&lt;/code&gt;, &lt;code&gt;\n&lt;/code&gt;, &lt;code&gt;\u2028&lt;/code&gt; and &lt;code&gt;\u2029&lt;/code&gt; are line separators&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;DOS-style files use &lt;code&gt;\r\n&lt;/code&gt;, may need special attention&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\b&lt;/code&gt;&lt;/td&gt;&lt;td&gt;restricts the match to the start and end of words&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;word characters: alphabets, digits, underscore&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\B&lt;/code&gt;&lt;/td&gt;&lt;td&gt;matches wherever &lt;code&gt;\b&lt;/code&gt; doesn't match&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;^&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt; and &lt;code&gt;\&lt;/code&gt; are &lt;strong&gt;metacharacters&lt;/strong&gt; in the above table, as these characters have a special meaning. Prefix a &lt;code&gt;\&lt;/code&gt; character to remove the special meaning and match such characters literally. For example, &lt;code&gt;\^&lt;/code&gt; will match a &lt;code&gt;^&lt;/code&gt; character instead of acting as an anchor.&lt;/p&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Feature&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pat1|pat2|pat3&lt;/code&gt;&lt;/td&gt;&lt;td&gt;multiple regexp combined as conditional OR&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;each alternative can have independent anchors&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;group pattern(s), also a capturing group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;a(b|c)d&lt;/code&gt;&lt;/td&gt;&lt;td&gt;same as &lt;code&gt;abd|acd&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?:pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;non-capturing group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?&amp;lt;name&amp;gt;pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;named capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match any character except line separators&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to match line separators as well&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;character class, matches one character among many&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Alternation precedence: pattern which matches earliest in the input gets higher priority. Tie-breaker is left-to-right if matches have the same starting location.&lt;/p&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Greedy Quantifiers&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt; times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match &lt;code&gt;0&lt;/code&gt; or more times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;+&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match &lt;code&gt;1&lt;/code&gt; or more times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;{m,n}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match &lt;code&gt;m&lt;/code&gt; to &lt;code&gt;n&lt;/code&gt; times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;{m,}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match at least &lt;code&gt;m&lt;/code&gt; times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;{n}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match exactly &lt;code&gt;n&lt;/code&gt; times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pat1.*pat2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;any number of characters between &lt;code&gt;pat1&lt;/code&gt; and &lt;code&gt;pat2&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pat1.*pat2|pat2.*pat1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match both &lt;code&gt;pat1&lt;/code&gt; and &lt;code&gt;pat2&lt;/code&gt; in any order&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Greedy&lt;/strong&gt; here means that the above quantifiers will match as much as possible that'll also honor the overall regexp. Appending a &lt;code&gt;?&lt;/code&gt; to greedy quantifiers makes them &lt;strong&gt;non-greedy&lt;/strong&gt;, i.e. match as &lt;em&gt;minimally&lt;/em&gt; as possible. Quantifiers can be applied to literal characters, groups, backreferences and character classes.&lt;/p&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Character class&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[ae;o]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;match any of these characters once&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[3-7]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;range of characters from &lt;code&gt;3&lt;/code&gt; to &lt;code&gt;7&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[^=b2]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negated set, match other than &lt;code&gt;=&lt;/code&gt; or &lt;code&gt;b&lt;/code&gt; or &lt;code&gt;2&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[a-z-]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;-&lt;/code&gt; should be the first/last or escaped using &lt;code&gt;\&lt;/code&gt; to match literally&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[+^]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;^&lt;/code&gt; shouldn't be the first character or escaped using &lt;code&gt;\&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;[\]\\]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;]&lt;/code&gt; and &lt;code&gt;\&lt;/code&gt; should be escaped using &lt;code&gt;\&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[&lt;/code&gt; doesn't need escaping, but &lt;code&gt;\[&lt;/code&gt; can also be used&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\w&lt;/code&gt;&lt;/td&gt;&lt;td&gt;similar to &lt;code&gt;[A-Za-z0-9_]&lt;/code&gt; for matching word characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\d&lt;/code&gt;&lt;/td&gt;&lt;td&gt;similar to &lt;code&gt;[0-9]&lt;/code&gt; for matching digit characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\s&lt;/code&gt;&lt;/td&gt;&lt;td&gt;similar to &lt;code&gt;[ \t\n\r\f\v]&lt;/code&gt; for matching whitespace characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use &lt;code&gt;\W&lt;/code&gt;, &lt;code&gt;\D&lt;/code&gt;, and &lt;code&gt;\S&lt;/code&gt; for their opposites respectively&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;u&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to enable unicode matching&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;&lt;td&gt;superset of &lt;code&gt;u&lt;/code&gt; flag, enables additional features&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\p{}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Unicode character sets&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\P{}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negated Unicode character sets&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;see &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Unicode_character_class_escape"&gt;MDN: Unicode character class escape&lt;/a&gt; for details&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\u{}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;specify Unicode characters using codepoints&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Lookarounds&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;lookarounds&lt;/td&gt;&lt;td&gt;create custom positive/negative assertions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;zero-width like anchors and not part of matching portions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?!pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negative lookahead assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?&amp;lt;!pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;negative lookbehind assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?=pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;positive lookahead assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?&amp;lt;=pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;positive lookbehind assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;variable length lookbehind is allowed&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?!pat1)(?=pat2)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;multiple assertions can be specified next to each other in any order&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;as they mark a matching location without consuming characters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;((?!pat).)*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Negates a regexp pattern&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Matched portion&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m = s.match(/pat/)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;assuming the &lt;code&gt;g&lt;/code&gt; flag isn't used and regexp succeeds,&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;returns an array with the matched portion and 3 properties&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;index&lt;/code&gt; property gives the starting location of the match&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;input&lt;/code&gt; property gives the input string &lt;code&gt;s&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;groups&lt;/code&gt; property gives dictionary of named capture groups&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m[0]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;for the above case, gives the entire matched portion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;m[N]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;matched portion of the Nth capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;&lt;td&gt;flag to get the starting and ending locations of the matching portions via the &lt;code&gt;indices&lt;/code&gt; property&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s.match(/pat/g)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;returns only the matched portions, no properties&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;s.matchAll(/pat/g)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;returns an iterator containing details for each matched portion and its properties&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Backreference&lt;/td&gt;&lt;td&gt;gives the matched portion of the Nth capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt;, &lt;code&gt;$3&lt;/code&gt;, etc in the replacement section&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$&amp;amp;&lt;/code&gt; gives the entire matched portion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$`&lt;/code&gt; gives the string before the matched portion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$'&lt;/code&gt; gives the string after the matched portion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use &lt;code&gt;\1&lt;/code&gt;, &lt;code&gt;\2&lt;/code&gt;, &lt;code&gt;\3&lt;/code&gt;, etc within the regexp definition&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;$$&lt;/code&gt;&lt;/td&gt;&lt;td&gt;insert &lt;code&gt;$&lt;/code&gt; literally in the replacement section&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;$0N&lt;/code&gt;&lt;/td&gt;&lt;td&gt;same as &lt;code&gt;$N&lt;/code&gt;, allows to separate backreference and other digits&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\N\xhh&lt;/code&gt;&lt;/td&gt;&lt;td&gt;allows to separate backreference and digits in the regexp definition&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;(?&amp;lt;name&amp;gt;pat)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;named capture group&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use &lt;code&gt;\k&amp;lt;name&amp;gt;&lt;/code&gt; for backreferencing in the regexp definition&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;use &lt;code&gt;$&amp;lt;name&amp;gt;&lt;/code&gt; for backreferencing in the replacement section&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;h2 id="regular-expression-examples"&gt;Regular expression examples&lt;a class="zola-anchor" href="#regular-expression-examples"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;test()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sentence &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'This is a sample string'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/is/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sentence&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;true
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/xyz/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sentence&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;false
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; if &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/ring/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sentence&lt;/span&gt;&lt;span&gt;)) {
&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'mission success'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mission success
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;new RegExp()&lt;/code&gt; constructor&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; new &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;RegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'i'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/dog/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;i
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; new &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;RegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;tabc'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/123&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;abc/
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;greeting &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hi'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; new &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;RegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;`${&lt;/span&gt;&lt;span style="color: #acb3c2;"&gt;greeting&lt;/span&gt;&lt;span style="color: #d07711;"&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;toUpperCase&lt;/span&gt;&lt;span style="color: #d07711;"&gt;()} there`&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/HI there/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;string and line anchors&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// string anchors
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;cat/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cater'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;true
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'surrender'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'newer'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'door'&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/er&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;$&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'surrender'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'newer'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// use 'm' flag to match at the start/end of each line
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;$&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'spare&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;par&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;era&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;dare'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;true
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// escape metacharacters to match them literally
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/b&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;2/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a^2 + b^2 - C*3'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;true
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;replace()&lt;/code&gt; method and word boundaries&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;items &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'catapults&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;concatenate&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;cat'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;items&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;gm&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'* '&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; * &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;catapults
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;concatenate
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;cat
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sample &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par spar apparent spare part'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// replace 'par' only at the start of word
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sample&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X spar apparent spare Xt'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// replace 'par' at the end of word but not whole word 'par'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sample&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\B&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par sX apparent spare part'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;alternations and grouping&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// replace either 'cat' at the start of string or 'cat' at the end of word
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'catapults concatenate cat scat'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;cat&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;cat&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Xapults concatenate X sX'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// same as: /\bpark\b|\bpart\b/g
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'park parked part party'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par(k&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;t)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X parked X party'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping"&gt;MDN: Regular Expressions Guide&lt;/a&gt; provides the &lt;code&gt;escapeRegExp()&lt;/code&gt; function, useful to automatically escape metacharacters.
&lt;ul&gt;
&lt;li&gt;See also &lt;a href="https://github.com/slevithan/xregexp"&gt;XRegExp&lt;/a&gt;, provides handy methods like &lt;a href="https://xregexp.com/api/#escape"&gt;XRegExp.escape()&lt;/a&gt; and &lt;a href="https://xregexp.com/api/#union"&gt;XRegExp.union()&lt;/a&gt;. The union method has additional functionality of allowing a mix of string and RegExp literals and also takes care of renumbering backreferences.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;function &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;escapeRegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;string&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;string&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;.&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;*+?^${}()|[&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\]\\&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;$&amp;amp;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;function &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;unionRegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;arr&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;arr&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;escapeRegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'|'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; new &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;RegExp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;unionRegExp&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'c^t'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog$'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'f|x'&lt;/span&gt;&lt;span&gt;]), &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'g'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/c&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;t&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;dog&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\$&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;f&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;x/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;dot metacharacter and quantifiers&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// matches character '2', any character and then character '3'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;35'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/2&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;3/&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'8'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'485'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// 's' flag will allow line separators to be matched as well
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Hi there&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;Have a Nice Day'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/the&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;ice/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;s&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Hi X Day'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// same as: /part|parrot|parent/g
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par part parrot parent'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/par(en&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;ro)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;t/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par X X X'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'ac'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abbc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'xabbbcz'&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/ab&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{1,4}&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;c/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abbc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'xabbbcz'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;match()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// entire matched portion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'too soon a song snatch'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/so&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;n/&lt;/span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'soon'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// matched portion of the second capture group
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;purchase &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'coffee:100g tea:250g sugar:75g chocolate:50g'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;purchase&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)g&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)g&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;chocolate:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)g/&lt;/span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'250'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// starting location of the matching portion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat and dog'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/dog/&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;index
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;8
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// start and end+1 location of the matching portion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'awesome'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;so&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;d&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;indices&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// get all matching portions with 'g' flag
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// no properties or group portions
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par spar apparent spare part'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;s&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[et]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'spare'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'part'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// useful for debugging purposes as well
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'green:3.14:teal::brown:oh!:blue'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;:/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':3.14:'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'::'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':oh!:'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;matchAll()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// same as: match(/so*n/g)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'song too soon snatch'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;matchAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/so&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;n/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;])
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'son'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'soon'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'sn'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// get the starting index for each match
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'song too soon snatch'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;matchAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/so&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;n/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;index&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;9&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;14&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// get only the capture group portions as an array for each match
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2023/04,1986/Mar,'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;matchAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\/&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;),/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;) [&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;)]
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;) [&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2023'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'04'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;) [&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1986'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mar'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;length&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span&gt;  [[&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Prototype&lt;/span&gt;&lt;span&gt;]]: &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;Array&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;function/dictionary in the replacement section&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;function &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;titleCase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;g1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;g2&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;g1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;toUpperCase&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+ &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;g2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;toLowerCase&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aBc ac ADC aBbBC'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(a)(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;c)/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;ig&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;titleCase&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Abc Ac Adc Abbbc'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1 42 317'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;m&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2 84 634'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;swap &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat'&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tiger'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tiger'&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat' &lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat tiger dog tiger cat'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/cat&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;tiger/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;k &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;swap&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;k&lt;/span&gt;&lt;span&gt;])
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tiger cat dog cat tiger'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;split()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// split based on one or more digit characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample123string42with777numbers'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'string'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'with'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'numbers'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// include the portion that caused the split as well
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample123string42with777numbers'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)/&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'string'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'with'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'777'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'numbers'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// split based on digit or whitespace characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'**1&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\f&lt;/span&gt;&lt;span style="color: #d07711;"&gt;2&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;3star&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;7 77&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;**'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\d\s&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'**'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'star'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'**'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// use non-capturing group if capturing is not needed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123handed42handy777handful500'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/hand(?:y&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;ful)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'ed42'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'777'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'500'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;backreferencing with normal/non-capturing/named capture groups&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// remove any number of consecutive duplicate words separated by space
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// use \W+ instead of space to cover cases like 'a;a&amp;lt;-;a'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aa a a a 42 f_1 f_1 f_13.14'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)( &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\1&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$1'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aa a 42 f_1 f_13.14'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// add something around the entire matched portion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'52 apples and 31 mangoes'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'($&amp;amp;)'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(52) apples and (31) mangoes'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// duplicate the first field and add it as the last field
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fork,42,nice,3.14'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/,&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$&amp;amp;,$`'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fork,42,nice,3.14,fork'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// use non-capturing groups when backreferencing isn't needed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,4,5,6,7'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;((?:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;,)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3}&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)/&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$1($2)'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,(4),5,6,7'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// named capture groups, same as: replace(/(\w+),(\w+)/g, '$2,$1')
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'good,bad 42,24 x,y'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?&amp;lt;&lt;/span&gt;&lt;span style="color: #acb3c2;"&gt;fw&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;),(?&amp;lt;&lt;/span&gt;&lt;span style="color: #acb3c2;"&gt;sw&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$&amp;lt;sw&amp;gt;,$&amp;lt;fw&amp;gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'bad,good 24,42 y,x'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;examples for lookarounds&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// change 'cat' only if it is not followed by a digit character
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// note that the end of string satisfies the given assertion
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// 'catcat' has two matches as the assertion doesn't consume characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hey cats! cat42 cat_5 catcat'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/cat(?!&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hey dogs! cat42 dog_5 dogdog'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// change whole word only if it is not preceded by : or --
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':cart apple --rest ;tea'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?&amp;lt;!:&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;--)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':cart X --rest ;X'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// extract digits only if it is preceded by - and followed by ; or :
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 apple-5, fig3; x-83, y-20: f12'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?&amp;lt;=-)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;(?=&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[;:]&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;g&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'20'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// words containing all lowercase vowels in any order
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;words &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'sequoia'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'questionable'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'exhibit'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'equation'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;words&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #c23f31;"&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w &lt;/span&gt;&lt;span style="color: #668f14;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?=&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;a)(?=&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;e)(?=&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;i)(?=&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;o)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;u/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;w&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'sequoia'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'questionable'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'equation'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// replace only the third occurrence of 'cat'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat scatter cater scat'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?&amp;lt;=(cat&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{2}&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)cat/&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat scatter Xer scat'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// match if 'do' is not there between 'at' and 'par'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/at((?!do)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;par/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox,cat,dog,parrot'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;u&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; flags&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ts " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ts"&gt;&lt;span style="color: #7f8989;"&gt;// extract all consecutive letters, use \P{L} to invert the set
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox:αλεπού,eagle:αετός'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\p&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;{L}&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;gu&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'αλεπού'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'eagle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'αετός'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// extract all consecutive Greek letters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox:αλεπού,eagle:αετός'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\p&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;{sc=Greek}&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;gu&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'αλεπού'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'αετός'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// extract whole words not surrounded by punctuation marks
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tie. ink east;'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/(?&amp;lt;!&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\p&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;{P})&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+\b&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;(?!&lt;/span&gt;&lt;span style="color: #108f3d;"&gt;\p&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;{P})/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;gu&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'ink'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;// remove all punctuation characters except . ! and ?
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;let &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;para &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&amp;quot;Hi&amp;quot;, there! How *are* you? All fine here.'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;para&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;[\&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;p&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;P&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;[.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;!?&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+/&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gv&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Hi there! How are you? All fine here.'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="debugging-and-visualization-tools"&gt;Debugging and Visualization tools&lt;a class="zola-anchor" href="#debugging-and-visualization-tools"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As your regexp gets complicated, it can get difficult to debug when you run into issues. Building your regexp step by step from scratch and testing against input strings will go a long way in correcting the problem. To aid in such a process, you could use &lt;a href="https://news.ycombinator.com/item?id=20614847"&gt;various online regexp tools&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://regex101.com/r/HSeO0z/1"&gt;regex101&lt;/a&gt; is a popular site to test your regexp. You'll have to first choose the flavor as JavaScript. Then you can add your regexp, input strings, choose flags and an optional replacement string. Matching portions will be highlighted and explanation is offered in separate panes. There's also a quick reference and other features like link sharing, code generator, quiz, cheatsheet, etc.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="regex101 example" src="/images/books/regex101.png" /&gt;&lt;/p&gt;
&lt;p&gt;Another useful tool is &lt;a href="https://jex.im/regulex/#!flags=&amp;amp;re=%5Cbpar(en%7Cro)%3Ft%5Cb"&gt;jex: regulex&lt;/a&gt; which converts your regexp to a railroad diagram, thus providing a visual aid to understanding the pattern.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="regulex example" src="/images/books/regulex.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="understanding-javascript-regexp-book"&gt;Understanding JavaScript RegExp book&lt;a class="zola-anchor" href="#understanding-javascript-regexp-book"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit my repo &lt;a href="https://github.com/learnbyexample/learn_js_regexp"&gt;learn_js_regexp&lt;/a&gt; for details about the book I wrote on JavaScript regular expressions. The ebook uses plenty of examples to explain the concepts from the basics and includes &lt;a href="https://github.com/learnbyexample/learn_js_regexp/blob/master/Exercises.md"&gt;exercises&lt;/a&gt; to test your understanding. The cheatsheet and examples presented in this post are based on the contents of this book.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Understanding JavaScript RegExp cover image" height="360px" src="https://raw.githubusercontent.com/learnbyexample/learn_js_regexp/master/images/js_regexp_ls.png" width="640px" /&gt;&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Fri, 27 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/javascript-regexp-cheatsheet/</guid></item><item><title>Understanding JavaScript RegExp book announcement</title><link>https://learnbyexample.github.io/understanding-javascript-regexp-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I just published a new version of &amp;quot;&lt;strong&gt;Understanding JavaScript RegExp&lt;/strong&gt;&amp;quot; ebook. Added examples for &lt;code&gt;d&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; flags, corrected many mistakes, improved examples, exercises and so on.&lt;/p&gt;
&lt;p&gt;This book will help you learn &lt;strong&gt;JavaScript Regular Expressions&lt;/strong&gt; step-by-step from beginner to advanced levels with &lt;strong&gt;hundreds of examples and exercises&lt;/strong&gt;.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of &lt;strong&gt;Understanding JavaScript RegExp&lt;/strong&gt; for FREE till 05-Nov-2023. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/js_regexp"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/js_regexp/c/new_js_regexp_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;All Books Bundle&lt;/strong&gt; is just $12 (normal price $32) — includes all my 13 programming ebooks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/new_js_regexp_release"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/learnbyexample-all-books/c/new_js_regexp_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Examples and exercises added for &lt;code&gt;d&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; flags&lt;/li&gt;
&lt;li&gt;Strings in code snippets changed to be uniformly represented in single quotes&lt;/li&gt;
&lt;li&gt;In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;Book title changed to &lt;strong&gt;Understanding JavaScript RegExp&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;li&gt;Images centered for EPUB format&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;On this blog, I &lt;a href="https://learnbyexample.github.io/tips/"&gt;post tips&lt;/a&gt; covering Python, command line tools and Vim. Here are video demos for these tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=THSMmCZQn1A&amp;amp;list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&amp;amp;list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Literally was having a mini-breakdown about not understanding Regex in algorithm solutions the other day and now I'm feeling so much better, so thank YOU! I genuinely feel like I'm developing the skill for spotting when and where to use them after so much practice!&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://twitter.com/codingwithlucy/status/1450668315635036160"&gt;feedback on twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Why is it needed?&lt;/li&gt;
&lt;li&gt;RegExp introduction&lt;/li&gt;
&lt;li&gt;Anchors&lt;/li&gt;
&lt;li&gt;Alternation and Grouping&lt;/li&gt;
&lt;li&gt;Escaping metacharacters&lt;/li&gt;
&lt;li&gt;Dot metacharacter and Quantifiers&lt;/li&gt;
&lt;li&gt;Interlude: Tools for debugging and visualization&lt;/li&gt;
&lt;li&gt;Working with matched portions&lt;/li&gt;
&lt;li&gt;Character class&lt;/li&gt;
&lt;li&gt;Groupings and backreferences&lt;/li&gt;
&lt;li&gt;Interlude: Common tasks&lt;/li&gt;
&lt;li&gt;Lookarounds&lt;/li&gt;
&lt;li&gt;Unicode&lt;/li&gt;
&lt;li&gt;Further Reading&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/learn_js_regexp/"&gt;https://learnbyexample.github.io/learn_js_regexp/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/learn_js_regexp"&gt;https://github.com/learnbyexample/learn_js_regexp&lt;/a&gt; for markdown source, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/learn_js_regexp/issues"&gt;https://github.com/learnbyexample/learn_js_regexp/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;learn by example.net@gmail.com&lt;/code&gt; (without the spaces)&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 26 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/understanding-javascript-regexp-announcement/</guid></item><item><title>Kenmore Dryer Repair</title><link>http://pxtl.ca/2023/10/24/dryer-repair/</link><description>&lt;p&gt;Once again, everything is made of garbage.&lt;/p&gt;

&lt;p&gt;&lt;img alt="failed heating coil in dryer" src="http://pxtl.ca/images/2023-10-24-dryer-repair/failed-coil.jpg" /&gt;&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Tue, 24 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/10/24/dryer-repair/</guid></item><item><title>Embed only the video from another post on X or Twitter</title><link>https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/</link><description>&lt;p&gt;Twitter has a new-ish feature that lets you embed only the video from another post or tweet in a post/tweet of your own (without quote-replying the source tweet itself). Only the video is then embedded in your post, and a &amp;#8230; &lt;a href="https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/"&gt;Embed only the video from another post on X or Twitter&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Sun, 22 Oct 2023 20:33:56 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/</guid></item><item><title>Four layers of self-improvement</title><link>https://jodavaho.io/posts/four-layers-self-improvement.html</link><description>&lt;p&gt;I&amp;rsquo;ve been tracking my OKR&amp;rsquo;s since 2014. Sometimes I accomplish what I set out
for, sometimes I don&amp;rsquo;t. Sometimes I accomplish &lt;em&gt;what I put in OKRs&lt;/em&gt; but didn&amp;rsquo;t
accomplish what I wanted.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I&amp;rsquo;ve learned.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There are usually four types of OKRs, and not differentiating types makes it &lt;em&gt;really hard&lt;/em&gt; to get what you wanted&lt;/li&gt;
&lt;li&gt;Not only are there four, but they are roughly speaking prerequisites of each other.&lt;/li&gt;
&lt;li&gt;Not only that, but looking at all the self-help books I&amp;rsquo;ve read, it actually seems like most the books &lt;em&gt;also&lt;/em&gt; fit into these four categories, and just reading them out of order makes no sense.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These categories are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Intent-building (actions that create action). Perhaps these are better known as &amp;ldquo;values&amp;rdquo;. When lost, you feel listless.&lt;/li&gt;
&lt;li&gt;Foundational stuff (actions that everyone needs to do to be &amp;ldquo;better&amp;rdquo;). Basic self care is here. When lost, you feel unhealthy and &amp;ldquo;stuck&amp;rdquo; even if you have good intent.&lt;/li&gt;
&lt;li&gt;Focus activities (the stuff you need to realize your specific vision of self). Family, job duties, social activities, etc are here. These &amp;ldquo;round out&amp;rdquo; an otherwise healthy person.&lt;/li&gt;
&lt;li&gt;Specialized activities (the stuff that really defines who you are). These are the things you are proud of. Resume-level or hobby-level things like &amp;ldquo;I love boxing&amp;rdquo; or &amp;ldquo;I love making cabinets&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So my strategy going forward is to work from the bottom up. If I find a day where I can&amp;rsquo;t get anything done, I&amp;rsquo;ll at least work on intent. Maybe it seems obvious, but just focusing on what I want and planning a better tomorrow makes tomorrow much easier. And focusing on intent allows you to remember throughout the day to work on your goals. Lately, just taking a break from work during the day to move an inch forward is a huge help. For example 10 pullups between meetings.&lt;/p&gt;
&lt;p&gt;Anyway, I will shortly analyze 3rd quarter, but for now, this idea of layering on improvements is something I&amp;rsquo;m noodling as I redefine what success looks like.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Sun, 22 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/four-layers-self-improvement.html</guid></item><item><title>io_uring basics: Writing a file to disk</title><link>http://notes.eatonphil.com/2023-10-19-write-file-to-disk-with-io_uring.html</link><description>&lt;p&gt;King and I &lt;a href="https://tigerbeetle.com/blog/a-friendly-abstraction-over-iouring-and-kqueue/"&gt;wrote a blog
post&lt;/a&gt;
about building an event-driven cross-platform IO library that used
io_uring on Linux. We sketched out how it works at a high level but I
hadn't yet internalized how you actually code with io_uring. So I
strapped myself down this week and wrote &lt;a href="https://github.com/eatonphil/io-playground"&gt;some
benchmarks&lt;/a&gt; to build my
intuition about io_uring and other IO models.&lt;/p&gt;
&lt;p&gt;I started with implementations in Go and ported them to Zig to make
sure I had done the Go versions decently. And I got some help from
King and other internetters to find some inefficiencies in my code.&lt;/p&gt;
&lt;p&gt;This post will walk through my process, getting increasingly efficient
(and a little increasingly complex) ways to write an entire file to
disk with io_uring, from Go and Zig.&lt;/p&gt;
&lt;p&gt;Notably, we're not going to &lt;code&gt;fsync()&lt;/code&gt; and we're not going to use
&lt;code&gt;O_DIRECT&lt;/code&gt;. So we won't be testing the entire IO pipeline from
userland to disk hardware but just how fast IO gets to the kernel. The
focus of this post is more on IO methods and using io_uring, not
absolute numbers.&lt;/p&gt;
&lt;p&gt;All code for this post is &lt;a href="https://github.com/eatonphil/io_uring-basics-writing-file"&gt;available on
GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  This code is going to indirectly show some differences in timing
  between Go and Zig. I could care less about benchmarketing. And I
  hope something about Zig vs Go is not what you take away from this
  post either.
  &lt;br /&gt;
  &lt;br /&gt;
  The goal is to build an intuition and be generally
  correct. Observing the same relative behavior between
  implementations across two languages helps me gain confidence what
  I'm doing is correct.
&lt;/p&gt;&lt;h3 id="io_uring"&gt;io_uring&lt;/h3&gt;&lt;p&gt;With normal blocking syscalls you just call &lt;code&gt;read()&lt;/code&gt; or &lt;code&gt;write()&lt;/code&gt; and
wait for the results. io_uring is one of Linux's more powerful
&lt;em&gt;asynchronous&lt;/em&gt; IO offerings. Unlike epoll, you can use io_uring with
both files and network connections. And unlike epoll you can even have
the syscall run in the kernel.&lt;/p&gt;
&lt;p&gt;To interact with io_uring, you register a submission queue for syscalls
and their arguments. And you register a completion queue for syscall
results.&lt;/p&gt;
&lt;p&gt;You can batch many syscalls in one single call to io_uring,
effectively turning up to N (4096 at most) syscalls into just one
syscall. The kernel still does all the work of the N syscalls but you
avoid some overhead.&lt;/p&gt;
&lt;p&gt;As you check the completion queue and handle completed submissions,
the submission queue is also freed all or somewhat, and you can now
add more submissions.&lt;/p&gt;
&lt;p&gt;For a more complete understanding, check out the kernel document
&lt;a href="https://kernel.dk/io_uring.pdf"&gt;Efficient IO with io_uring&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="io_uring-vs-liburing"&gt;io_uring vs liburing&lt;/h3&gt;&lt;p&gt;io_uring is a complex, low-level interface. Shuveb Hussain has &lt;a href="https://unixism.net/2020/04/io-uring-by-example-part-1-introduction/"&gt;an
excellent
series&lt;/a&gt;
on programming io_uring. But that was too low-level for me as I was
trying to figure out how to just get something working.&lt;/p&gt;
&lt;p&gt;Instead, most people use &lt;a href="https://github.com/axboe/liburing"&gt;liburing&lt;/a&gt;
or a ported version of it like &lt;a href="https://github.com/ziglang/zig/blob/master/lib/std/os/linux/io_uring.zig"&gt;the Zig standard library's
io_uring.zig&lt;/a&gt;
or &lt;a href="https://github.com/Iceber/iouring-go"&gt;Iceber's iouring-go&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;io_uring started clicking for me when I tried out the iouring-go
library. So we'll start there.&lt;/p&gt;
&lt;h3 id="boilerplate"&gt;Boilerplate&lt;/h3&gt;&lt;p&gt;First off, let's set up some boilerplate for the Go and Zig code.&lt;/p&gt;
&lt;p&gt;In main.go add:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bytes&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;assert&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="nx"&gt;read&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="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;out.bin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_RDWR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_TRUNC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mo"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;t1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;,%f,%f\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;out.bin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in main.zig add:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&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;&amp;quot;std&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OUT_FILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;out.bin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &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;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nwritten&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;@memcpy&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;written&lt;/span&gt;&lt;span class="p"&gt;..],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&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;nwritten&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nwritten&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&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;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&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="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getStdOut&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{s}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;createFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OUT_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;truncate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@floatFromInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ns_per_s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getStdOut&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;,{d},{d}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@floatFromInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&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;len&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OUT_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&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;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&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="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="keep-it-simple:-write()"&gt;Keep it simple: write()&lt;/h3&gt;&lt;p&gt;Now let's add the naive version of writing bytes to disk: calling
&lt;code&gt;write()&lt;/code&gt; repeatedly until all data has been written to disk.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;main.go&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;104857600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 100MiB&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;readNBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/random&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RUNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RUNS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;blocking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in &lt;code&gt;main.zig&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_allocator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;104857600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 100MiB&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readNBytes&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="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/random&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RUNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RUNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;blocking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&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;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's build and run these programs and store the results to CSV we
can analyze with DuckDB.&lt;/p&gt;
&lt;p&gt;Go first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;main.go&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;gomain
$&lt;span class="w"&gt; &lt;/span&gt;./gomain&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;go.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'go.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.07251540000000001s&lt;/td&gt;
&lt;td&gt;1.4GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And Zig:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'zig.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.0656907669s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Alright, we've got a baseline now and both language implementations
are in the same ballpark.&lt;/p&gt;
&lt;p&gt;Let's add a simple io_uring version!&lt;/p&gt;
&lt;h3 id="io_uring,-1-entry,-go"&gt;io_uring, 1 entry, Go&lt;/h3&gt;&lt;p&gt;The &lt;a href="https://github.com/Iceber/iouring-go#quickstart"&gt;iouring-go&lt;/a&gt;
library has really excellent documentation for getting started.&lt;/p&gt;
&lt;p&gt;To keep it simple, we'll use io_uring with only 1 entry. Add the
following to &lt;code&gt;func main()&lt;/code&gt; after the existing &lt;code&gt;benchmark()&lt;/code&gt; call in
&lt;code&gt;main.go&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;io_uring&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iouring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;len&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;len&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="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;prepRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iouring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&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;Fd&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;&lt;span class="w"&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;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint64&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubmitRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prepRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReturnInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that &lt;code&gt;benchmark&lt;/code&gt; takes care of &lt;code&gt;f.Seek(0)&lt;/code&gt; before each run. And
it also validates that the file contents are equivalent to the input
&lt;code&gt;data&lt;/code&gt;. So it validates the benchmark for correctness.&lt;/p&gt;
&lt;p&gt;Alright, let's run this new Go implementation with io_uring!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;mod&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;gomain
$&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;mod&lt;span class="w"&gt; &lt;/span&gt;tidy
$&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;main.go&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;gomain
$&lt;span class="w"&gt; &lt;/span&gt;./gomain&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;go.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'go.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.0811486s&lt;/td&gt;
&lt;td&gt;1.3GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;io_uring&lt;/td&gt;
&lt;td&gt;0.5083049999999999s&lt;/td&gt;
&lt;td&gt;213.2MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Well that looks terrible.&lt;/p&gt;
&lt;p&gt;Let's port it to Zig to see if we notice the same behavior there.&lt;/p&gt;
&lt;h3 id="io_uring,-1-entry,-zig"&gt;io_uring, 1 entry, Zig&lt;/h3&gt;&lt;p&gt;There isn't an official Zig tutorial on io_uring I'm aware of. But
&lt;a href="https://github.com/ziglang/zig/blob/master/lib/std/os/linux/io_uring.zig"&gt;io_uring.zig&lt;/a&gt;
is easy enough to browse through. And there are tests in that file
that also show how to use it.&lt;/p&gt;
&lt;p&gt;And now that we've explored a bit in Go the basic gist should be
similar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;initialize io_uring&lt;/li&gt;
&lt;li&gt;submit an entry&lt;/li&gt;
&lt;li&gt;wait for it to finish&lt;/li&gt;
&lt;li&gt;move on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add the following to &lt;code&gt;fn main()&lt;/code&gt; after the existing benchmark block in
&lt;code&gt;main.zig&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;iouring&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IO_Uring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit_and_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_cqe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@intCast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'zig.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.06650093630000001s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;io_uring&lt;/td&gt;
&lt;td&gt;0.17542890139999998s&lt;/td&gt;
&lt;td&gt;597.7MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Well it's similarly pretty bad. But our implementation ignores one
major aspect of io_uring: batching requests.&lt;/p&gt;
&lt;p&gt;Let's do some refactoring!&lt;/p&gt;
&lt;h3 id="io_uring,-n-entries,-go"&gt;io_uring, N entries, Go&lt;/h3&gt;&lt;p&gt;To support submitting N entries, we're going to have an inner loop
running up to N that fills up N entries to io_uring.&lt;/p&gt;
&lt;p&gt;Then we'll wait for the N submissions to complete and check their
results.&lt;/p&gt;
&lt;p&gt;We'll keep going until we write the entire file.&lt;/p&gt;
&lt;p&gt;All of this can stay inside the loop in &lt;code&gt;main&lt;/code&gt;, I'm just dropping
preceding whitespace for nicer formatting here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nx"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;io_uring_%d_entries&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iouring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nx"&gt;iouring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PrepRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;submittedEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;submittedEntries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iouring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fd&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;submittedEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SubmitRequests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="nx"&gt;submittedEntries&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ErrResults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReturnInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There are some specific things in there to notice.&lt;/p&gt;
&lt;p&gt;First, toward the end of the file we may not have &lt;code&gt;N&lt;/code&gt; entries to
submit. We may have &lt;code&gt;1&lt;/code&gt; or even &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If we have &lt;code&gt;0&lt;/code&gt; to submit, we need to not even submit anything
otherwise the Go library hangs. Similarly, if we don't slice
&lt;code&gt;requests&lt;/code&gt; to &lt;code&gt;requests[:submittedEntries]&lt;/code&gt;, the Go library will
segfault if &lt;code&gt;submittedEntries &amp;lt; N&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Other than that, let's build and run this!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;gomain
$&lt;span class="w"&gt; &lt;/span&gt;./gomain&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;go.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'go.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.0740368s&lt;/td&gt;
&lt;td&gt;1.4GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;io_uring_128_entries&lt;/td&gt;
&lt;td&gt;0.127519s&lt;/td&gt;
&lt;td&gt;836.6MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;io_uring_1_entries&lt;/td&gt;
&lt;td&gt;0.46831579999999995s&lt;/td&gt;
&lt;td&gt;226.9MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now we're getting somewhere! Still half the throughput but a 4x
improvement from using only a single entry.&lt;/p&gt;
&lt;p&gt;Let's port the N entry code to Zig.&lt;/p&gt;
&lt;h3 id="io_uring,-n-entries,-zig"&gt;io_uring, N entries, Zig&lt;/h3&gt;&lt;p&gt;Unlike Go we can't do closures, so we'll have to make
&lt;code&gt;benchmarkIOUringNEntries&lt;/code&gt; a top-level function and keep the calls to
it in the loop in &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_allocator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;104857600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 100MiB&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readNBytes&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="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/random&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RUNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RUNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;blocking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&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;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;benchmarkIOUringNEntries&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;benchmarkIOUringNEntries&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And for the implementation itself, the only two big differences from
the first version are that we'll bulk-read completion events (&lt;code&gt;cqe&lt;/code&gt;s)
and that we'll create and wait for many submissions at once.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&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="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;iouring_{}_entries&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IO_Uring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alloc&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io_uring_cqe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit_and_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;waited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_cqes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&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;submitted&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;waited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&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;submitted&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|*&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@intCast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'zig.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.0674331114s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iouring_128_entries&lt;/td&gt;
&lt;td&gt;0.06773539590000001s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iouring_1_entries&lt;/td&gt;
&lt;td&gt;0.1855542556s&lt;/td&gt;
&lt;td&gt;569.9MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Huh, that's surprising! We caught up to blocking writes with io_uring
in Zig, but not in Go, even though we made good progress in Go.&lt;/p&gt;
&lt;h3 id="ring-buffers"&gt;Ring buffers&lt;/h3&gt;&lt;p&gt;But we can do a bit better. We're doing batching, but the API is
called "io_uring" not "io_batch". We're not even making use of the
ring buffer behavior io_uring gives us!&lt;/p&gt;
&lt;p&gt;We are waiting for all submitted results complete. But there's no
reason to do that. Instead we should submit as much as we can. But we
should not block waiting for completions. We should handle completions
when they happen. And we should retry submissions until we're done
reading. Retrying if there's no space for the moment.&lt;/p&gt;
&lt;p&gt;Unfortunately the Go library doesn't seem to expose this ring behavior
of io_uring. Or I've missed it.&lt;/p&gt;
&lt;p&gt;But we can do it in Zig. Let's go.&lt;/p&gt;
&lt;h3 id="io_uring,-ring-buffer,-zig"&gt;io_uring, ring buffer, Zig&lt;/h3&gt;&lt;p&gt;We need to change the way we track which offsets we need to submit so
far. We also need to keep the loop going until we are sure we have
&lt;em&gt;written&lt;/em&gt; all data. And we need to stop blocking on the number we
submitted; never blocking at all.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;benchmarkIOUringNEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&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="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;iouring_{}_entries&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IO_Uring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alloc&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io_uring_cqe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubmissionQueueFull&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;submittedEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit_and_wait&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="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cqesDone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_cqes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqes&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;cqesDone&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|*&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@intCast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The code got a bit simpler! Granted, we're omitting error handling.&lt;/p&gt;
&lt;p&gt;Build and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'zig.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iouring_128_entries&lt;/td&gt;
&lt;td&gt;0.06035423609999999s&lt;/td&gt;
&lt;td&gt;1.7GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iouring_1_entries&lt;/td&gt;
&lt;td&gt;0.0610197624s&lt;/td&gt;
&lt;td&gt;1.7GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.0671628515s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Not bad!&lt;/p&gt;
&lt;h3 id="crank-it-up"&gt;Crank it up&lt;/h3&gt;&lt;p&gt;We've been inserting 100MiB of data. Let's go up to 1GiB to see how
that affects things. Ideally the more data we write the more we
reflect realistic long-term results.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;main.zig&lt;/code&gt; just change &lt;code&gt;SIZE&lt;/code&gt; to &lt;code&gt;1073741824&lt;/code&gt;. Rebuild and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'out.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iouring_128_entries&lt;/td&gt;
&lt;td&gt;0.6063814535s&lt;/td&gt;
&lt;td&gt;1.7GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iouring_1_entries&lt;/td&gt;
&lt;td&gt;0.6167537295000001s&lt;/td&gt;
&lt;td&gt;1.7GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.6831747749s&lt;/td&gt;
&lt;td&gt;1.5GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;No real difference, perfect!&lt;/p&gt;
&lt;p&gt;Let's make one more change though. Let's up the &lt;code&gt;BUFFER_SIZE&lt;/code&gt; from
4KiB to 1MiB.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;zig.csv
$&lt;span class="w"&gt; &lt;/span&gt;duckdb&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select column0 as method, avg(cast(column1 as double)) || 's' avg_time, format_bytes(avg(column2::double)::bigint) || '/s' as avg_throughput from 'out.csv' group by column0 order by avg(cast(column1 as double)) asc&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;method&lt;/th&gt;
&lt;th&gt;avg_time&lt;/th&gt;
&lt;th&gt;avg_throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iouring_128_entries&lt;/td&gt;
&lt;td&gt;0.2756831357s&lt;/td&gt;
&lt;td&gt;3.8GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iouring_1_entries&lt;/td&gt;
&lt;td&gt;0.27575404880000004s&lt;/td&gt;
&lt;td&gt;3.8GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;td&gt;0.2833337046s&lt;/td&gt;
&lt;td&gt;3.7GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Hey that's an improvement!&lt;/p&gt;
&lt;h3 id="control"&gt;Control&lt;/h3&gt;&lt;p&gt;All these numbers are machine-specific obviously. So what does an
existing tool like
&lt;a href="https://fio.readthedocs.io/en/latest/fio_doc.html"&gt;fio&lt;/a&gt; say?
(Assuming I'm using it correctly. I await your corrections!)&lt;/p&gt;
&lt;p&gt;With a 4KiB buffer size:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;fio&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="o"&gt;=&lt;/span&gt;fiotest&lt;span class="w"&gt; &lt;/span&gt;--rw&lt;span class="o"&gt;=&lt;/span&gt;write&lt;span class="w"&gt; &lt;/span&gt;--size&lt;span class="o"&gt;=&lt;/span&gt;1G&lt;span class="w"&gt; &lt;/span&gt;--bs&lt;span class="o"&gt;=&lt;/span&gt;4k&lt;span class="w"&gt; &lt;/span&gt;--group_reporting&lt;span class="w"&gt; &lt;/span&gt;--ioengine&lt;span class="o"&gt;=&lt;/span&gt;sync
fiotest:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;write,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;R&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;4096B-4096B,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;W&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;4096B-4096B,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;T&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;4096B-4096B,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ioengine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sync,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;iodepth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
fio-3.33
Starting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;process
Jobs:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
fiotest:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;groupid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;err&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2437359&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Thu&lt;span class="w"&gt; &lt;/span&gt;Oct&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;:33:42&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;write:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IOPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;282k,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;BW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1102MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1156MB/s&lt;span class="o"&gt;)(&lt;/span&gt;1024MiB/929msec&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;zone&lt;span class="w"&gt; &lt;/span&gt;resets
&lt;span class="w"&gt;    &lt;/span&gt;clat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;nsec&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2349&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;54099&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2709&lt;/span&gt;.48,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1325&lt;/span&gt;.83
&lt;span class="w"&gt;     &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;nsec&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2390&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;54139&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2752&lt;/span&gt;.89,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1334&lt;/span&gt;.62
&lt;span class="w"&gt;    &lt;/span&gt;clat&lt;span class="w"&gt; &lt;/span&gt;percentiles&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;nsec&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2416&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2416&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2416&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2448&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2448&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2448&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2448&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2480&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;70&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2512&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2544&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;90&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2832&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;95&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3504&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5792&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.50th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="m"&gt;15296&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.90th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="m"&gt;19584&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.95th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="m"&gt;20096&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.99th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="m"&gt;22656&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;bw&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;KiB/s&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;940856&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;940856&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;per&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;83&lt;/span&gt;.36%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;940856&lt;/span&gt;.00,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.00,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;samples&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;iops&lt;span class="w"&gt;        &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;235214&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;235214&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;235214&lt;/span&gt;.00,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.00,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;samples&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;usec&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;97&lt;/span&gt;.22%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;10&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.03%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;20&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.71%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;50&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.04%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;100&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.01%
&lt;span class="w"&gt;  &lt;/span&gt;cpu&lt;span class="w"&gt;          &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;.35%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;82&lt;/span&gt;.11%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;majf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;minf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;IO&lt;span class="w"&gt; &lt;/span&gt;depths&lt;span class="w"&gt;    &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;submit&lt;span class="w"&gt;    &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;issued&lt;span class="w"&gt; &lt;/span&gt;rwts:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,262144,0,0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;short&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,0,0,0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dropped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,0,0,0
&lt;span class="w"&gt;     &lt;/span&gt;latency&lt;span class="w"&gt;   &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;percentile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.00%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

Run&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;group&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;WRITE:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1102MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1156MB/s&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;1102MiB/s-1102MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1156MB/s-1156MB/s&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;io&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024MiB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1074MB&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;929&lt;/span&gt;-929msec
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;1.2GB/s is about in the ballpark of what we got.&lt;/p&gt;
&lt;p&gt;And with a 1MiB buffer size?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;fio&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="o"&gt;=&lt;/span&gt;fiotest&lt;span class="w"&gt; &lt;/span&gt;--rw&lt;span class="o"&gt;=&lt;/span&gt;write&lt;span class="w"&gt; &lt;/span&gt;--size&lt;span class="o"&gt;=&lt;/span&gt;1G&lt;span class="w"&gt; &lt;/span&gt;--bs&lt;span class="o"&gt;=&lt;/span&gt;1M&lt;span class="w"&gt; &lt;/span&gt;--group_reporting&lt;span class="w"&gt; &lt;/span&gt;--ioengine&lt;span class="o"&gt;=&lt;/span&gt;sync
fiotest:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;write,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;R&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1024KiB-1024KiB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;W&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1024KiB-1024KiB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;T&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1024KiB-1024KiB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ioengine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sync,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;iodepth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
fio-3.33
Starting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;process
fiotest:&lt;span class="w"&gt; &lt;/span&gt;Laying&lt;span class="w"&gt; &lt;/span&gt;out&lt;span class="w"&gt; &lt;/span&gt;IO&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;1024MiB&lt;span class="o"&gt;)&lt;/span&gt;

fiotest:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;groupid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;err&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2437239&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Thu&lt;span class="w"&gt; &lt;/span&gt;Oct&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;:32:09&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;write:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IOPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3953&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;BW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3954MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;4146MB/s&lt;span class="o"&gt;)(&lt;/span&gt;1024MiB/259msec&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;zone&lt;span class="w"&gt; &lt;/span&gt;resets
&lt;span class="w"&gt;    &lt;/span&gt;clat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;usec&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;221&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1205&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;241&lt;/span&gt;.83,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;43&lt;/span&gt;.93
&lt;span class="w"&gt;     &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;usec&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;228&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1250&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;251&lt;/span&gt;.68,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stdev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;45&lt;/span&gt;.80
&lt;span class="w"&gt;    &lt;/span&gt;clat&lt;span class="w"&gt; &lt;/span&gt;percentiles&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;usec&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;225&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;225&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;227&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;227&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;231&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;233&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;235&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;239&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;70&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;243&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;249&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;90&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;262&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;95&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;269&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.00th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;302&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.50th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;318&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.90th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1074&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.95th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1205&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;.99th&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1205&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;usec&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;250&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;.96%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;500&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;.85%
&lt;span class="w"&gt;  &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;msec&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.20%
&lt;span class="w"&gt;  &lt;/span&gt;cpu&lt;span class="w"&gt;          &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.26%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;94&lt;/span&gt;.96%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;majf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;minf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;IO&lt;span class="w"&gt; &lt;/span&gt;depths&lt;span class="w"&gt;    &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;submit&lt;span class="w"&gt;    &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%,&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0%
&lt;span class="w"&gt;     &lt;/span&gt;issued&lt;span class="w"&gt; &lt;/span&gt;rwts:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,1024,0,0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;short&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,0,0,0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dropped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,0,0,0
&lt;span class="w"&gt;     &lt;/span&gt;latency&lt;span class="w"&gt;   &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;percentile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.00%,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

Run&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;group&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;WRITE:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3954MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;4146MB/s&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;3954MiB/s-3954MiB/s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;4146MB/s-4146MB/s&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;io&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024MiB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;1074MB&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;259&lt;/span&gt;-259msec
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;3.9GB/s is also roughly in the same ballpark we got.&lt;/p&gt;
&lt;p&gt;Our code seems reasonable!&lt;/p&gt;
&lt;h3 id="what's-next?"&gt;What's next?&lt;/h3&gt;&lt;p&gt;None of this is original. &lt;code&gt;fio&lt;/code&gt; is a similar tool, written in C, with
many different IO engines including &lt;code&gt;libaio&lt;/code&gt; and &lt;code&gt;writev&lt;/code&gt; support. And
it has many different IO workloads.&lt;/p&gt;
&lt;p&gt;But it's been enjoyable to learn more about these APIs. How to program
them and how they compare to eachother.&lt;/p&gt;
&lt;p&gt;So next steps could include adding additional IO engines or IO
workloads.&lt;/p&gt;
&lt;p&gt;Also, either I need to understand Iceber's Go library better or its
API needs to be loosened up a little bit so we can get that awesome
ring buffer behavior we could use from Zig.&lt;/p&gt;
&lt;p&gt;Keep an eye out here and on my &lt;a href="https://github.com/eatonphil/io-playground"&gt;io-playground
repo&lt;/a&gt;!&lt;/p&gt;
&lt;h3 id="selected-responses-after-publication"&gt;Selected responses after publication&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;wizeman on lobsters
&lt;a href="https://lobste.rs/s/rimkv3/io_uring_basics_writing_file_disk#c_qvlx5u"&gt;suggests&lt;/a&gt;
measuring at least 30 seconds worth of writing data and
&lt;code&gt;fsync()&lt;/code&gt;-ing if you want to test the entire IO subsystem and not
just hitting the kernel cache.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Digging into io_uring has been on my list for a long time now! This week I finally made made some progress.&lt;br /&gt;&lt;br /&gt;Let's go on a little journey through a few increasingly complex (and useful) implementations of writing a file to disk with io_uring.&lt;a href="https://t.co/gR9K2OQs2R"&gt;https://t.co/gR9K2OQs2R&lt;/a&gt; &lt;a href="https://t.co/TMaC8QYL6k"&gt;pic.twitter.com/TMaC8QYL6k&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1715151609615773965?ref_src=twsrc%5Etfw"&gt;October 19, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 19 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-10-19-write-file-to-disk-with-io_uring.html</guid></item><item><title>The List</title><link>https://www.marginalia.nu/log/91-the-list/</link><description>As a general observation, I tend to be more productive when I know what to do next at any given moment.
There are days when I&amp;rsquo;ve seemingly gotten a &amp;ldquo;week&amp;rdquo; of work done on an afternoon, those are the days when what I needed to do was very clear, and I basically just had a list of items to tick off one by one.
There have admittedly also those ignoble weeks weeks when I&amp;rsquo;ve gotten an afternoon&amp;rsquo;s work done, mostly they are weeks when it&amp;rsquo;s not been at all clear what to do next.</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 19 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/91-the-list/</guid></item><item><title>crosstable.love</title><link>http://akkartik.name/post/crosstable</link><description>&lt;p&gt;
&lt;a href="https://git.sr.ht/~akkartik/crosstable.love"&gt;crosstable.love&lt;/a&gt; is a
little app I whipped up for tracking standings during the Cricket World Cup,
just to avoid the drudgery of resorting rows as new results come in.

&lt;p&gt;
&lt;a href="https://akkartik.name/images/20231018-crosstable.webm"&gt;
  &lt;video style="width: 80%; margin-left: 10%; border: 2px solid #aaaaff;"&gt;
    &lt;source src="https://akkartik.name/images/20231018-crosstable.webm" /&gt;
  &lt;/video&gt;
&lt;/a&gt;
&lt;div class="btw" style="text-align: right;"&gt;
  video; 20 seconds
&lt;/div&gt;</description><author>Kartik Agaram</author><pubDate>Wed, 18 Oct 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/crosstable</guid></item><item><title>theft deterrent</title><link>http://blog.onepatchdown.net/society/2023/10/17/theft-deterrent/</link><description>&lt;p&gt;My thought, to help with our problem of theft today (2023) is that Target, et al, copy the Costco model, and require membership for entry into the store.&lt;/p&gt;

&lt;p&gt;Have membership cost $0, but is revocable upon video evidence of stealing shit. Swipe to get into the store, pay when you leave.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Wed, 18 Oct 2023 00:10:17 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/society/2023/10/17/theft-deterrent/</guid></item><item><title>Stocketa</title><link>https://paulstamatiou.com/stocketa</link><description>&lt;div&gt;Please read &lt;a href="https://paulstamatiou.com/stocketa/" title="Stocketa"&gt;this Stocketa article&lt;/a&gt; in a browser to see embedded Stocketa videos.&lt;/div&gt;
&lt;p&gt;It was March 2020, I was in New England when covid quarantine had just begun and I found myself much more homebound. In these situations I&amp;#x27;m not one to just do nothing. I always have some sort of project or hobby to keep me busy, be it taking and editing photos, writing detailed blog posts, or coding something.&lt;/p&gt;
&lt;p&gt;I had always wanted to learn iOS development, so this felt like the perfect opportunity. I was particularly keen to learn SwiftUI to build my designs natively. Years back I had dabbled with writing an Objective-C app, but it never quite filled me with joy, to say the least. As a designer, I&amp;#x27;ve spent the last decade getting good with prototyping tools and frameworks to mimic realistic, interactive mobile prototypes. Building it myself was the natural next step to give me even more control over the experience, and &lt;a href="https://paulstamatiou.com/craft/"&gt;get even deeper into the craft.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I started learning Swift and SwiftUI basics with courses like Design+Code, and Hacking with Swift. But I knew myself—I learn best with a project of my own. I knew exactly what I wanted to build: a better stock portfolio tracker. The stock market had taken a plunge with covid and I was doing dozens of trades per week with everything on sale. It would be fun to have a simple app of my own to track the performance of my trades instead of dealing with the subpar sites and apps from my stockbrokers.&lt;/p&gt;
&lt;img alt="Stocketa app" height="2754" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-phone.webp" width="3442" /&gt;
&lt;p&gt;Little did I know this project would consume my nights and weekends for over two years, often spending 10-20 hours a week on it. I built a ton of functionality, sweat all the details, wrote a comprehensive node backend for it, created an LLC for it, and managed an active Testflight. I was learning Swift and SwiftUI along the way, so I was often redesigning and rebuilding as I went.&lt;/p&gt;
&lt;p&gt;Eventually, I decided to stop pursuing it. I&amp;#x27;ll get into the why a bit later. &lt;strong&gt;This post is for me to document &lt;a href="https://stocketa.com"&gt;Stocketa&lt;/a&gt;.&lt;/strong&gt; I designed and built a lot that I&amp;#x27;m proud of and want to have somewhere to archive it before I pull the plug.&lt;/p&gt;
&lt;img alt="10 images that would have been used for the Stocketa App Store screenshots if Stocketa had been published" height="2720" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-app-store.webp" width="3233" /&gt;
&lt;small&gt;The mostly-finished App Store screenshots that would have been published.&lt;/small&gt;
&lt;h3&gt;What is &lt;s&gt;was&lt;/s&gt; Stocketa&lt;/h3&gt;
&lt;p&gt;Stocketa was a simple portfolio tracker designed with care. It came out of my frustration of having to check the clunky apps and sites that my brokers had just to get a glimpse at how my investments were doing. I&amp;#x27;m a casual investor and don&amp;#x27;t need much from a tool like this but I had assets in various places, and I just wanted one easy and well-designed place to check them. I wanted to see real per-transaction gain/loss info, based on my own cost basis.&lt;/p&gt;
&lt;p&gt;While there are tons of apps, brokers, and neobanks that address this, nothing quite felt like what I wanted. There were several types of solutions in this space:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First off, there are simple stock apps like Apple Stocks that let you see stock charts, news, and more. They have good data and don&amp;#x27;t require any accounts to use. However, they offer no functionality to log your holdings and track your gains/losses.&lt;/li&gt;
&lt;li&gt;There are lots of broker apps (e.g., Robinhood) that let you trade and track your holdings, but they only let you see your holdings from them, not from any other financial services you might use.&lt;/li&gt;
&lt;li&gt;Then there&amp;#x27;s a whole category of what I&amp;#x27;d call advanced stock services like TradingView (and several that have since died or pivoted over the years), but they&amp;#x27;re not mobile-first or as elegant as I wanted.&lt;/li&gt;
&lt;li&gt;There are broader investment advisor services like Personal Capital. While they do often have some aggregator functionality to link up your other accounts, they&amp;#x27;re not that powerful for diving into individual assets, aren&amp;#x27;t particularly well-designed, and are primarily focused on upselling you on their own investment advising services (seriously, I used to get endless 8am calls from Personal Capital).&lt;/li&gt;
&lt;li&gt;And there are lots of smaller apps on the App Store where you can track your holdings but the design and UX leaves a lot to be desired. I&amp;#x27;ve tried most of them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From a design perspective, I generally found that financial apps bias towards density when displaying your assets. They assume you have tons of stocks and prioritize a list view where you have to tap in to a detail view to see more info. I found myself constantly tapping in, manipulating a chart or looking at some basic stats, then swiping back and repeating all that for several stocks. It was not easy or the kind of at a glance info I wanted.&lt;/p&gt;
&lt;p&gt;That left me with my challenge. I wanted a &lt;em&gt;simple&lt;/em&gt; portfolio tracker aimed at the casual investor. It didn&amp;#x27;t have to do it all, but it should aim to do a few things well. An app just for me.&lt;/p&gt;
&lt;img alt="Stocketa running on an iPhone" height="2007" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-iphone-DSC06278.webp" width="3000" /&gt;
&lt;h3&gt;Going custom&lt;/h3&gt;
&lt;p&gt;I was more than thrilled to be working on a new app from scratch. After years of working at large companies with rigid design systems and gobs of stakeholders for every little design decision, I was yearning for some creative freedom. Freedom from process, freedom from typical design constraints. I would be in full control, designing and building it myself. &lt;strong&gt;It was &lt;em&gt;my&lt;/em&gt; &lt;a href="https://paulstamatiou.com/side-projects/"&gt;side project&lt;/a&gt; after all&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I had gotten tired of designing for traditional mobile apps. You know the type.. you add a tab bar, a typical navigation stack, page header and the app already feels significantly basic, muted, and limited out of the gate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This was my place to have fun.&lt;/strong&gt; I wanted to go custom and toss out whatever components the system gives you for free. And I knew that by having to figure out how to build various UI components from scratch, I&amp;#x27;d learn a lot along the way. Almost all of Stocketa&amp;#x27;s UI is custom.&lt;/p&gt;
&lt;p&gt;I will say that &lt;strong&gt;going custom is almost always the absolutely &lt;em&gt;wrong&lt;/em&gt; thing to do&lt;/strong&gt; 99% of the time. The operating system you&amp;#x27;re working in—be it iOS, Android, macOS—has invested a ton in a highly-considered suite of components. They&amp;#x27;re remarkably accessible, well-tested, and work on a variety of devices.&lt;/p&gt;
&lt;p&gt;It&amp;#x27;s absolutely no small feat to consider creating a custom component for your app. However, when going custom does make sense and you&amp;#x27;re okay with the added scope and responsibility, the app becomes your canvas to do whatever you want and &lt;em&gt;make it yours&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The first area where custom made sense for me was general app navigation. When I got started, I knew I wanted this app to have minimal app chrome. I didn&amp;#x27;t want a standard tab bar and header taking up space. I wasn&amp;#x27;t going to require much navigation: most things would just live inside the stock cards from the home timeline.&lt;/p&gt;
&lt;p&gt;The app would be focused around showing more with less. Personally, I only cared about tracking a few stocks, but I hated the work of constantly having to tap in to see a detail view, or interact with the chart. As such, the main scroll view needed to be more immersive and not get occluded by unnecessary app chrome.&lt;/p&gt;
&lt;p&gt;It started with app navigation, custom stock cards, sheets, and more. Needless to say, I spent a lot of time on the details with everything being custom. Below are a few examples:&lt;/p&gt;
&lt;div&gt;Please read &lt;a href="https://paulstamatiou.com/stocketa/" title="Stocketa"&gt;this Stocketa article&lt;/a&gt; in a browser to see embedded Stocketa videos.&lt;/div&gt;
&lt;h3&gt;Designing and building&lt;/h3&gt;
&lt;p&gt;I started with the basics: getting a scroll view of stock cards working, adding charts, and hooking it up to a database for persistence (initially Core Data before moving to Firebase). Then I added the requisite network calls and polling to update the stock card from a financial data provider. I started with IEX as the data provider as they had a decent API, reasonable pricing, and allowed commercial use, which would be required for a consumer app like this to go on the App Store.&lt;/p&gt;
&lt;img alt="Early Stocketa build screenshot" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-early-stocketa-build.webp" /&gt;
&lt;small&gt;Working on an early build in 2020.&lt;/small&gt;
&lt;p&gt;One of the initial key decisions with Stocketa was that I wanted to track stock transactions in a comprehensive way. Simply logging the number of shares you had for each stock was not going to cut it. This ended up being a significant amount of work to do properly, but it was necessary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For each stock sale or purchase I wanted to be able to display not only the total gain/loss, but per-transaction gain/loss so you could tell if an individual transaction was a good trade.&lt;/li&gt;
&lt;li&gt;When it comes to stock sales, I needed to know the cost basis method (FIFO, LIFO, et cetera) so I could correctly calculate and display the realized and unrealized gains on a particular transaction. When selling shares, I also needed to know from which previous transaction or transactions the shares would be sold so I could get the correct cost basis and accurately calculate the gain/loss based on the cost basis method.&lt;/li&gt;
&lt;li&gt;And then there was dealing with stock splits, which seems simple at first glance but ended up being quite a bit of work to build in a robust way: not simply altering previous transactions. They should be automatically applied for new splits, but also able to be manually applied if you didn&amp;#x27;t log split-adjusted transactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;img alt="A glimpse at part of how comprehensive transaction types in Stocketa were shown" height="2160" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa18@2x.jpg" width="3840" /&gt;
&lt;small&gt;A glimpse at part of how comprehensive transaction types in Stocketa were shown.&lt;/small&gt;
&lt;p&gt;At some point I gave it a name: Stocketa. In Greek you might say "marketa" (Μαρκέτα, though αγορά might be a more proper word) when referring to a market, so this was like coalescing Stock and Market with that in mind.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;When I was interviewing for a new job in 2021, I created these slides as part of my presentation after talking about my work at Twitter for the previous 9 years. These weren&amp;#x27;t all of Stocketa slides, and there was a lot of additional talking points I had for each slide, but you get the idea.&lt;/p&gt;
&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa01@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa02@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa03@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa04@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa05@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa06@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa07@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa08@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa09@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa10@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa11@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa12@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa13@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa14@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa15@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa16@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa17@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa18@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa19@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa20@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa21@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa22@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa23@2x.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1125" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-slides-Stocketa24@2x.jpg" width="2000" /&gt;
&lt;small&gt;Slides touching on various parts of Stocketa as of late 2021. The design of many of these screens is now outdated, but it&amp;#x27;s a good look at a point in time.&lt;/small&gt;
&lt;h3&gt;A note about SwiftUI&lt;/h3&gt;
&lt;p&gt;I kept designing, building.. and then redesigning and rebuilding. One of the side effects of learning Swift and SwiftUI as I was building this was that I was constantly redoing specific pages and parts of the app as I went. I continually found better ways to build things, and similarly I would continually refine designs. About a year or so into building Stocketa I &lt;a href="https://paulstamatiou.com/getting-started-with-swiftui/"&gt;wrote about my experience with SwiftUI&lt;/a&gt;. Much of that still rings true.&lt;/p&gt;
&lt;p&gt;SwiftUI has come a long way since iOS 13. It&amp;#x27;s more performant, there&amp;#x27;s more parity in available components and functionality compared with UIKit, and you get more control over things. And with iOS 17, you get some goodies like shaders, keyframes, scroll transitions, and more.&lt;/p&gt;
&lt;video controls="false" poster="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-xcode-swiftui-preview-stocketa-poster.webp"&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-xcode-swiftui-preview-stocketa.webm" type="video/webm" /&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-xcode-swiftui-preview-stocketa.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
&lt;small&gt;Xcode showing a SwiftUI preview.&lt;/small&gt;
&lt;p&gt;There&amp;#x27;s still a gap between the types of things you can do with AppKit and UIKit, but SwiftUI is undeniably a powerful tool that&amp;#x27;s dramatically more approachable. And not just a coding tool; it&amp;#x27;s a great design tool. Designers can quickly lay things out, hook up interactions, and get a real feel for a design using a native tool.&lt;/p&gt;
&lt;p&gt;Almost all of Stocketa&amp;#x27;s UI is built with SwiftUI. There were only a few occasions when I found it necessary to use UIKit to achieve some goal.&lt;sup&gt;&lt;a href="#footnote-1" id="r1" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt; None of this would have been possible, or drawn me to take a closer look into building iOS apps, if it hadn&amp;#x27;t been for SwiftUI.&lt;/p&gt;
&lt;h3&gt;What I built&lt;/h3&gt;
&lt;p&gt;I used the app daily and continued pouring time into every feature and detail to meet my high quality bar. While I initially planned for this to be a simple to build app that would not require a backend, or even any user accounts, that did not last for long. I quickly realized I needed my own backend to unlock a few things.&lt;/p&gt;
&lt;p&gt;At first, this was so I didn&amp;#x27;t have to include the API key for my financial data provider inside the app for security reasons—I wouldn&amp;#x27;t want someone to find it and start running up my bill by abusing it. I created a backend proxy for the API with rate-limiting and throttling. Then, I wanted to have it be authenticated with user accounts, so I added Sign In With Apple along with a stock data caching system so that I only had to fetch stats for a stock once with a certain time period (depending on the particular stat that was either seconds, 24 hours, or weekly) regardless of however many user accounts needed that stock info. The main goal with that was to also reduce my costs associated with financial data API usage.&lt;/p&gt;
&lt;p&gt;By this point I really started enjoying the speed at which I could update endpoints and business logic entirely on the backend. I started moving logic I had in the app to the backend. &lt;strong&gt;Stocketa was starting to come together.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even though this was just a solo project, I used Linear to keep track of tasks, ideas, features, customer feedback, and milestones.&lt;/p&gt;
&lt;img alt="My Linear app usage for Stocketa" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-linear.webp" /&gt;
&lt;p&gt;I had prioritized lists of things to build and bugs to fix. I was using—and loving—Linear for this. Here&amp;#x27;s a list of &lt;em&gt;most&lt;/em&gt; of the things I built for Stocketa:&lt;/p&gt;
&lt;div&gt;Please read &lt;a href="https://paulstamatiou.com/stocketa/" title="Stocketa"&gt;this Stocketa article&lt;/a&gt; in a browser to see embedded Stocketa videos.&lt;/div&gt;
&lt;p&gt;I ran a small TestFlight alpha with close friends, then increasingly I added more people. At one point I was up to around 1,000 people. That was the most I wanted to allow. I received lots of feedback from overall great first impressions (below), to general feature requests, and of course lots of bug reports.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Already the app is filling a need for me in its personal approach to tracking investments: no pushing for sales, no integrations with brokers and no unnecessary permissions. Brilliant.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Simply amazed at the SwiftUI work you’ve poured into this app. So beautiful. The onboarding alone is a masterclass in attention to detail. Bravo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;love it, this feels so polished and slick already... really great use of SwiftUI&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Just got the invite, added my stocks... the animations are beautiful, design is clean and precise... it’s all around beautiful. Congrats!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;F**k, man. This app is incredible. You’re building all of this on your own?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Dude. This app is f**king beautiful. Really great job. Just imported shares - super easy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;This NUX is insane. The particles when you add a stock. Gives me the happy brain chemicals..&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I had thousands on a waiting list but there were API and hosting costs associated with the increased usage that I didn&amp;#x27;t want to bear yet, as well as a huge increase in support and customer feedback that took a lot of time to manage. You start to hear the same types of feedback, and it just becomes a chore to manage that many emails. That good news was that something was resonating and people cared enough to go through the work to send in bug reports and feature requests.&lt;/p&gt;
&lt;h3&gt;The website&lt;/h3&gt;
&lt;p&gt;At some point early in feature development I took a break to focus on the website. At first I wanted a simple landing page to build interest, and collect emails for a waitlist so I could keep people in the loop. And I didn&amp;#x27;t want to spend that much time on it.&lt;/p&gt;
&lt;video controls="true" poster="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-v0-landing-page-poster.webp"&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-v0-landing-page.webm" type="video/webm" /&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-v0-landing-page.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
&lt;small&gt;Video: Initial Stocketa teaser homepage.&lt;/small&gt;
&lt;p&gt;There was nothing fancy or particularly special about this initial page. It was just something quick I wanted to get out the door. And now, a while later, there&amp;#x27;s a lot I hate about this early design. The background texture was too strong, the headline alignment feels odd with the phone.. but I digress.&lt;/p&gt;
&lt;p&gt;In that initial design I did start playing with some wave-related elements—a loose reference to stock market charts going up and down. And then to add to that, I built a few mini stock cards gently hovering in place (thanks to &lt;code&gt;offset-path&lt;/code&gt; CSS along with 2 keyframe animations) in the background. Each stock card had an SVG line chart—loosely based on that stock&amp;#x27;s actual performance around the time—that would animate on page load. I ended up keeping a version of these mini stock cards on the latest version of the website, and then on an onboarding screen inside the app.&lt;/p&gt;
&lt;p&gt;The original inspiration for these random floating stock cards was from my now &lt;a href="https://photos.paulstamatiou.com/work"&gt;out-of-date work page&lt;/a&gt; where I highlighted projects with some large cards, each with their own intro animation based on the project. One of them was for my work on Twitter Video, where video clip thumbnails would fan out from the phone on hover (shown below).&lt;/p&gt;
&lt;video controls="true" poster="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-portfolio-twitter-video-hover-poster.webp"&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-portfolio-twitter-video-hover.webm" type="video/webm" /&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-portfolio-twitter-video-hover.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
&lt;small&gt;The floating mini stock cards on the Stocketa homepage were inspired by the hover state animation on this project card from my old portfolio page.&lt;/small&gt;
&lt;h3&gt;Homepage design &lt;code&gt;v1.0&lt;/code&gt;&lt;/h3&gt;
&lt;video controls="true" poster="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-old-stocketa-website-poster.webp"&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-old-stocketa-website.webm" type="video/webm" /&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-old-stocketa-website.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
&lt;small&gt;Video: The first full version of Stocketa.com in early 2021, currently &lt;strong&gt;&lt;a href="https://stocketa.com/v1/"&gt;archived here&lt;/a&gt;&lt;/strong&gt;. The App Store QR code was meant to be displayed on scroll but in this version I didn&amp;#x27;t have that hooked up as the app wasn&amp;#x27;t live.&lt;/small&gt;
&lt;p&gt;In early 2021 I ended up getting around to designing and building out the first complete homepage for Stocketa. I had enough functionality and features built that it felt worthy of investing some time into showcasing them on the site.&lt;/p&gt;
&lt;p&gt;Lots of app websites at the time (and still now) had the typical hero section on their homepage: some app screenshot in a phone frame off to one side, with accompanying header and subtitle text adjacent. Nothing wrong with that, but I wanted something a bit more engaging. I wasn&amp;#x27;t entirely interested in optimizing for conversion, and cramming a lot in above the page fold.&lt;/p&gt;
&lt;img alt="Old Stocketa explorations" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-old-stocketa-explorations.webp" /&gt;
&lt;small&gt;Initial design explorations in figma.&lt;/small&gt;
&lt;p&gt;I thought it would be nice to try to do something where the 3D phone frame was angled and then began to change position on scroll while a brief teaser video of the app functionality played. Apple had done something similar in recent years with product pages, such as the first-generation AirPods Pro. They used over 100 frames of the AirPods that would progress as you scrolled:&lt;/p&gt;
&lt;video controls="true" poster="https://turbo.paulstamatiou.com/uploads/2023/08/airpods-pro-first-gen-site-animation-poster.webp"&gt;
      
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/airpods-pro-first-gen-site-animation2.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
&lt;small&gt;Example of a technique used by Apple.&lt;/small&gt;
&lt;p&gt;There&amp;#x27;s a lot of ways to achieve an effect like this. One route route is using Lottie and essentially converting each frame of your video or animation to a base64-encoded string in a json file for the Lottie web player to manage. It&amp;#x27;s not a flawless solution, however, and can incur some large file sizes depending on what you&amp;#x27;re doing. It&amp;#x27;s best for vector micro-animations, not large devices with complex visual content. I also didn&amp;#x27;t want to incur the cost of that large initial json download blocking the very first thing you see on the site.&lt;/p&gt;
&lt;p&gt;Instead I opted to have javascript display a sequence of frames composited to &lt;code&gt;canvas&lt;/code&gt; along with an offscreenCanvas for performance. I used &lt;a href="https://rotato.app/"&gt;Rotato&lt;/a&gt; to import my short Stocketa screencast, create a 3D phone video, and then create a PNG sequence of frames. I wasn&amp;#x27;t super pleased with the quality—the phone frame didn&amp;#x27;t look super realistic and had some rough pixelated edges—but it was good enough compared to doing it all by hand with other design tools.&lt;/p&gt;
&lt;video controls="true" poster="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-old-stocketa-website-hero-animation-poster.webp"&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-old-stocketa-website-hero-animation.webm" type="video/webm" /&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-old-stocketa-website-hero-animation.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
&lt;small&gt;Scroll-based Stocketa 3D phone, paired with subtle parallax for header elements and the floating stock cards. There were some other minor scroll effects like the gradient glow behind the phone spreading.&lt;/small&gt;
&lt;p&gt;I then exported optimized transparent webp, and jpg files (with accompanying alpha masks created in Sketch to save bandwidth) and had the javascript detect which file type to use for the animation. While I was pleased with how this effect turned out, it ended up becoming a rather huge annoyance when I wanted to update the content later—it&amp;#x27;s a pain to generate, optimize, and update the image sequence each time.&lt;/p&gt;
&lt;img alt="Old Stocketa Layout" height="2452" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-old-stocketa-site-grid-layout-2.webp" width="3278" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="Old Stocketa Layout" height="2400" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-old-stocketa-site-grid-layout-1.webp" width="3244" /&gt;
&lt;small&gt;Other bits of the website: cards for various features.&lt;/small&gt;
&lt;p&gt;And finally, as for the rest of the content on this site I went with a basic feature card approach, sometimes called a "bento box" layout; particularly when varying card sizes are used. This is a layout that I&amp;#x27;ve grown to like a bit less over the years as it has become very common.&lt;/p&gt;
&lt;h3&gt;Homepage design &lt;code&gt;v2.0&lt;/code&gt;&lt;/h3&gt;
&lt;video controls="true" poster="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-new-stocketa-website-poster-2.webp"&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-new-stocketa-website.webm" type="video/webm" /&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-new-stocketa-website.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
&lt;small&gt;Video: New Stocketa site&lt;/small&gt;
&lt;p&gt;A year later, it was time for another redesign. Stocketa had more functionality, and a lot more refinements. I had more to share, and I had grown tired of the 3D phone frame interaction. It was neat but it was just there for show; it wasn&amp;#x27;t the best way to show off parts of the app as it scrolled off screen. It was also a pain to update the frame sequences. I essentially just wanted to take screenshots of the real app from the iOS simulator and upload them.&lt;/p&gt;
&lt;p&gt;Separately, I didn&amp;#x27;t want a super long page with a lot of repetitive modules and screenshots. I wanted a skimmable feature list. I began to wonder if I could get away with just a single phone frame on the site that showed everything.&lt;/p&gt;
&lt;p&gt;I eventually landed on a two pane approach: the device frame was always in view, fixed on the right side, while the scrollable left side contained the feature list. The trade-off with this design was that it would require hovering over each feature item to be able to update the screenshot shown in the device frame. I figured an interesting compromise to that would be having the phones stack up and away on scroll. Almost like the old Apple Time Machine backup UI.&lt;/p&gt;
&lt;p&gt;I built several versions of this and found that it didn&amp;#x27;t feel great. There wasn&amp;#x27;t much scroll distance so if you scrolled at a normal or fast pace, you&amp;#x27;d see a bunch of phones flicker by you as the animations tried to keep up. It was too busy and hectic, not to mention a lot of state management to deal with jumping between your current scroll-based position screenshot to a hover-based screenshot if you hovered over a feature list item. I ended up simplifying a bit and only having the scroll-based UI manage just a few states: the intro phone frame, the first feature screenshot, and then a final animation at the bottom of the page.&lt;/p&gt;
&lt;video controls="true" poster="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-homepage-v2-responsive-poster.webp"&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-homepage-v2-responsive.webm" type="video/webm" /&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-homepage-v2-responsive.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
&lt;small&gt;The website&amp;#x27;s mobile layout uses a carousel of screenshots in the header.&lt;/small&gt;
&lt;p&gt;After sorting out the core interactions, I had to figure out how to deal with mobile and tablet interactions. The fix two-pane layout made this a bit of a challenge. I ended up turning the header into a carousel of device frames you could scroll through.&lt;/p&gt;
&lt;p&gt;And then finally, I added a few small bits of polish ✨:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hero title text gradient changes based on scroll&lt;/li&gt;
&lt;li&gt;The icon accent color and background circle color change based on scroll&lt;/li&gt;
&lt;li&gt;When hovering over any feature list item, there is a subtle background color change created by an overlaid gradient. SVG-created noise/grain is also overlaid on top of the background.&lt;/li&gt;
&lt;li&gt;When hovering over any feature list item, or when you&amp;#x27;re reached the end of the page, there&amp;#x27;s a subtle icon particle emitter that floats up icons from the bottom. Each feature used a different icon for this.&lt;/li&gt;
&lt;/ul&gt;
&lt;video controls="true" poster="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-homepage-v2-particle-emitter-poster.webp"&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-homepage-v2-particle-emitter.webm" type="video/webm" /&gt;
      &lt;source src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-homepage-v2-particle-emitter.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
&lt;small&gt;Particle emitter effects: icons float up from the bottom, based on the feature list item being hovered on. There&amp;#x27;s also another very subtle emitter generating some tiny floating particles.&lt;/small&gt;
&lt;p&gt;Overall I liked this design, but looking back at almost a year later, the gradient text and background colors feel a bit too much for my taste now.&lt;/p&gt;
&lt;h3&gt;API troubles&lt;/h3&gt;
&lt;p&gt;Eventually it became clear that the quality of financial data Stocketa relied on was not cutting it. The first provider I used, IEX, would sometimes have charts outdated, inaccurate prices (especially for less frequently traded assets on the IEX exchange), and lacked data for OTC markets (they required you sign a pricey license directly with OTC), as well as significantly limited data for Nasdaq-listed stocks (even basic things like pre-market and after-hours data). The last straw was when IEX dropped mutual fund data entirely, all without any advance notice.&lt;/p&gt;
&lt;p&gt;I later used another provider that allowed commercial use that didn&amp;#x27;t charge too much ($100/month) but I had even more problems with their data and reliability. Endpoints were even returning old and incorrect data, and even once alerted to issues, their team would not only ignore the bugs but claim they didn&amp;#x27;t exist. I couldn&amp;#x27;t ship Stocketa with unreliable financial data to people that would be paying me for a quality app.&lt;/p&gt;
&lt;p&gt;I also could not get certain types of data with paid APIs. I had to build a scraping engine on my backend that would visit a few sites to get the data I needed for less updated data like dividend yield, earnings dates, and even for OEF data (other providers would be more than a day delayed for this at times).&lt;/p&gt;
&lt;p&gt;Needless to say, this was all frustrating. It was a hassle, hard to maintain, and unreliable. At the same time, my backend continued getting more involved to deal with more and more of the intricacies of dealing with stocks. Things like dealing with companies merging or stock tickers renaming.&lt;/p&gt;
&lt;img alt="Stocketa Backend" src="https://turbo.paulstamatiou.com/uploads/2023/08/copyright-paulstamatiou_com-stocketa-backend.webp" /&gt;
&lt;small&gt;~13k LOC node/express backend (shown in Zed but I now prefer &lt;!-- --&gt;&lt;a href="https://twitter.com/Stammy/status/1713303350987698459"&gt;Cursor&lt;/a&gt;&lt;!-- --&gt;) for Cloud Run. It manages auth, caching, news, core business logic, cleaning up data, notifications, widgets, working with multiple API, and a bit of scraping. This was my first time doing anything of this size with node and typescript.&lt;/small&gt;
&lt;p&gt;Starting out I thought getting access to a stock market data API would be the easy part. I quickly learned about the world of market data feeds, SIPs (Securities Information Processor), and vendor agreements.&lt;/p&gt;
&lt;p&gt;Quality market data exists, it&amp;#x27;s just not made for indie app developers. It&amp;#x27;s very expensive. Several leading U.S. market data providers have commercial use data pricing starting at $2,000 per month. And that&amp;#x27;s not for everything, if you need things like index data or options data, there&amp;#x27;s more charges ahead.&lt;/p&gt;
&lt;p&gt;I spoke with one of these providers last year, trying to get them to understand how small app developers need affordable commercial use data. They presented a startup-focused plan that they were pitching for $499/month in addition to a per-MAU fee. Even that I would still consider very expensive for my needs, and it seems like that may have fallen by the wayside and is no longer promoted on their website.&lt;/p&gt;
&lt;p&gt;I could spend months developing Stocketa, incurring that monthly API cost until I went live (or risk getting shutdown if I tried to use a cheaper non-commercial plan during the TestFlight phase) then not make enough money to cover costs on the App Store and have to shut it down shortly after anyways.&lt;/p&gt;
&lt;p&gt;Overall, it feels like no one in this cares to cater to the developer and startup crowd, or they believe there&amp;#x27;s not much money for them to pursue there. This was the same case with Plaid when I briefly looked into getting access to their investments API to automatically sync holdings data. They required some sort of minimum length contract.&lt;/p&gt;
&lt;h3&gt;Why I stopped working on Stocketa&lt;/h3&gt;
&lt;p&gt;When I started this project, I really began to wonder why where weren&amp;#x27;t any great indie apps focused on stocks like this. Just simple, well-designed apps to help the casual investor keep track of their investments. Well I think I was starting to get my answer: getting quality financial data, even just for the United States, is a nightmare. You can only get it if you have a lot of money to invest in your project. And good luck if you really want a great API with comprehensive international data.&lt;/p&gt;
&lt;p&gt;I can&amp;#x27;t help but draw parallels to what happened recently with the Twitter and Reddit APIs. The risk of running your project or business solely on top of another company&amp;#x27;s API, no matter how reliable you think they may be, is extremely high.&lt;/p&gt;
&lt;p&gt;Stocketa was always meant to be a side project—never a real business. For it to be a real business, I would need to keep adding functionality and introducing more ways to make money. I couldn&amp;#x27;t just have a simple and elegant little app tracking your stocks. It would have to do more, like dipping into financial advice services, or offering stock trading. That&amp;#x27;s a hyper-competitive space lined with massive companies. That would mean a busier, cluttered app. The exact reason why I started Stocketa was because of all those cluttered apps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I decided to stop working on Stocketa for a few reasons:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data:&lt;/strong&gt; As mentioned above, all the expenses and challenges associated with acquiring reliable, affordable, and high-quality financial data that would make it feasible to release this as a reasonably priced subscription-based app.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Support:&lt;/strong&gt; The associated investment in customer support. Based on how people were using my TestFlight, it was going to be a significant investment to keep up with support, emails, maintenance, and ongoing feature development. However, part of this was likely due to complexity incurred from having to fix or skirt around unreliable data. There were lots of things I wanted to keep improving and refining with the app and that wouldn&amp;#x27;t end any time soon.&lt;/p&gt;
&lt;p&gt;If I were to release and charge for Stocketa, I would feel especially on the hook to resolve issues in a timely manner and that&amp;#x27;s not something I can accomodate now because of the next point.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Time:&lt;/strong&gt; And most importantly, I&amp;#x27;ve found something more worthy of my complete focus and attention: Rewind AI. I sunk a lot of nights and weekends into Stocketa over the years. It consumed every spare moment, even taking over my occasional time writing blog posts here when I had something to share. That was something I really missed. Now, I would much rather be focusing my creative thinking time on how I can improve and grow Rewind.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I originally started working on Stocketa with one goal: to learn Swift and SwiftUI my way with a project that was interesting to me. At the time that was scratching my own itch of keeping track of my investments. While it turned into a much larger and unshipped project, I learned a ton about native iOS development and was able to push myself creatively.&lt;/p&gt;
&lt;p&gt;These days I&amp;#x27;m happiest investing my design, product and development time into Rewind AI, where my SwiftUI knowledge learned with Stocketa has been tremendously helpful.&lt;/p&gt;
&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;hr /&gt;&lt;br /&gt;&lt;h3&gt;Footnotes&lt;/h3&gt;&lt;p&gt;&lt;a href="#r1"&gt;1&lt;/a&gt;&lt;!-- --&gt; I only had to go down to UIKit a few times. One time for things like custom text fields for more control over interactions, styling, text input, and formatting. Another to use a CollectionView for drag-to-reorder stocks. A third time for creating particle emitters in a few places, like confetti effects, as well as one area where I have some twinkling stars in a night illustration when the stock market is closed. And one last time to get more control over gestures. I needed to get a location when a long press &lt;em&gt;begins&lt;/em&gt; and SwiftUI LongPressGesture() didn&amp;#x27;t provide that, so I used UILongPressGestureRecognizer instead.&lt;/p&gt;</description><author>Paul Stamatiou</author><pubDate>Mon, 16 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://paulstamatiou.com/stocketa</guid></item><item><title>2023–10–15: New GIT mirror</title><link>https://xnux.eu/log/#096</link><author>megi's PinePhone Development Log</author><pubDate>Sun, 15 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#096</guid></item><item><title>2023–10–14: Orange Pi 5 Plus and QuartzPro64 upstreamed</title><link>https://xnux.eu/log/#095</link><author>megi's PinePhone Development Log</author><pubDate>Sat, 14 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#095</guid></item><item><title>Increment only numbers matching regex in Vim</title><link>https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/</link><description>&lt;p&gt;Long-time vim or neovim users are probably already aware that visually selecting a block of text then pressing CTRL + A in vim will result in any numbers in the selected block of text being incremented by 1. This works &amp;#8230; &lt;a href="https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/"&gt;Increment only numbers matching regex in Vim&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Fri, 13 Oct 2023 20:47:14 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/</guid></item><item><title>What I’m up to - October 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-october-2023</link><description>&lt;div class="prose"&gt;
  &lt;div&gt;This is my monthly newsletter about what I'm up to, which &lt;a href="https://www.philipithomas.com/posts/how-to-replace-social-media-with-a-personal-newsletter"&gt;I send in place of social media&lt;/a&gt;.&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;✨ What I was up to in September&lt;/strong&gt;&lt;/h2&gt;&lt;div&gt;I launched &lt;a href="https://booklet.group"&gt;Booklet&lt;/a&gt;, a forum as an alternative to chat. It's my attempt to make communication more asynchronous for professional groups. Knowledge workers &lt;a href="https://blog.rescuetime.com/slack-and-email-cost/"&gt;check chat every 6 minutes&lt;/a&gt;, fracturing their attention and preventing deep work. Booklet solves this by encouraging long-form, threaded discussions - and summarizing all activity into one single email per day.&lt;br /&gt; &lt;br /&gt;I wrote about my motivation for Booklet &lt;a href="https://www.contraption.co/news/launching-booklet/"&gt;on the Contraption Company site&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The actual launch of Booklet went better than expected - with thousands of visitors in the first day, and hundreds of communities created. I've been iterating rapidly based on user feedback, including rolling out a &lt;a href="https://hq.booklet.group/posts/new-in-booklet-better-newsletters-with-ai"&gt;variety of OpenAI-powered tools&lt;/a&gt; to improve the email summary. &lt;br /&gt;&lt;br /&gt;Some of the early use case for Booklet have been:&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Investors connecting their portfolio founders in a network&lt;/li&gt;
&lt;li&gt;A marketplace building a community of workers to engage them between gigs&lt;/li&gt;
&lt;li&gt;A Substack newsletter adding a subscriber community&lt;/li&gt;
&lt;li&gt;Companies replacing Google Groups for internal announcements&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;If you know anybody looking for a solution like these, please send them to Booklet. And, you can try out Booklet by joining its customer community at&lt;a href="https://hq.booklet.group"&gt; hq.booklet.group&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Besides that, I had a wonderful trip to Stockholm where the &lt;a href="https://www.vasamuseet.se/en"&gt;Vasa Museum&lt;/a&gt; fascinated me, I ate too many cardamom buns, and I stopped by the &lt;a href="https://www.nobelprize.org/about/nobel-museum/"&gt;Nobel Prize Museum&lt;/a&gt;. I also attended the New York Coffee Festival, and briefly stopped by Chicago for a wedding.&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;🤔 Things to share&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles&lt;/strong&gt;: &lt;a href="https://craigmod.com/ridgeline/170/"&gt;The Magical Japanese Art of Luggage Forwarding&lt;/a&gt;, &lt;a href="https://www.hugo.pm/the-loneliness-economy/"&gt;The Loneliness Economy&lt;/a&gt;, &lt;a href="https://johnnybowman.substack.com/p/protocol-daddies-3a0"&gt;Protocol Daddies&lt;/a&gt;, &lt;a href="http://catb.org/jargon/html/"&gt;The Jargon File&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books&lt;/strong&gt;: &lt;a href="https://www.amazon.com/Coddling-of-American-Mind-audiobook/dp/B079P7PDWB/ref=sr_1_1?crid=3OBJG2MV94CK5&amp;amp;keywords=coddling+of+the+american+mind&amp;amp;qid=1697139596&amp;amp;sprefix=coddling+of+the+american+mi%2Caps%2C269&amp;amp;sr=8-1"&gt;Coddling of the American Mind&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Podcasts&lt;/strong&gt;: &lt;a href="https://www.youtube.com/watch?v=aZ-BjJZxNoA"&gt;Airbnb CEO Brian Chesky on early rejection, customer focus&lt;/a&gt; - a look at how Jony Ive is turning Airbnb into a centralized product organization (&lt;em&gt;see "Trend" below&lt;/em&gt;). &lt;a href="https://timwendelboe.podbean.com/e/episode-29-three-star-coffee-service-coffee-at-restaurant-noma-with-carolyne-lane/"&gt;Three Star Coffee Service: Coffee at restaurant Noma&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps&lt;/strong&gt;: &lt;a href="https://www.helicone.ai"&gt;Helicone&lt;/a&gt; for OpenAI monitoring and caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video&lt;/strong&gt;: "&lt;a href="https://www.youtube.com/watch?v=dZxbVGhpEkI"&gt;How to Live an Asymmetric Life&lt;/a&gt;" &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trend: &lt;/strong&gt;With the downturn, more tech CEOs seem to be retaking the "Head of product" role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffees I'm drinking: &lt;/strong&gt;&lt;a href="https://mvsm.coffee/products/super-natural-ethiopia-natural"&gt;Man Vs. Machine Supernatural,&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;&lt;a href="https://www.heartroasters.com/products/guatemala-amate-1"&gt;Heart Guatemala Amate&lt;/a&gt;, and notable IRL coffees at &lt;a href="http://cafepascal.se/"&gt;Café Pascal&lt;/a&gt; and &lt;a href="https://www.dropcoffee.com/"&gt;Drop Coffee&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;&lt;strong&gt;📫 What I'm up to in September&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Working on Booklet, and enjoying a first month without travel in a long time.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;&lt;strong&gt;📍 Where I'll be &lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;NYC all month 🗽&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;em&gt;(Let me know if we overlap)&lt;/em&gt;&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;📸 Photo&lt;/strong&gt;&lt;/h2&gt;&lt;div&gt;
&lt;br /&gt;&lt;figure class="attachment attachment--preview attachment--JPG"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBZ2xrIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--883c6755b0ad5a1103cad4001717d6635a569f2a/IMG_9933.JPG" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Caradmom buns for fika in Stockholm&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;&lt;div&gt;&lt;figure class="attachment attachment--preview attachment--JPG"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBZ3RrIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--811391dbb7a418e5e2bbe6ecb56e97cce1a401ef/IMG_0081.JPG" /&gt;

  &lt;figcaption class="attachment__caption"&gt;The Vasa - a ~500 year old ship that was salvaged and now is on display in an eponymous museum in Stockholm&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;figure class="attachment attachment--preview attachment--JPG"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBZ3BrIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--113a15e1e439a1eca7be64bbf1b36466dec4f1d3/IMG_0089.JPG" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Intricate carvings on the Vasa&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Thu, 12 Oct 2023 22:58:04 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-october-2023</guid></item><item><title>Football Queries, part 1</title><link>https://rjp.is/blogging/posts/2023/10/football-queries/</link><description>In which we note some queries on the football database.</description><author>infrequent oscillations</author><pubDate>Tue, 10 Oct 2023 09:55:39 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/10/football-queries/</guid></item><item><title>Please to meet you, hope you guessed my name?</title><link>https://blog.steve.fi/please_to_meet_you__hope_you_guessed_my_name_.html</link><description>&lt;p&gt;"Hello, my name is Steve" - those are words I've said a million times in my life, however they are not true words.&lt;/p&gt;

&lt;p&gt;If you want to get all &lt;em&gt;technical&lt;/em&gt; about things, my name has always been SteveN.&lt;/p&gt;

&lt;p&gt;Mostly this hasn't mattered to me, or anybody else, I introduce myself as Steve, people call me Steve, and Steve is the name that makes me turn my head, when shouted across a bar.    However things changed when I moved to Finland.&lt;/p&gt;

&lt;p&gt;In Finland I had to open new bank accounts, sign mortgages, hand over IDs, and there were many many pieces of paper I signed, or forms I filled out.  Unfortunately I screwed up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If I were thinking clearly I'd think "Oh, this is something &lt;em&gt;official&lt;/em&gt;, I'd best write SteveN".&lt;/li&gt;
&lt;li&gt;If I were distracted, or not being careful I'd write my name as "Steve", and then sign it as Steve.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The end result?  I've been in Finland for approximately eight years, and I have some official documentation calling me Steve, and some other official documentation calling myself Steven.  (For example my "Permanent Residency Permit" calls me Steve, but my Social Security ID knows me as Steven.)&lt;/p&gt;

&lt;p&gt;Every now and again somebody queries the mismatch, and there are daily moments of pain where I have to interact with different agencies, so I made the obvious decision:  I'm gonna change my name.&lt;/p&gt;

&lt;p&gt;A fee of €60 and a simple online form was sufficient to initiate the process.  The processing time was given as "one to five months" on the &lt;a href="https://dvv.fi/en/changing-forename" rel="nofollow"&gt;official forename changing page&lt;/a&gt;, but happily the process was complete in a month.&lt;/p&gt;

&lt;p&gt;I will now need to do a little housekeeping by getting updated bank-cards, etc, and then complete the process by changing my UK passport to match.  Hopefully this won't take too long - but I guess if Finland knows me as Steve and the UK knows me as Steven I'll still be in a bit of a screwed up state, albeit one that is consistent in each country!&lt;/p&gt;

&lt;p&gt;Not a big change really, but also it feels weird to suddenly say "Hello, my name is Steve" and mean it.&lt;/p&gt;

&lt;p&gt;People are weird.&lt;/p&gt;

&lt;p&gt;Names are interesting.&lt;/p&gt;

&lt;p&gt;The end.&lt;/p&gt;

&lt;p&gt;Fin.&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Mon, 09 Oct 2023 12:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/please_to_meet_you__hope_you_guessed_my_name_.html</guid></item><item><title>tcpproxy 0.4 released</title><link>https://neosmart.net/blog/tcpproxy-0-4-released/</link><description>&lt;p&gt;This blog post was a bit delayed in the pipeline, but a new release of tcproxy, our educational async (tokio) rust command line proxy project, is now available for download (precompiled binaries or install via cargo). I was actually surprised &amp;#8230; &lt;a href="https://neosmart.net/blog/tcpproxy-0-4-released/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/tcpproxy-0-4-released/"&gt;tcpproxy 0.4 released&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Sun, 08 Oct 2023 21:53:36 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/tcpproxy-0-4-released/</guid></item><item><title>Moving Marginalia to a New Server</title><link>https://www.marginalia.nu/log/90-new-server-design/</link><description>So the search engine is moving to a new server soon, thanks to the generous grant mentioned recently.
If you visit search.marginalia.nu now, it may or may not use the old or new server. It&amp;rsquo;ll be like this for a while, since I need them both for testing and maintenance type work.
I&amp;rsquo;ll also apologize if this post is a bit chaotic. It is a reflection of a very chaotic couple of weeks that apart from setting up this migration also involved a very short notice invitation for a presentation at ossym23.</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 07 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/90-new-server-design/</guid></item><item><title>Go database driver overhead on insert-heavy workloads</title><link>http://notes.eatonphil.com/2023-10-05-go-database-sql-overhead-on-insert-heavy-workloads.html</link><description>&lt;p&gt;The most popular SQLite and PostgreSQL database drivers in Go are
(roughly) 20-76% slower than alternative Go drivers on insert-heavy
benchmarks of mine. So if you are bulk-inserting data with Go (and
potentially also bulk-retrieving data with Go), you may want to
consider the driver carefully. And you may want to consider avoiding
&lt;code&gt;database/sql&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Some driver authors have
&lt;a href="https://github.com/lib/pq/issues/771"&gt;noted&lt;/a&gt; and
&lt;a href="https://github.com/ClickHouse/clickhouse-go/tree/main#benchmark"&gt;benchmarked&lt;/a&gt;
issues with
&lt;a href="https://github.com/jackc/pgx#choosing-between-the-pgx-and-databasesql-interfaces"&gt;database/sql&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So it may be the case that &lt;code&gt;database/sql&lt;/code&gt; is responsible for some of
this overhead. And indeed the variations between drivers in this post
will be demonstrated by using &lt;code&gt;database/sql&lt;/code&gt; and avoiding it. This post
won't specifically prove that the variation is due to the
&lt;code&gt;database/sql&lt;/code&gt; interface. But that doesn't change the premise.&lt;/p&gt;
&lt;p class="note"&gt;
  Not covered in this post but something to consider:
  JetBrains &lt;a href="https://blog.jetbrains.com/go/2023/04/27/comparing-db-packages/"&gt;has
  suggested&lt;/a&gt; that other frontends like sqlc, sqlx, and GORM do
  worse than &lt;code&gt;database/sql&lt;/code&gt;.
&lt;/p&gt;&lt;p&gt;This post is built on the workload, environment, libraries, and
methodology in my &lt;a href="https://github.com/eatonphil/databases-intuition"&gt;databases-intuition repo on
GitHub&lt;/a&gt;. See the
repo for details that will help you reproduce or correct me.&lt;/p&gt;
&lt;h3 id="insert-workload"&gt;INSERT workload&lt;/h3&gt;&lt;p&gt;In this workload, the data is random and there are no indexes. Neither
of these aspects matter for this post though because we're comparing
behavior within the same database among different drivers. This was
just a workload I already had.&lt;/p&gt;
&lt;p&gt;Two different data sizes are tested:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;10M rows with 16 columns, each column is 32 bytes&lt;/li&gt;
&lt;li&gt;10M rows with 3 columns, each column is 8 bytes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each test is run 10 times and we record median, standard deviation,
min, max and throughput.&lt;/p&gt;
&lt;h3 id="sqlite"&gt;SQLite&lt;/h3&gt;&lt;p&gt;Both variations presented here load 10M rows using a single prepared
statement called for each row within a single transaction.&lt;/p&gt;
&lt;p&gt;The most popular driver is
&lt;a href="https://github.com/mattn/go-sqlite3"&gt;mattn/go-sqlite3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is roughly 20-40% slower than another driver that avoids
&lt;code&gt;database/sql&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;10M Rows, 16 columns, each column 32 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;56&lt;/span&gt;.53&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.26s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;55&lt;/span&gt;.05s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;.62s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;176&lt;/span&gt;,893.65&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,853.90&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;167&lt;/span&gt;,719.97&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;181&lt;/span&gt;,646.02&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;10M Rows, 3 columns, each column 8 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;.92&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.25s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;.69s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;.67s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;628&lt;/span&gt;,044.37&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;,703.92&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;599&lt;/span&gt;,852.91&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;637&lt;/span&gt;,435.60&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The other driver I tested is my own fork of
&lt;a href="https://github.com/bvinc/go-sqlite-lite"&gt;bvinc/go-sqlite-lite&lt;/a&gt; called
&lt;a href="https://github.com/eatonphil/gosqlite"&gt;eatonphil/gosqlite&lt;/a&gt;. I forked
it because it is unmaintained and I wanted to bring it up-to-date for
tests like this.&lt;/p&gt;
&lt;p&gt;10M Rows, 16 columns, each column 32 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;45&lt;/span&gt;.51&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.70s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;43&lt;/span&gt;.72s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;45&lt;/span&gt;.93s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;219&lt;/span&gt;,729.65&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,447.56&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;217&lt;/span&gt;,742.98&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;228&lt;/span&gt;,711.51&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;10M Rows, 3 columns, each column 8 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.44&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.20s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.02s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.68s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;957&lt;/span&gt;,939.60&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;,879.43&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;936&lt;/span&gt;,114.60&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;998&lt;/span&gt;,426.62&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="postgresql"&gt;PostgreSQL&lt;/h3&gt;&lt;p&gt;Both variations presented use PostgreSQL's &lt;a href="https://www.postgresql.org/docs/current/sql-copy.html"&gt;&lt;code&gt;COPY
FROM&lt;/code&gt;&lt;/a&gt;
support. This is significantly faster for PostgreSQL than doing the
prepared statement we do in
SQLite. (&lt;a href="https://github.com/eatonphil/databases-intuition#postgresql-prepared-insert"&gt;Here&lt;/a&gt;
are my results for doing prepared statement INSERTs in PostgreSQL if
you are curious.)&lt;/p&gt;
&lt;p&gt;The most popular PostgreSQL driver is
&lt;a href="https://github.com/lib/pq"&gt;lib/pq&lt;/a&gt;. The &lt;a href="https://github.com/lib/pq/issues/771"&gt;performance
issues&lt;/a&gt; with lib/pq are
&lt;a href="https://github.com/jackc/pgx#choosing-between-the-pgx-and-databasesql-interfaces"&gt;well-known&lt;/a&gt;,
and the &lt;a href="https://github.com/lib/pq#status"&gt;repo itself&lt;/a&gt; is marked as
no longer developed.&lt;/p&gt;
&lt;p&gt;It is roughly 44-76% slower than an alternative driver that avoids
&lt;code&gt;database/sql&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;10M Rows, 16 columns, each column 32 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;104&lt;/span&gt;.53&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.40s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;102&lt;/span&gt;.57s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;110&lt;/span&gt;.08s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;95&lt;/span&gt;,665.37&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;,129.25&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;90&lt;/span&gt;,847.08&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;97&lt;/span&gt;,490.96&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;10M Rows, 3 columns, each column 8 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.16&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.43s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.44s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.80s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,225,986.47&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;,631.53&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,136,581.82&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,343,441.37&lt;span class="w"&gt; &lt;/span&gt;rows
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The other driver I tested is
&lt;a href="https://github.com/jackc/pgx"&gt;jackc/pgx&lt;/a&gt;, without &lt;code&gt;database/sql&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;10M Rows, 16 columns, each column 32 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;46&lt;/span&gt;.54&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.60s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;44&lt;/span&gt;.09s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;49&lt;/span&gt;.51s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;214&lt;/span&gt;,869.42&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;,265.10&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;201&lt;/span&gt;,991.37&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;226&lt;/span&gt;,801.07&lt;span class="w"&gt; &lt;/span&gt;rows/s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;10M Rows, 3 columns, each column 8 bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Timing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.20&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.44s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.71s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.96s
Throughput:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,923,722.79&lt;span class="w"&gt; &lt;/span&gt;±&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;156&lt;/span&gt;,820.46&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Min:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,676,894.32&lt;span class="w"&gt; &lt;/span&gt;rows/s,&lt;span class="w"&gt; &lt;/span&gt;Max:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;,124,966.60&lt;span class="w"&gt; &lt;/span&gt;rows/
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The discrepancies here are even greater than with the different SQLite
drivers.&lt;/p&gt;
&lt;h3 id="workloads-with-small-resultset"&gt;Workloads with small resultset&lt;/h3&gt;&lt;p&gt;I won't go into as much detail but if you're doing queries that don't
return many rows, the difference between drivers is negligible.&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://github.com/eatonphil/databases-intuition#selects"&gt;here&lt;/a&gt; for details.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;If you are doing INSERT-heavy workloads, or you are processing large
number of rows returned from your SQL database, you might want to try
benchmarking the same workload with different drivers.&lt;/p&gt;
&lt;p&gt;And specifically, there is likely no good reason to use &lt;code&gt;lib/pq&lt;/code&gt;
anymore for accessing PostgreSQL from Go. Just use jackc/pgx.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;For INSERT-heavy workloads in Go, you may want to switch database drivers. For PostgreSQL and SQLite, the popular drivers are 20-76% slower for this workload in my tests.&lt;br /&gt;&lt;br /&gt;Some driver developers have reported issues with database/sql as an interface.&lt;a href="https://t.co/NLVp0P2uiV"&gt;https://t.co/NLVp0P2uiV&lt;/a&gt; &lt;a href="https://t.co/RxTbgMZ1MG"&gt;pic.twitter.com/RxTbgMZ1MG&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1710249941904351718?ref_src=twsrc%5Etfw"&gt;October 6, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 05 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-10-05-go-database-sql-overhead-on-insert-heavy-workloads.html</guid></item><item><title>Quitting while you're ahead</title><link>https://tiltingatwindmills.dev/quitting-while-youre-ahead/</link><description>I often wonder, how many actors are there who quietly disappeared beacause they
made a bunch of money from one lucky success? Hollywood…</description><author>Tilting at Windmills</author><pubDate>Thu, 05 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://tiltingatwindmills.dev/quitting-while-youre-ahead/</guid></item><item><title>Throughput Theater</title><link>https://jodavaho.io/posts/dev-throughput-theater-1.html</link><description>&lt;blockquote&gt;
&lt;p&gt;I regret to inform you that member initialization lists will not make your program noticeably faster.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The point of this post is to vent, so it is highly opinionated.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Style-based performance optimizations are a waste of time&lt;/li&gt;
&lt;li&gt;Good fundamentals are more important than micro-optimizations&lt;/li&gt;
&lt;li&gt;Compile and test times are vastly underrated&lt;/li&gt;
&lt;li&gt;For some reason, code / function &lt;em&gt;generalization&lt;/em&gt; is valued higher than &lt;em&gt;ergonomics&lt;/em&gt; or &lt;em&gt;real performance&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I form this opinion based on experiences with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The near-constant debate over style, move semantics, constructor initialization lists, and other micro-optimizations&lt;/li&gt;
&lt;li&gt;Making small changes and seeing large performance gains, sometimes 1,000-100,000x by:
&lt;ul&gt;
&lt;li&gt;Using hardware more efficiently&lt;/li&gt;
&lt;li&gt;Reframing the core problem&lt;/li&gt;
&lt;li&gt;Using the right data structures&lt;/li&gt;
&lt;li&gt;Using the right algorithms&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, if you&amp;rsquo;re worried about the performance problems with initialize+copy vs std::move, you&amp;rsquo;re either doing something very wrong or you&amp;rsquo;re down to the last 0.1% of performance improvements.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Thu, 05 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/dev-throughput-theater-1.html</guid></item><item><title>Perl One-Liners Guide book announcement</title><link>https://learnbyexample.github.io/perl-oneliners-guide-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;Perl One-Liners Guide&lt;/strong&gt; ebook. Examples, exercises, solutions, descriptions and external links were added/updated/corrected.&lt;/p&gt;
&lt;p&gt;When it comes to command line text processing, there are several well known tools like &lt;code&gt;grep&lt;/code&gt; for filtering, &lt;code&gt;sed&lt;/code&gt; for substitution and &lt;code&gt;awk&lt;/code&gt; for field processing. Compared to such tools, &lt;strong&gt;Perl has a feature rich regular expression engine, plenty of builtin modules and a thriving ecosystem&lt;/strong&gt;. Another advantage is that Perl is more &lt;strong&gt;portable&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This ebook will show examples for filtering and substitution features, field processing, using standard and third-party modules, multiple file processing, how to construct solutions that depend on multiple records, how to compare records and fields between two or more files, how to identify duplicates while maintaining input order and so on.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of &lt;strong&gt;Perl One-Liners Guide&lt;/strong&gt; for FREE till 07-October-2023. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/perl-oneliners"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/perl-oneliners/c/new_perl_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;All Books Bundle&lt;/strong&gt; is just $12 (normal price $32), includes all my 13 programming ebooks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/new_perl_release"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/learnbyexample-all-books/c/new_perl_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Command version updated to &lt;strong&gt;Perl 5.38.0&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;option &lt;code&gt;-g&lt;/code&gt; slurps entire file contents&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Many more exercises added&lt;/li&gt;
&lt;li&gt;Long sections split into smaller ones&lt;/li&gt;
&lt;li&gt;In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;Book title changed to &lt;strong&gt;Perl One-Liners Guide&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;On this blog, I &lt;a href="https://learnbyexample.github.io/tips/"&gt;post tips&lt;/a&gt; covering Python, command line tools and Vim. Here are video demos for these tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=THSMmCZQn1A&amp;amp;list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&amp;amp;list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;This is fantastic! 👏 I use Perl one-liners for record and text processing a lot and this will be definitely something I will keep coming back to - I’ve already learned a trick from “Context Matching” (9) 🙂&lt;/p&gt;
&lt;p&gt;— &lt;a href="https://programming.dev/comment/3277968"&gt;feedback on Linux@lemmy.ml&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;One-liner introduction&lt;/li&gt;
&lt;li&gt;Line processing&lt;/li&gt;
&lt;li&gt;In-place file editing&lt;/li&gt;
&lt;li&gt;Field separators&lt;/li&gt;
&lt;li&gt;Record separators&lt;/li&gt;
&lt;li&gt;Using modules&lt;/li&gt;
&lt;li&gt;Multiple file input&lt;/li&gt;
&lt;li&gt;Processing multiple records&lt;/li&gt;
&lt;li&gt;Two file processing&lt;/li&gt;
&lt;li&gt;Dealing with duplicates&lt;/li&gt;
&lt;li&gt;Perl rename command&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/learn_perl_oneliners/"&gt;https://learnbyexample.github.io/learn_perl_oneliners/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/learn_perl_oneliners"&gt;https://github.com/learnbyexample/learn_perl_oneliners&lt;/a&gt; for markdown source, example files, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tips, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/learn_perl_oneliners/issues"&gt;https://github.com/learnbyexample/learn_perl_oneliners/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 04 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/perl-oneliners-guide-announcement/</guid></item><item><title>Evolution of value</title><link>http://blog.onepatchdown.net/philosophy/2023/10/03/evolution-of-value/</link><description>&lt;h3 id="the-evolution-of-value-from-land-labor-and-capital-to-trust-time-and-perspective"&gt;The Evolution of Value: From Land, Labor, and Capital to Trust, Time, and Perspective&lt;/h3&gt;

&lt;h4 id="introduction"&gt;Introduction&lt;/h4&gt;

&lt;p&gt;The world has undergone significant transformations in the way value is created, measured, and exchanged. Traditionally, the three pillars of economic value were land, labor, and capital. However, in the modern era, these have been supplanted by less tangible but equally vital resources: trust, time, and perspective (POV).&lt;/p&gt;

&lt;h4 id="the-traditional-triad-land-labor-and-capital"&gt;The Traditional Triad: Land, Labor, and Capital&lt;/h4&gt;

&lt;h5 id="land"&gt;Land&lt;/h5&gt;
&lt;p&gt;In agrarian societies, land was the primary source of wealth. It provided the raw materials for food, shelter, and trade. Control over land often equated to power and economic stability.&lt;/p&gt;

&lt;h5 id="labor"&gt;Labor&lt;/h5&gt;
&lt;p&gt;Labor transformed raw materials into goods. The skill and efficiency of labor determined the quality and quantity of output, affecting the overall economic health of a society.&lt;/p&gt;

&lt;h5 id="capital"&gt;Capital&lt;/h5&gt;
&lt;p&gt;Capital, both financial and physical, acted as the catalyst for growth. It enabled investment in technology and infrastructure, facilitating the expansion of both land and labor productivity.&lt;/p&gt;

&lt;h4 id="the-modern-triad-trust-time-and-perspective"&gt;The Modern Triad: Trust, Time, and Perspective&lt;/h4&gt;

&lt;h5 id="trust"&gt;Trust&lt;/h5&gt;
&lt;p&gt;In today’s interconnected world, trust has become a cornerstone of value. Whether it’s trust in a brand, a technology, or a system, it enables transactions and relationships that form the basis of modern economies.&lt;/p&gt;

&lt;h5 id="time"&gt;Time&lt;/h5&gt;
&lt;p&gt;Time has always been a finite resource, but its importance has escalated in the age of information. Efficiency, speed, and the ability to manage time effectively are now critical factors in economic success.&lt;/p&gt;

&lt;h5 id="perspective"&gt;Perspective&lt;/h5&gt;
&lt;p&gt;In a world saturated with information and options, perspective or POV has become a valuable asset. Unique insights, innovative thinking, and the ability to solve complex problems are highly sought after.&lt;/p&gt;

&lt;h4 id="the-shift-explained"&gt;The Shift Explained&lt;/h4&gt;

&lt;h5 id="technological-advancements"&gt;Technological Advancements&lt;/h5&gt;
&lt;p&gt;The digital revolution has minimized the importance of land and physical capital. Virtual real estate and digital assets have become new forms of land and capital.&lt;/p&gt;

&lt;h5 id="globalization"&gt;Globalization&lt;/h5&gt;
&lt;p&gt;The global market has made labor more of a commodity, easily outsourced or automated. Trust, however, cannot be outsourced; it must be built and maintained.&lt;/p&gt;

&lt;h5 id="information-age"&gt;Information Age&lt;/h5&gt;
&lt;p&gt;The explosion of information has made time and perspective more valuable. The ability to quickly assimilate information and offer a unique POV is invaluable.&lt;/p&gt;

&lt;h4 id="conclusion"&gt;Conclusion&lt;/h4&gt;

&lt;p&gt;The transition from land, labor, and capital to trust, time, and perspective reflects the evolving complexities of the modern world. While the traditional triad laid the foundation for economic systems, the modern triad is shaping the future, emphasizing the intangible yet indispensable resources that drive today’s economies.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Wed, 04 Oct 2023 00:09:00 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/philosophy/2023/10/03/evolution-of-value/</guid></item><item><title>Four Pillars of Disagreement</title><link>http://blog.onepatchdown.net/philosophy/2023/10/03/four-pillars-disagreement/</link><description>&lt;h2 id="the-four-pillars-of-disagreement-non-negotiable-categories"&gt;The Four Pillars of Disagreement: Non-Negotiable Categories&lt;/h2&gt;

&lt;p&gt;Disagreements aren’t just random occurrences; they are structured, predictable, and most importantly, classifiable. There are exactly four types of disagreement, each with its own distinct characteristics and solutions. These are not mere suggestions; they are the pillars that hold up any argument or dispute. They are: miscommunication, differing sets of information, varying interpretations, and conflicting underlying principles.&lt;/p&gt;

&lt;h3 id="miscommunication-the-unquestionable-culprit"&gt;Miscommunication: The Unquestionable Culprit&lt;/h3&gt;
&lt;p&gt;Let’s get this straight: when two colleagues can’t even agree on a meeting time—one thinking it’s at 3 PM and the other at 4 PM—that’s not just an oversight. It’s a glaring example of miscommunication, pure and simple. The solution is equally straightforward: clarify, confirm, and move on.&lt;/p&gt;

&lt;h3 id="differing-sets-of-information-the-undeniable-divider"&gt;Differing Sets of Information: The Undeniable Divider&lt;/h3&gt;
&lt;p&gt;Consider two employees locked in debate over a new company policy. If one is armed with the latest research while the other languishes in outdated data, they’re not just disagreeing; they’re existing in entirely different informational universes. The fix? Update, align, and proceed. Anything less is a disservice to the discussion.&lt;/p&gt;

&lt;h3 id="varying-interpretations-the-inarguable-confounder"&gt;Varying Interpretations: The Inarguable Confounder&lt;/h3&gt;
&lt;p&gt;Text messages are not just words on a screen; they are a battleground for interpretation. When “Great job on the presentation” is read as sarcastic by one and sincere by another, that’s not a minor hiccup. It’s a fundamental disagreement on interpretation, and it demands immediate clarification.&lt;/p&gt;

&lt;h3 id="conflicting-underlying-principles-the-irrefutable-core"&gt;Conflicting Underlying Principles: The Irrefutable Core&lt;/h3&gt;
&lt;p&gt;When two friends argue politics from opposing ideological platforms—one valuing individual freedom and the other social equality—there’s no skirting the issue. This is a clash of principles, as foundational as it gets. Don’t expect a quick fix; this type of disagreement calls for deep reflection and, often, an acceptance of ideological diversity.&lt;/p&gt;

&lt;h3 id="conclusion-the-immutable-truth"&gt;Conclusion: The Immutable Truth&lt;/h3&gt;
&lt;p&gt;These are not just types of disagreements; they are the framework within which all disagreements occur. Whether it’s the undeniable role of miscommunication, the clear-cut impact of differing information, the indisputable effect of varying interpretations, or the unyielding influence of conflicting principles, these four categories are the bedrock of all disputes. Recognize them, address them, and only then can you navigate the complex landscape of human disagreement. The goal is not to win, but to understand.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Tue, 03 Oct 2023 04:00:00 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/philosophy/2023/10/03/four-pillars-disagreement/</guid></item><item><title>A Tale of Two Pharmacies</title><link>http://pxtl.ca/2023/10/03/a-tale-of-two-pharmacies/</link><description>&lt;p&gt;What follows is the story of why I no longer go to my old pharmacy.  This is
recounted from memory, and some of the details may be inaccurate, but the gist
is there.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;drops in at local pharmacy&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"I'm running out of my meds, can you refill them?"&lt;/p&gt;

&lt;p&gt;"Sorry, you have to come closer to refill-day, so come back Saturday."&lt;/p&gt;

&lt;p&gt;"Okay, I'll come back then."&lt;/p&gt;

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

&lt;p&gt;"Okay,  it's Saturday, here to refill my meds."&lt;/p&gt;

&lt;p&gt;"... yeah, you're out of refills.  We'll have to talk to your doctor."&lt;/p&gt;

&lt;p&gt;"... but it's the weekend?"&lt;/p&gt;

&lt;p&gt;"Oh, we'll sort it out on Monday."&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Tue, 03 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/10/03/a-tale-of-two-pharmacies/</guid></item><item><title>Closing the gap on fediverse hashtag visibility with hashtag-importer</title><link>https://anisse.astier.eu/hashtag-importer-release.html</link><description>&lt;p&gt;I have released a small application, &lt;a href="https://github.com/anisse/hashtag-importer"&gt;hashtag-importer&lt;/a&gt;, that users of a Mastodon instance can use to slowly import more content from low-traffic hashtags, into their instance (with their admin's permission).&lt;/p&gt;
&lt;h1&gt;Why hashtag-importer&lt;/h1&gt;
&lt;p&gt;In the fediverse, your server might not see all posts made by everyone; it should only see posts …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Mon, 02 Oct 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/hashtag-importer-release.html</guid></item><item><title>Intercepting and modifying Linux system calls with ptrace</title><link>http://notes.eatonphil.com/2023-10-01-intercepting-and-modifying-linux-system-calls-with-ptrace.html</link><description>&lt;p&gt;How software fails is interesting. But real-world errors can be
infrequent to manifest. &lt;a href="https://course.ece.cmu.edu/~ece749/docs/faultInjectionSurvey.pdf"&gt;Fault
injection&lt;/a&gt;
is a formal-sounding term that just means: trying to explicitly
trigger errors in the hopes of discovering bad logic, typically
during automated tests.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jepsen-io/jepsen"&gt;Jepsen&lt;/a&gt;
and &lt;a href="https://github.com/Netflix/chaosmonkey"&gt;ChaosMonkey&lt;/a&gt; are two
famous examples that help to trigger process and network failure. But
what about disk and filesystem errors?&lt;/p&gt;
&lt;p&gt;A few avenues seem worth investigating:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A custom FUSE filesystem&lt;/li&gt;
&lt;li&gt;An LD_PRELOAD interception layer&lt;/li&gt;
&lt;li&gt;A ptrace system call interception layer&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;SECCOMP_RET_TRAP&lt;/code&gt; interception layer&lt;/li&gt;
&lt;li&gt;Or, symbolic analysis a la &lt;a href="https://research.cs.wisc.edu/adsl/Publications/alice-osdi14.html"&gt;Alice from University of
Wisconsin-Madison&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would like to try out FUSE sometime. But LD_PRELOAD layer only works
if IO goes through libc, which won't be the case for all
programs. ptrace is something I've wanted to dig into for years since
learning about
&lt;a href="https://www.usenix.org/system/files/hotcloud19-paper-young.pdf"&gt;gvisor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SECCOMP_RET_TRAP&lt;/code&gt; doesn't have the same high-level guides that ptrace
does so maybe I'll dig into it later. And symbolic analysis might be
able to detect bad workloads but it also isn't fault injection. Maybe
it's the better idea but fault injection just sounds more fun.&lt;/p&gt;
&lt;p&gt;So this particular post will cover intercepting system calls
(syscalls) using ptrace with code written in Zig. Not because readers
will likely write their own code in Zig but because hopefully the Zig
code will be easier for you to read and adapt to your language
compared to if we had to deal with the verbosity and inconvenience of
C.&lt;/p&gt;
&lt;p&gt;In the end, we'll be able to intercept and force short (incomplete)
writes in a Go, Python, and C program. Emulating a disk that is having
an issue completing the write. This is a case that isn't common, but
should probably be handled with retries in production code.&lt;/p&gt;
&lt;p&gt;This post corresponds roughly to &lt;a href="https://github.com/eatonphil/badio/tree/720c3ee0482e6dcb1dd49d1789bccf86747b7776"&gt;this
commit&lt;/a&gt;
on GitHub.&lt;/p&gt;
&lt;h3 id="a-bad-program"&gt;A bad program&lt;/h3&gt;&lt;p&gt;First off, let's write some code for a program that would exhibit a
short write. Basically, we write to a file and don't check how many
bytes we wrote. This is extremely common code; or at least I've
written it often.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;test.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_RDWR&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_CREATE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_TRUNC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mo"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;some great stuff&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With this code, if the &lt;code&gt;Write()&lt;/code&gt; call doesn't actually succeed in
writing everything, we won't know that. And the file will contain less
than all of &lt;code&gt;some great stuff&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This logical mistake will happen rarely, if ever, on a normal
disk. But it is possible.&lt;/p&gt;
&lt;p&gt;Now that we've got an example program in mind, let's see if we can
trigger the logic error.&lt;/p&gt;
&lt;h3 id="ptrace"&gt;ptrace&lt;/h3&gt;&lt;p&gt;ptrace is a somewhat cross-platform layer that allows you to intercept
syscalls in a process. You can read and modify memory and registers in
the process, when the syscalls starts and before it finishes.&lt;/p&gt;
&lt;p&gt;gdb and strace both use ptrace for their magic.&lt;/p&gt;
&lt;p&gt;Google's gvisor that &lt;a href="https://cloud.google.com/run/docs/container-contract"&gt;powers various serverless runtimes in Google
Cloud&lt;/a&gt; was also
historically based on ptrace (&lt;code&gt;PTRACE_SYSEMU&lt;/code&gt; specifically, which we
won't explore much in this post).&lt;/p&gt;
&lt;p class="note"&gt;
  Interestingly though,
  gvisor &lt;a href="https://gvisor.dev/blog/2023/04/28/systrap-release/"&gt;switched
  only this year &lt;/a&gt; (2023) to a different default backend for
  trapping system calls. Based
  on &lt;a href="https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt"&gt;&lt;code&gt;SECCOMP_RET_TRAP&lt;/code&gt;&lt;/a&gt;.
  &lt;br /&gt;
  &lt;br /&gt;
  You can get similar vibes
  from &lt;a href="https://www.brendangregg.com/blog/2014-05-11/strace-wow-much-syscall.html"&gt;this
  Brendan Gregg post&lt;/a&gt; on the dangers of using strace (that is based
  on ptrace) in production.
&lt;/p&gt;&lt;p&gt;Although ptrace is cross-platform, actually writing
cross-platform-aware code with ptrace can be complex. So this post
assumes amd64/linux.&lt;/p&gt;
&lt;h3 id="protocol"&gt;Protocol&lt;/h3&gt;&lt;p&gt;The ptrace protocol is described in the &lt;a href="https://man7.org/linux/man-pages/man2/ptrace.2.html"&gt;ptrace
manpage&lt;/a&gt;, but
&lt;a href="https://nullprogram.com/blog/2018/06/23/"&gt;Chris Wellons&lt;/a&gt; and &lt;a href="https://webdocs.cs.ualberta.ca/~paullu/C498/meng.ptrace.slides.pdf"&gt;a
University of Alberta
group&lt;/a&gt;
also wrote nice introductions. I referenced these three pages
heavily.&lt;/p&gt;
&lt;p&gt;Here's what the UAlberta page has to say:&lt;/p&gt;
&lt;p&gt;&lt;img alt="ptrace's syscall tracing protocol" src="/assets/ptraceprotocol.webp" /&gt;&lt;/p&gt;
&lt;p&gt;We fork and have the child call &lt;code&gt;PTRACE_TRACEME&lt;/code&gt;. Then we handle each
syscall entrance by calling &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt; and waiting with &lt;code&gt;wait&lt;/code&gt;
until the child has entered the syscall. It is in this moment we can
mess with things.&lt;/p&gt;
&lt;h3 id="implementation"&gt;Implementation&lt;/h3&gt;&lt;p&gt;Let's turn that graphic into Zig code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&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;&amp;quot;std&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@cImport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;@cInclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sys/ptrace.h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;@cInclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sys/user.h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;@cInclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sys/wait.h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;@cInclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;errno.h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?*&lt;/span&gt;&lt;span class="n"&gt;anyopaque&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// TODO //&lt;/span&gt;

&lt;span class="kr"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArenaAllocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&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="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_allocator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argsAlloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arena&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Fork failed!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Child process&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PTRACE_TRACEME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;arena&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="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;..],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Parent process&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childInterceptSyscalls&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So like the graphic suggested, we fork and start a child process. That
means this Zig program should be called like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;--library&lt;span class="w"&gt; &lt;/span&gt;c&lt;span class="w"&gt; &lt;/span&gt;main.zig
$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;/actual/program/to/intercept&lt;span class="w"&gt; &lt;/span&gt;--and&lt;span class="w"&gt; &lt;/span&gt;--its&lt;span class="w"&gt; &lt;/span&gt;args
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Presumably, as with strace or gdb, we could instead attach to an
already running process with &lt;code&gt;PTRACE_ATTACH&lt;/code&gt; or &lt;code&gt;PTRACE_SEIZE&lt;/code&gt; (based
on the &lt;a href="https://man7.org/linux/man-pages/man2/ptrace.2.html"&gt;ptrace
manpage&lt;/a&gt;) rather
than going the &lt;code&gt;PTRACE_TRACEME&lt;/code&gt; route. But I haven't tried that out
yet.&lt;/p&gt;
&lt;p&gt;With the child ready to be intercepted, we can implement the
&lt;code&gt;ChildManager&lt;/code&gt; that actually does the interception.&lt;/p&gt;
&lt;h4 id="childmanager"&gt;ChildManager&lt;/h4&gt;&lt;p&gt;The core of the &lt;code&gt;ChildManager&lt;/code&gt; is an infinite loop (at least as long
as the child process lives) that waits for the next syscall and then
calls a hook for the sytem call if it exists.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &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="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArenaAllocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// TODO //&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;childInterceptSyscalls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Handle syscall entrance&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childWaitForSyscall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IFEXITED&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getABIArguments&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Later we'll write a hook for the &lt;code&gt;sys_write&lt;/code&gt; syscall that
will force an incomplete write.&lt;/p&gt;
&lt;p&gt;Back to the protocol, &lt;code&gt;childWaitForSyscall&lt;/code&gt; will call &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt;
to allow the child process to start up again and continue until the
next syscall. We'll follow that by &lt;code&gt;wait&lt;/code&gt;-ing for the child
process to be stopped again so we can handle the syscall entrance.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;childWaitForSyscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PTRACE_SYSCALL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@bitCast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that we've intercepted a syscall (after &lt;code&gt;waitpid&lt;/code&gt; finishes
blocking), we need to figure out what syscall it was. We do this by
calling &lt;code&gt;PTRACE_GETREGS&lt;/code&gt; and reading the &lt;code&gt;rax&lt;/code&gt; register which
according to &lt;a href="https://stackoverflow.com/a/54957101/1507139"&gt;amd64/linux calling
convention&lt;/a&gt; is the
syscall called.&lt;/p&gt;
&lt;h4 id="registers"&gt;Registers&lt;/h4&gt;&lt;p&gt;&lt;code&gt;PTRACE_GETREGS&lt;/code&gt; fills out the &lt;a href="https://sites.uclouvain.be/SystInfo/usr/include/sys/user.h.html"&gt;following
struct&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;user_regs_struct&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r14&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r13&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rbp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rbx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rcx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rdx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rsi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rdi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orig_rax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eflags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fs_base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gs_base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;es&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's write a little amd64/linux-specific wrapper for accessing
meaningful fields.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_regs_struct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rdi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rsi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rdx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setNth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rdi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rsi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rdx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rax&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orig_rax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One thing to note is that the field we read to get &lt;code&gt;rax&lt;/code&gt; is not
&lt;code&gt;aa.regs.rax&lt;/code&gt; but &lt;code&gt;aa.regs.orig_rax&lt;/code&gt;. This is because &lt;code&gt;rax&lt;/code&gt; is also
the return value and &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt; gets called twice for some
syscalls on entrance and exit. The &lt;code&gt;orig_rax&lt;/code&gt; field preserves the
original &lt;code&gt;rax&lt;/code&gt; value on syscall entrance. You can read more about this
&lt;a href="https://stackoverflow.com/questions/6468896/why-is-orig-eax-provided-in-addition-to-eax/6469069#6469069"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="getting-and-setting-registers"&gt;Getting and setting registers&lt;/h4&gt;&lt;p&gt;Now let's write the &lt;code&gt;ChildManager&lt;/code&gt; code that actually calls
&lt;code&gt;PTRACE_GETREGS&lt;/code&gt; to fill out one of these structs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PTRACE_GETREGS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Setting registers is similar, we just pass the struct back and call
&lt;code&gt;PTRACE_SETREGS&lt;/code&gt; instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PTRACE_SETREGS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="a-hook"&gt;A hook&lt;/h4&gt;&lt;p&gt;Now we finally have enough code to write a hook that can get and set
registers; i.e. manipulate a system call!&lt;/p&gt;
&lt;p&gt;We'll start by registering a &lt;code&gt;sys_write&lt;/code&gt; hook in the &lt;code&gt;hooks&lt;/code&gt; field we
check in &lt;code&gt;childInterceptSyscalls&lt;/code&gt; above.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&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="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;anyerror&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}{.{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@intFromEnum&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syscalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writeHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we look at the &lt;a href="https://man7.org/linux/man-pages/man2/write.2.html"&gt;manpage for
&lt;code&gt;write&lt;/code&gt;&lt;/a&gt; we see it
takes three arguments&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The file descriptor (fd) to write to&lt;/li&gt;
&lt;li&gt;The address to start writing data from&lt;/li&gt;
&lt;li&gt;And the number of bytes to write&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Going back to the &lt;a href="https://stackoverflow.com/questions/2535989/what-are-the-calling-conventions-for-unix-linux-system-calls-and-user-space-f"&gt;calling
convention&lt;/a&gt;
that means the fd will be in &lt;code&gt;rdi&lt;/code&gt;, the data address in &lt;code&gt;rsi&lt;/code&gt;, and the
data length in &lt;code&gt;rdx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So if we shorten the data length, we should be creating a short
(incomplete) write.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;anyerror&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&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="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Truncate some bytes&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In a more sophisticated version of this program, we could randomly
decide when to truncate data and randomly decide how much data to
truncate. However, for our purposes this is sufficient.&lt;/p&gt;
&lt;p&gt;But there are some real problems with this code. When I ran this
program against a basic Go program, I saw duplicate requests.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Ah ok, PTRACE_SYSCALL gets hit when you both enter and exit a syscall.&lt;br /&gt;&lt;br /&gt;So each time you call PTRACE_SYSCALL and you do stuff, you just call it again afterwards to handle/wait for the exit. &lt;a href="https://t.co/PjmNwcMepG"&gt;pic.twitter.com/PjmNwcMepG&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1707846783035183267?ref_src=twsrc%5Etfw"&gt;September 29, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;p&gt;So the deal with &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt; is that for (most?) syscalls, you
get to modify data before the data actually is handled by the
kernel. And you get to modify data after the kernel has finished the
syscall too.&lt;/p&gt;
&lt;p&gt;This makes sense because &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt; (unlike &lt;code&gt;PTRACE_SYSEMU&lt;/code&gt;)
allows the syscall to actually happen. And if we wanted to, for
example, modify the syscall exit code, we'd have to do that after the
syscall was done not before it started. We are modifying registers
directly after all.&lt;/p&gt;
&lt;p&gt;All this means for our Zig code is that when we handle &lt;code&gt;sys_write&lt;/code&gt;, we
need to call &lt;code&gt;PTRACE_SYSCALL&lt;/code&gt; again to process the syscall
exit. Otherwise we'd reach this &lt;code&gt;writeHandler&lt;/code&gt; for both entries and
exits, which would require some additional way of disambiguating
entrances from exits.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;anyerror&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&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="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Truncate some bytes&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childReadData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&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;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Got a write on {}: {s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Handle syscall exit&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childWaitForSyscall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  We could put the &lt;code&gt;cm.childWaitForSyscall()&lt;/code&gt; waiting for
  the syscall exit in the main loop and I did try that at
  first. However, not all syscalls seemed to have the same entry and
  exit hook and this resulted in the hooks sometimes starting with a
  syscall exit rather than a syscall entry. So rather than making the
  code more complicated, I decided to only wait for the exit on
  syscalls I knew had an exit (by observation at least), like
  &lt;code&gt;sys_write&lt;/code&gt;.
&lt;/p&gt;&lt;h3 id="multiple-writes?-no-bad-logic?"&gt;Multiple writes? No bad logic?&lt;/h3&gt;&lt;p&gt;So I had this code as is, correctly handling syscall entrances and
exits, but I was seeing multiple write calls. And the text file I was
writing to had the complete text I wanted to write. There was no short
write even though I truncated the data length.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Ok so what happens in this Go program if I truncate the amount of data?&lt;br /&gt;&lt;br /&gt;I assumed Go would do nothing since all I did was call `f.Write()` once and `f.Write()` returns a number of bytes written.&lt;br /&gt;&lt;br /&gt;But actually, it still writes everything! &lt;a href="https://t.co/OSalKEbERM"&gt;pic.twitter.com/OSalKEbERM&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1707854642250408119?ref_src=twsrc%5Etfw"&gt;September 29, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;
&lt;p&gt;This took some digging into Go source code to understand. If you trace
what &lt;code&gt;os.File.Write()&lt;/code&gt; does on Linux you eventually get to
&lt;a href="https://cs.opensource.google/go/go/+/refs/tags/go1.21.1:src/internal/poll/fd_unix.go"&gt;src/internal/poll/fd_unix.go&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Write implements io.Writer.&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;FD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prepareWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IsStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;maxRW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;maxRW&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ignoringEINTRIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sysfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EAGAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ErrUnexpectedEOF&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This might be common knowledge but I didn't realize Go did this. And
when I tried out the same basic program in Python and even C, the
behavior was the same. The builtin &lt;code&gt;write()&lt;/code&gt; behavior on a file (in
many languages apparantly) is to retry until all data is written, with
some exceptions.&lt;/p&gt;
&lt;p&gt;This makes sense since files on disk, unlike file descriptors backed
by network sockets, are generally always available. Compared to a
network connection, disks are physically close and almost always
stay connected. (With some obvious exceptions like
network-attached storage and thumb drives.)&lt;/p&gt;
&lt;p&gt;So to trigger the short write, the easiest way seems to have the
&lt;code&gt;sys_write&lt;/code&gt; call return an error that is NOT &lt;code&gt;EAGAIN&lt;/code&gt; since the code
will retry if that is the error.&lt;/p&gt;
&lt;p&gt;After looking through the &lt;a href="https://man7.org/linux/man-pages/man2/write.2.html#ERRORS"&gt;list of errors that sys_write can
return&lt;/a&gt;,
&lt;code&gt;EIO&lt;/code&gt; seems like a nice one.&lt;/p&gt;
&lt;p&gt;So let's do our final version of &lt;code&gt;writeHandler&lt;/code&gt; and on the syscall
exit, we'll modify the return value (&lt;code&gt;rax&lt;/code&gt; in amd64/linux) to be
&lt;code&gt;EIO&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;anyerror&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&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="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Truncate some bytes&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Handle syscall exit&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childWaitForSyscall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getABIArguments&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Force the writes to stop after the first one by returning EIO.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EIO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's give it a whirl!&lt;/p&gt;
&lt;h3 id="all-together"&gt;All together&lt;/h3&gt;&lt;p&gt;Build the Zig fault injector and the Go test code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;--library&lt;span class="w"&gt; &lt;/span&gt;c&lt;span class="w"&gt; &lt;/span&gt;main.zig
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;main.go&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;test/main
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And check &lt;code&gt;test.txt&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;test.txt
some&lt;span class="w"&gt; &lt;/span&gt;great&lt;span class="w"&gt; &lt;/span&gt;stu
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hey, that's a short write! :)&lt;/p&gt;
&lt;h3 id="sidenote:-reading-data-from-the-child"&gt;Sidenote: Reading data from the child&lt;/h3&gt;&lt;p&gt;We accomplished everything we set out to, but there's one other useful
thing we can do: reading the actual data passed to the write syscall.&lt;/p&gt;
&lt;p&gt;Just like how we can get the child process registers with
&lt;code&gt;PTRACE_GETREGS&lt;/code&gt;, we can read child memory with
&lt;code&gt;PTRACE_PEEKDATA&lt;/code&gt;. &lt;code&gt;PTRACE_PEEKDATA&lt;/code&gt; takes the child process id and
the memory address in the child to read from. It returns a word of
data (which on amd64/linux is 8 bytes).&lt;/p&gt;
&lt;p&gt;We can use the syscall arguments (data address and length) to keep
calling &lt;code&gt;PTRACE_PEEKDATA&lt;/code&gt; on the child until we've read all bytes of
the data the child process wanted to write:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;childReadData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span 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="n"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&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="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &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;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PTRACE_PEEKDATA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&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;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;cNullPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &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;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&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;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we could modify &lt;code&gt;writeHandler&lt;/code&gt; to print out the entirety of the write message each time (for debugging):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChildManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ABIArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;anyerror&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&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="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Truncate some bytes&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setNth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entryArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childReadData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&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;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Got a write on {}: {s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Handle syscall exit&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childWaitForSyscall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getABIArguments&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Force the writes to stop after the first one by returning EIO.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;c_ulong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EIO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setABIArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;exitArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's pretty neat!&lt;/p&gt;
&lt;h3 id="next-steps"&gt;Next steps&lt;/h3&gt;&lt;p&gt;Short writes are just one of many bad IO interactions. Another fun one
would be to completely buffer all writes on a file descriptor (not
allowing anything to be written to disk at all) until fsync is called
on the file descriptor. Or &lt;a href="https://www.usenix.org/conference/atc20/presentation/rebello"&gt;forcing fsyncs to
fail&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;An interesting optimization would be to apply &lt;a href="https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt"&gt;seccomp
filters&lt;/a&gt;
so that rather than paying a penalty for watching every syscall, I
only get notified about the ones I have hooks for like
&lt;code&gt;sys_write&lt;/code&gt;. &lt;a href="https://www.alfonsobeato.net/c/filter-and-modify-system-calls-with-seccomp-and-ptrace/"&gt;Here's another
post&lt;/a&gt;
that explores ptrace with seccomp filters.&lt;/p&gt;
&lt;p&gt;Credits: Thank you Charlie Cummings and Paul Khuong for reviewing a draft
of this post!&lt;/p&gt;
&lt;h3 id="selected-responses-after-publication"&gt;Selected responses after publication&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;oscooter on Reddit &lt;a href="https://www.reddit.com/r/linux/comments/16x32l3/comment/k380m9q/?utm_source=reddit&amp;amp;utm_medium=web2x&amp;amp;context=3"&gt;gave some
tips&lt;/a&gt;
on using ptrace, including using &lt;code&gt;process_vm_readv&lt;/code&gt; instead of
&lt;code&gt;PTRACE_PEEKDATA&lt;/code&gt; to read memory from the tracee process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Fault injection is a scary-sounding term. Intercepting and modifying Linux system calls sounds scary too.&lt;br /&gt;&lt;br /&gt;But it's a neat way to trigger logical errors in programs, to build confidence we wrote code correctly.&lt;br /&gt;&lt;br /&gt;Let's trigger short writes to disk in Zig!&lt;a href="https://t.co/0C3tWt3vtT"&gt;https://t.co/0C3tWt3vtT&lt;/a&gt; &lt;a href="https://t.co/OS7auDe8jR"&gt;pic.twitter.com/OS7auDe8jR&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1708482934863180004?ref_src=twsrc%5Etfw"&gt;October 1, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sun, 01 Oct 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-10-01-intercepting-and-modifying-linux-system-calls-with-ptrace.html</guid></item><item><title>Wordcount III: Beating the system `wc`</title><link>https://bytepawn.com/beating-the-system-wc.html</link><description>&lt;p&gt;In this follow-up to the previous article about writing a C++ version of the Unix command-line utility &lt;code&gt;wc&lt;/code&gt;, I make some modifications to beat my system &lt;code&gt;wc&lt;/code&gt; in performance tests.&lt;br /&gt;&lt;br /&gt; &lt;img alt="wc" src="/images/wcpp3.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 29 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/beating-the-system-wc.html</guid></item><item><title>Publishing my first game using pico-8</title><link>https://boyter.org/posts/publishing-a-pico8-game/</link><description>&lt;p&gt;TL/DR; &lt;a href="https://boyter.itch.io/wizard-duel"&gt;Play/Download a copy from itch.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I had always wanted to create games. In fact its one of the reasons I went down the development path. For various reasons I never invested enough time (probably too much time playing them) and went into writing code for businesses.&lt;/p&gt;
&lt;p&gt;In hindsight that was probably the right move from a financial perspective, since it seems most game developers tend to be fairly underpaid and the drama in the game development space makes high school teen drama look tame.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 28 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/publishing-a-pico8-game/</guid></item><item><title>Vim tip 33: editing with text objects</title><link>https://learnbyexample.github.io/tips/vim-tip-33/</link><description>&lt;p&gt;Combining motions such as &lt;code&gt;w&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt; and &lt;code&gt;f&lt;/code&gt; with editing commands like &lt;code&gt;d&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; require precise positioning to be effective.&lt;/p&gt;
&lt;p&gt;Vim also provides a list of handy context based options to make certain editing use cases easier using the &lt;code&gt;i&lt;/code&gt; and &lt;code&gt;a&lt;/code&gt; text object selections. You can easily remember the difference between these two options by thinking &lt;code&gt;i&lt;/code&gt; as &lt;strong&gt;inner&lt;/strong&gt; and &lt;code&gt;a&lt;/code&gt; as &lt;strong&gt;around&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;diw&lt;/kbd&gt; delete a word regardless of where the cursor is on that word
&lt;ul&gt;
&lt;li&gt;equivalent to using &lt;kbd&gt;de&lt;/kbd&gt; when the cursor is on the first character of the word&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;diW&lt;/kbd&gt; delete a WORD regardless of where the cursor is on that WORD&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;daw&lt;/kbd&gt; delete a word regardless of where the cursor is on that word as well as a space character to the left/right of the word depending on its position in the current sentence&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;dis&lt;/kbd&gt; delete a sentence regardless of where the cursor is on that sentence&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;yas&lt;/kbd&gt; copy a sentence regardless of where the cursor is on that sentence as well as a space character to the left/right&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;cip&lt;/kbd&gt; delete a paragraph regardless of where the cursor is on that paragraph and change to Insert mode&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;dit&lt;/kbd&gt; delete all characters within HTML/XML tags, nesting is taken care as well
&lt;ul&gt;
&lt;li&gt;see &lt;a href="https://vimhelp.org/motion.txt.html#tag-blocks"&gt;:h tag-blocks&lt;/a&gt; for details about corner cases&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;di&amp;quot;&lt;/kbd&gt; delete all characters within a pair of double quotes, regardless of where the cursor is within the quotes&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;da'&lt;/kbd&gt; delete all characters within a pair of single quotes along with the quote characters&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;ci(&lt;/kbd&gt; delete all characters within &lt;code&gt;()&lt;/code&gt; and change to Insert mode
&lt;ul&gt;
&lt;li&gt;works even if the parenthesis are spread over multiple lines, nesting is taken care as well&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;ya}&lt;/kbd&gt; copy all characters within &lt;code&gt;{}&lt;/code&gt; including the &lt;code&gt;{}&lt;/code&gt; characters
&lt;ul&gt;
&lt;li&gt;works even if the braces are spread over multiple lines, nesting is taken care as well&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; You can use a count prefix for nested cases. For example, &lt;kbd&gt;c2i{&lt;/kbd&gt; will clear the inner braces (including the braces, and this could be nested too) and then only the text between braces for the next level.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://vimhelp.org/motion.txt.html#text-objects"&gt;:h text-objects&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 25 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-33/</guid></item><item><title>Old-School CGI Scripts!</title><link>https://blog.steve.fi/old_school_cgi_scripts_.html</link><description>&lt;p&gt;I'm not sure if I've talked about my job here, but I recently celebrated my one year anniversary - whilst on a company offsite trip to Sweden.  When I joined the company there were approximately 100 people employed by it.  Nowadays the numbers are much higher.&lt;/p&gt;

&lt;p&gt;Having more people around is pretty awesome, but I realized that there were a lot of people wandering around the office who I didn't recognize so it occurred to me to make a game of it.&lt;/p&gt;

&lt;p&gt;I had the idea I could write a slack bot to quiz me on my colleagues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Show a random face, using the Slack profile picture.&lt;/li&gt;
&lt;li&gt;Give a list of 5 names.&lt;/li&gt;
&lt;li&gt;Ask me which was correct.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I spent an hour messing around with various Slack APIs, and decided the whole thing was too much of a hassle.   Instead I wrote a simple script to download the details of all members of the workspace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name.&lt;/li&gt;
&lt;li&gt;Email address.&lt;/li&gt;
&lt;li&gt;Profile picture URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then using that data, &lt;code&gt;users.json&lt;/code&gt;, I hacked up a simple web application in Python, using the flask API.  There only needed to be two pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A page ("/") to show five random images, each with five random names beneath them.&lt;/li&gt;
&lt;li&gt;A page ("/quiz") to receive the HTTP POST, and score.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All in all this took only two hours or so.  Old-school CGI is pretty awesome like that - Hidden values meant the whole thing could be stateless:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; &amp;lt;input type="hidden" name="1answer" value="Bob Smith" ..
 &amp;lt;input type="hidden" name="1profile" value="Sales" ..
 &amp;lt;input type="hidden" name="1url" value="https://.." ..

 &amp;lt;input type="hidden" name="2answer" value="Sally Smith" ..
 &amp;lt;input type="hidden" name="2profile" value="Sales" ..
 &amp;lt;input type="hidden" name="2url" value="https://.." ..
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The only downside is that I don't have any authentication, so there is no ability to have a leaderboard.  I've looked at the Okta samples and I think it would be easy to add, but I guess that would make it more complex and less portable.  That said I'm not sharing the code this time, so who cares if it is tied to the company?&lt;/p&gt;

&lt;p&gt;Anyway sometimes I forget how fast and easy it is to spinup a random virtual machine and present a HTTP(S) service for interactive use.  This is one of those times when I remembered.&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Sun, 24 Sep 2023 22:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/old_school_cgi_scripts_.html</guid></item><item><title>How I would've designed Overwatch 2</title><link>http://pxtl.ca/2023/09/24/overwatch2-4v4/</link><description>&lt;p&gt;Okay I’ve been playing the Overwatch 2 anniversary events and considering how
many of them are small-group team-vs-team things (Catch the Patchmari,
Starwatch). I slept on these the first time through and playing them now I’m
surprised at how much I’m enjoying them, particularly StarWatch despite some of
its dumber ideas (like the AI-controlled Doomfist). This has cemented my
previous opinion:&lt;/p&gt;

&lt;p&gt;OW2 should’ve been 4v4 (2 DPS, 1 tank, 1 healer) with some kind of
fast-respawning mechanic for the healer, and a standard double-team mode (8v8)
either in quickplay or the arcade or something&lt;/p&gt;

&lt;p&gt;Then the number of heroes and the popularity of the roles reflects the number in
play - there are twice as many DPS heroes to choose from, twice as many DPS
players, and twice as many on the team.&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Sun, 24 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/09/24/overwatch2-4v4/</guid></item><item><title>Quantified Kilimanjaro (or Procrastination)</title><link>/hiking/2023/09/quantified-kilimanjaro.html</link><description>&lt;p&gt;I climbed Kilimanjaro via the Rongai route in October of 2012, capturing runkeeper, and other metrics all along the way. I’ve also been meaning to write this post since then and it’s taken over 10 years to finally overcome procrastination and write up how I logged my data up to and down from the summit.&lt;/p&gt;

&lt;h2 id="planning---what-route-when-and-what-coordinator"&gt;Planning - what route, when, and what coordinator&lt;/h2&gt;

&lt;p&gt;I think I first wanted to climb Kilimanjaro when I heard about it in Toto’s Africa song. It seemed pretty cool as the tallest single standing mountain in Africa and just mysterious as this giant mountain sticking up out of the wilds. And then as I aged, it seemed part of culture with &lt;a href="https://en.wikipedia.org/wiki/The_Snows_of_Kilimanjaro_(short_story)"&gt;Hemingway’s Snows of Kilimanjaro&lt;/a&gt; and just every time I heard about it, it seemed like an amazing thing to do, like visit &lt;a href="https://en.wikipedia.org/wiki/Shangri-La"&gt;Shangri-La&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I love hiking, but am pretty weak and not willing to train for real mountain climbing. I learned tht Kilimanjaro is over 19,000 feet but doesn’t require any technical training or special equipment so it seemed like something that might be achievable. And one night, while hiking the Inca Trail with my uncle John and friend Calros, we started talking about “wouldn’t it be cool to do this with Kilimanjaro.” That was in 2006, and in 2012, John and I went on another trip and summited Kilimanjaro. So it’s definitely doable for a tourist.&lt;/p&gt;

&lt;p&gt;Researching Kilimanjaro, I read quite a bit. I relied mostly on some guidebooks, mostly &lt;a href="https://www.amazon.com/gp/product/1852844132/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;amp;psc=1"&gt;Kilimanjaro: A Trekker’s Guide by Alex Steward (2004, Ciccerone)&lt;/a&gt; and &lt;a href="https://www.amazon.com/Kilimanjaro-All-one-Kilimanjaro-Trailblazer/dp/1905864957"&gt;Kilimanjaro - The Trekking Guide to Africa’s Highest Mountain by Henry Stedman&lt;/a&gt;, and I also remember reading &lt;a href="https://www.amazon.com/gp/product/1566567815/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;amp;psc=1"&gt;Michael Moushabeck’s Kilimanjaro: A Photographic Journey to the Roof of Africa&lt;/a&gt; that wasn’t very useful but had many nice photos. I think I like books over web sites because of the editing and density of information vs filtering through tons of web pages.&lt;/p&gt;

&lt;h3 id="route"&gt;Route&lt;/h3&gt;

&lt;p&gt;Kilimanjaro is really popular so there are many &lt;a href="https://en.wikipedia.org/wiki/Mount_Kilimanjaro_climbing_routes"&gt;different routes&lt;/a&gt; to take:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Rongai - less busy, and has more time at each altitude for acclimatization - ended up selecting this one for 7 days because of acclimatization, this is usually 6 days but we added an extra day to acclimatize with our tour operator&lt;/li&gt;
  &lt;li&gt;Marangu - most popular, sleeping in huts, steeper climb - ended up not picking this because it had a lower success rate and seemed like it would be more crowded&lt;/li&gt;
  &lt;li&gt;Macheme - second most popular, I think people call this the “whiskey” route as it’s supposed to be hard&lt;/li&gt;
  &lt;li&gt;Lemosho - considered this because it’s also longest at 8 days, but ended up not taking it because the extra day didn’t work out with my work’s vacation schedule, and it had some driving and drop off rather than all hiking&lt;/li&gt;
  &lt;li&gt;Umbwe - shortest and quickest route&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s a new route now called “Northern Circuit” that I don’t remember that is 9 days so I didn’t consider that in our planning.&lt;/p&gt;

&lt;p&gt;I think we picked Rongai because it was kind of in a goldilocks zone of not being too long and not too short. My biggest concern was acclimatization because I read that’s the biggest reason for failure so this seemed like a good route to gain altitude slowly.&lt;/p&gt;

&lt;p&gt;Joseph Mosha, my guide, handed me a copy of this hand drawn map and it was like my bible during the trek. I love it so much and have it framed on my wall.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Route map drawn by my guide Joseph Mosha" src="/assets/images/kilimanjaro_rongai_route.jpg" /&gt;&lt;/p&gt;

&lt;h3 id="coordinator"&gt;Coordinator&lt;/h3&gt;

&lt;p&gt;It’s possible to climb Kilimanjaro on your own. While I wanted to be cost conscious, I still had a pretty open window and budget of when to go, so I wanted to find a tour operator. The trip is offered by lots of travel groups but I think most resell programs or have remote people scheduling local operators.&lt;/p&gt;

&lt;p&gt;I wanted to make sure the operator was locally based to both support Tanzania people as well as to make sure porters and guides were treated ethically and paid. I wanted to use the &lt;a href="https://www.tailormadeafrica.com/mount-kilimanjaro/the-african-walking-company/"&gt;African Walking Company&lt;/a&gt; but didn’t find a way to book directly through them so ended up booking through &lt;a href="https://peakplanet.com/"&gt;PeakPlanet&lt;/a&gt; to get to them. Back in 2012, I paid $2450 for the 7 day rongai. This was the price to add an extra day to their advertised 6-day trip. It looks like the now offer an &lt;a href="https://peakplanet.com/kilimanjaro-group-dates-prices/"&gt;9-day rongai for about $3k&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is for a small group trip where I think there was a max size of 8 people or something and they guaranteed the departure dates. There were cheaper operators if you had more flexible dates or could handle cancellations.&lt;/p&gt;

&lt;p&gt;I also made sure this included airport transfers and shoulder lodging and wanted to pay a single price to handle all the “incidentals.” So this price included pickup from Arusha (JRO) airport, transport to Moshi and staying in their lodge before and after the trek.&lt;/p&gt;

&lt;p&gt;This ended up being really important because there were only three people on our trek and other operators would have delayed or cancelled. PeakPlanet/AWS said that they would definitely depart and return on our selected dates and they stuck to that.&lt;/p&gt;

&lt;p&gt;Using local staff was really important to us because I thought that Tanzanians would be more knowledgeable and all guides and porters must be local, so if non-Tanzanians are involved those are extra people beyond the actual people carrying out the trek. And PeakPlanet/AWS was really great in that we only met with and worked with Tanzanians and the two guides we had - Joseph Mosha and Elija Joel - were very experienced (dozens of summits), knowledgeable about climbing and mountain sickness, and very friendly and caring. I think Elija is the single most important reason I was able to complete and without his help, I don’t think I would make it.&lt;/p&gt;

&lt;p&gt;PeakPlanet did have a lot of guidance on equipment, training, and general information that made preparing for the trip easier.&lt;/p&gt;

&lt;p&gt;Here’s the route map they shared for Rongai…&lt;/p&gt;

&lt;p&gt;&lt;img alt="PeakPlanet Rongai Route" src="/assets/images/kilimanjaro_rongai_from_peak_planet.png" /&gt;&lt;/p&gt;

&lt;h3 id="when-to-go"&gt;When to go&lt;/h3&gt;

&lt;p&gt;I wanted to visit Kilimanjaro before the snows were gone and but wanted to find the best weather possible. I ended up picking late September 28th through October 6th to hit the end of the rainy season and also near a full moon on summit. Weather ended up being great and we didn’t have any rain at all. And I saw almost no snow as the trail was dry even up to the summit.&lt;/p&gt;

&lt;h3 id="equipment"&gt;Equipment&lt;/h3&gt;

&lt;p&gt;The Stedman book was super helpful for all equipment purchases. I think I booked my trip and flight in February, so had about six months to shop for deals on equipment.&lt;/p&gt;

&lt;p&gt;My basic plan was to have multiple layers, travel light, and have waterproof stuff just in case. Porters carried my bad so I had a 30 pound limit on that and just a day bag that I carried.&lt;/p&gt;

&lt;p&gt;I rented my sleeping bag and tent from the operator, but ended up bringing the following gear:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Zamberlan boots - this is probably the most important item and I spent the most time. I considered Saloman, Asolo, Lowa, and every boot sold in Atlanta and online. I went with Zamberlan because of the fit, reputation, and features. I went with the &lt;a href="https://www.zamberlanusa.com/product/9789427/996-vioz-gtx-r-men-s-hiking-backpacking-boots-dark-grey"&gt;996 Vioz GTX&lt;/a&gt; as they were waterproof with goretex, leather, vibram soles, and a toe box that fit well with me. The waterproof ended up being unnecessary as I never experience snow, but they have been really handy since then going through streams and puddles and never worrying. I still wear them on week-end hikes and they are almost done. I’d guess I have over 1,000 miles over 12 years and have definitely got my money’s worth. I think I spent $300. Also, I have the original laces and they’ve held up really well. I spent about six months breaking them in on local trails and hikes.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.icebreaker.com/en-us/tshirts"&gt;Icebreaker merino wool t-shirts&lt;/a&gt; x 2 - these were amazing for both warmth as well as durability. I alternated them each day and they were still not funky by the end of the trip. They are itchy the first day but afterwards very comfortable. I still wear them 11 years later.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.icebreaker.com/en-us/mens-baselayers/merino-260-tech-long-sleeve-half-zip-thermal-top/104372U2.html?dwvar_104372U2_color=865&amp;amp;dwvar_104372U2_US=in_line"&gt;Icebreaker 260 thermal top&lt;/a&gt; and &lt;a href="https://www.icebreaker.com/en-us/mens-baselayers/merino-260-tech-thermal-leggings-with-fly/104374U2.html?dwvar_104374U2_color=001&amp;amp;dwvar_104374U2_US=in_line"&gt;leggings&lt;/a&gt;. My top has a hood though that fits snugly with just the face exposed. Icebreaker didn’t have as many options back then and I was able to get them at REI on sale. I wore these on the last few days when it got cold and they were really helpful.&lt;/li&gt;
  &lt;li&gt;Smartwool socks (x3) and underwear (x3). More merino and I alternated these each day. I think my base layer was all merino and it worked out pretty well.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.patagonia.com/product/mens-ultralight-down-jacket/84757.html"&gt;Patagonia Ultralight Down Hoody&lt;/a&gt; - this was very light at 900 grams and packed into its own little bag. I wore it at camp at night and on the summit when I pretty much wore everything&lt;/li&gt;
  &lt;li&gt;1-2 fleece mid layers that I don’t remember them. Comically I think they were lululemon or REI.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://arcteryx.com/us/en/shop/venta-ar-glove"&gt;Arc’teryx Zenta AR Glove&lt;/a&gt; - these looked cool and also lasted, but my fingers were so cold on summit, even wearing inner wool liner gloves. I would buy something else instead.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://leaf.arcteryx.com/us/en/shop/mens/alpha-lt-jacket-gen-2"&gt;Arc’teryx Alpha LT Hard Shell&lt;/a&gt; - I think this was the most expensive item I bought, but I liked it. It was extremely light so I carried it in my day bag even from the beginning. It provided no warmth but completely stopped wind when worn over my layers and had vents under the arms I opened up to help regulate heat. I still have it and wear it frequently so I think it’s a decent buy. I think I bought an XL and wish I had bought an XXL because I end up wearing it over everything else. I also bought a soft shell and ended up not wearing it because I just put this over my layers.&lt;/li&gt;
  &lt;li&gt;Camelbak Tycoon - 3 liter water capacity with 20 liters dry storage. This had insulation for the water bladder and drinking tube. Camelbak doesn’t make this any more but it’s great and I still use it for all my hikes. Camelbak support has been amazing too, the clip across my chest that holds the straps in place broke and camelbak sent me a new one free.&lt;/li&gt;
  &lt;li&gt;I brought waterproof outer pants that I don’t remember the brand. I never had to use them.&lt;/li&gt;
  &lt;li&gt;Nomad GoalZero solar kit and battery - this was really useful for charging my iphone every day. I was able to log every day’s hike and this solar kit for $100 from REI worked out well enough that I just hung the panels on my backpack each day, and charged my phone at night. I had an old iphone with gps.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://runkeeper.com"&gt;RunKeeper&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Some gps altitude app that shows me altitude based on GPS but didn’t need signal. I can’t remember the name, but used it and screenshot each day. I can’t find the screenshots, but recorded the altitudes into my diary.&lt;/li&gt;
  &lt;li&gt;Moleskine Squared Reporter Hardcover (that the don’t make any more).&lt;/li&gt;
  &lt;li&gt;Some astronaut pen that wrote in subzero temperatures, upside down, and under pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="medications-and-vaccinations"&gt;Medications and Vaccinations&lt;/h3&gt;

&lt;p&gt;People debate whether to take Diamox or not. I read about the side effects and they were minimal so I went to a travel clinic for my vaccinations and had them give me a prescription for Diamox. I tested it out for a few days to see if I had any negative effects, and didn’t. So I took it throughout my trip. It did make me urinate a bit more than usual, but that’s not so bad as you want to drink lots of water.&lt;/p&gt;

&lt;p&gt;I also took Malarone to protect against Malaria and took that before leaving the US and after returning. Got the same prescription from my travel clinic. I’ve taken it a few times for other trips since then and haven’t had any negative effects.&lt;/p&gt;

&lt;p&gt;I had a prescription for xanax and took that some nights to help sleep through the screaming animals (monkeys maybe) and cold. Some people advise against this, but I think it was a life saver as it can be hard to sleep when it’s 0 degrees and you have altitude adjustment and are on the side of a mountain.&lt;/p&gt;

&lt;p&gt;My travel clinic gave me the following vaccinations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Typhoid&lt;/li&gt;
  &lt;li&gt;Hepatitis B&lt;/li&gt;
  &lt;li&gt;Polio&lt;/li&gt;
  &lt;li&gt;Meningococcal meningitis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I already had Hepatitis A and Yellow Fever from a previous trip and they were still good. I also had DPT and MMR.&lt;/p&gt;

&lt;p&gt;CDC recommends Rabies but I didn’t get it as it’s expensive, has multiple courses, and it’s extremely rare to encounter wildlife on Kilimanjaro. If I had gone on a safari, I would have considered it more.&lt;/p&gt;

&lt;h2 id="daily-log"&gt;Daily log&lt;/h2&gt;

&lt;p&gt;I was really into quantified self back in 2011 and 2012 so I wanted to make sure to keep a log each day. I used a fitbit to track my exercise and food and &lt;a href="https://youtu.be/GS1hINtdJKg"&gt;lost 120+ pounds&lt;/a&gt;. And I used RunKeeper to log each hike throughout the day. And kept a paper record in my notebook.&lt;/p&gt;

&lt;p&gt;I didn’t find any detailed logs of the actual hikes, so I thought I would capture it all here in case people wanted to see specific routes, distances, times, altitude, etc. Of course, it’s 11 years later, but then the best time to plant a tree is 20 years ago and the second best is today.&lt;/p&gt;

&lt;p&gt;Runkeeper doesn’t really have a nice way to share or embed these hikes any more, so I’m just screenshotting each one. I’ve used runkeeper forever and it’s nice they keep the data, but I wish they had better exports.&lt;/p&gt;

&lt;p&gt;I flew ATL-&amp;gt;JFK-&amp;gt;AMS-&amp;gt;JRO and arrived on 9/27 and stayed in Kilimanjaro Mountain Resort in Marangu.&lt;/p&gt;

&lt;h3 id="september-28-friday"&gt;September 28, Friday&lt;/h3&gt;

&lt;p&gt;9/28 - stayed in hotel in Marangu with a trip into Moshi for shopping. Prep day to get over jet lag. $80 for the taxi from hotel to Moshi and back. Paid for everything in the trip in US dollars.&lt;/p&gt;

&lt;p&gt;Comically, this is the only day I got a photo of Kilimanjaro looming in the distance. You could see it through the trees from the hotel. I spent the entire trek with Kilimanjaro slowing getting closer and then farther away. I must have looked at it 2,000 times. But never too a picture. Each time looking at it, I was tired and looking at where I was going and never took a photo.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Kilimanjaro trough the trees from Marangu" src="/assets/images/kilimanjaro_in_the_distance.jpg" /&gt;&lt;/p&gt;

&lt;h3 id="september-29-day1-saturday"&gt;September 29 (Day1), Saturday&lt;/h3&gt;

&lt;p&gt;5.54 miles, 641 meters gained. Camped at 2,641 meters.&lt;/p&gt;

&lt;p&gt;Started at 1035 at the base of Rongai route. Made camp at 3pm. Started at 2,000 meters and set camp at 2,641. After camp, took a 45 minute walk up and back for acclimatization.&lt;/p&gt;

&lt;p&gt;4 specific segments for today’s hikes.&lt;/p&gt;

&lt;h4 id="day1-hike1-200-miles-1-hour-14-minutes-finished-at-1215pm"&gt;&lt;a href="http://rnkpr.com/a219aaf"&gt;Day1-Hike1&lt;/a&gt;, 2.00 miles, 1 hour, 14 minutes, finished at 12:15pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day1-Hike1, 2.00 miles, 1 hour, 14 minutes, finished at 12:15pm" src="/assets/images/kilimanjaro_rk_day1_hike1-2023-09-29_1101.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike1-2023-09-29_1101.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike1-2023-09-29_1101.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day1-hike2-126-miles-4430-finished-at-154pm"&gt;&lt;a href="http://rnkpr.com/a219aak"&gt;Day1-Hike2&lt;/a&gt;, 1.26 miles, 44:30, finished at 1:54pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day1-Hike2, 1.26 miles, 44:30, finished at 1:54pm" src="/assets/images/kilimanjaro_rk_day1_hike2-2023-09-29_1310.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike2-2023-09-29_1310.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike2-2023-09-29_1310.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day1-hike3-106-miles-4342-finished-at-303pm"&gt;&lt;a href="http://rnkpr.com/a219aaq"&gt;Day1-Hike3&lt;/a&gt;, 1.06 miles, 43:42, finished at 3:03pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day1-Hike3, 1.06 miles, 43:42, finished at 3:05pm" src="/assets/images/kilimanjaro_rk_day1_hike3-2023-09-29_1421.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike3-2023-09-29_1421.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike3-2023-09-29_1421.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day1-hike4-122-miles-4602-finished-at-418pm"&gt;&lt;a href="http://rnkpr.com/a219ab7"&gt;Day1-Hike4&lt;/a&gt;, 1.22 miles, 46:02, finished at 4:18pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day1-Hike4, 1.22 miles, 46:02, finished at 4:18pm" src="/assets/images/kilimanjaro_rk_day1_hike4-2023-09-29_1532.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike4-2023-09-29_1532.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day1_hike4-2023-09-29_1532.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="september-30-day2-sunday"&gt;September 30 (Day2), Sunday&lt;/h3&gt;

&lt;p&gt;6.92 miles, 1018 meters gained. Camped at 3,659 meters.&lt;/p&gt;

&lt;p&gt;4 segments today as well.&lt;/p&gt;

&lt;h4 id="day2-hike1-173-miles-10529-finished-942am"&gt;&lt;a href="http://rnkpr.com/a219abk"&gt;Day2-Hike1&lt;/a&gt;, 1.73 miles, 1:05:29, finished 9:42am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day2-Hike1, 1.73 miles, 1:05:29, finished 9:42am" src="/assets/images/kilimanjaro_rk_day2_hike1-2023-09-30_0837.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike1-2023-09-30_0837.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike1-2023-09-30_0837.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day2-hike2-138-miles-14346-finished-1124am"&gt;&lt;a href="http://rnkpr.com/a219ad1"&gt;Day2-Hike2&lt;/a&gt;, 1.38 miles, 1:43:46, finished 11:24am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day2-Hike2, 1.38 miles, 1:43:46, finished 11:24am" src="/assets/images/kilimanjaro_rk_day2_hike2-2023-09-30_0951.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike2-2023-09-30_0951.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike2-2023-09-30_0951.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day2-hike3-225-miles-13746-finished-257pm"&gt;&lt;a href="http://rnkpr.com/a219ad9"&gt;Day2-Hike3&lt;/a&gt;, 2.25 miles, 1:37:46, finished 2:57pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day2-Hike3, 2.25 miles, 1:37:46, finished 2:57pm" src="/assets/images/kilimanjaro_rk_day2_hike3-2023-09-30_1320.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike3-2023-09-30_1320.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike3-2023-09-30_1320.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day2-hike4-156-miles-12100-finished-437pm"&gt;&lt;a href="http://rnkpr.com/a219ade"&gt;Day2-Hike4&lt;/a&gt;, 1.56 miles, 1:21:00, finished 4:37pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day2-Hike3, 1.56 miles, 1:21:00, finished 4:37pm" src="/assets/images/kilimanjaro_rk_day2_hike4-2023-09-30_1516.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike4-2023-09-30_1516.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day2_hike4-2023-09-30_1516.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="october-1-day3-monday"&gt;October 1 (Day3), Monday&lt;/h3&gt;

&lt;p&gt;2.20 miles, 677 meters gained. Camped at 4,336 meters.&lt;/p&gt;

&lt;p&gt;Just a short distance today, but a lot of altitude gained. Camped at Mawenzi Tarn at the base of &lt;a href="https://www.tranquilkilimanjaro.com/places/mawenzi-peak-mount-kilimanjaro/"&gt;Mawenzi Peak&lt;/a&gt;. This is the first day I really started to notice &lt;a href="https://medlineplus.gov/ency/article/000133.htm"&gt;Acute Mountain Sickness (AMS)&lt;/a&gt;. I had “thick thinking” where it would just take a long time to think through things, it took me 3 minutes to put batteries in my headlamp and that would normally take 30 seconds.&lt;/p&gt;

&lt;h4 id="day3-hike1-220-miles-25017-finished-1152am"&gt;&lt;a href="http://rnkpr.com/a219adq"&gt;Day3-Hike1&lt;/a&gt;, 2.20 miles, 2:50:17, finished 11:52am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day3-Hike1, 2.20 miles, 2:50:17, finished 11:52am" src="/assets/images/kilimanjaro_rk_day3_hike1-2023-10-01_0902.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day3_hike1-2023-10-01_0902.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day3_hike1-2023-10-01_0902.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="october-2-day4-tuesday"&gt;October 2 (Day4), Tuesday&lt;/h3&gt;

&lt;p&gt;1.3 miles, 164 meters gained. Camped at 4,500 meters.&lt;/p&gt;

&lt;p&gt;Stayed at Mawenzi Tarn again and spent the day just hanging out and acclimatizing. This is the only day I touched snow as I went hiked around the base to some standing snow.&lt;/p&gt;

&lt;p&gt;We were above the clouds and it was really beautiful just relaxing after a few days and using lots of sunblock. Also spent time with clouds rolling over us and it was really neat to be inside the cloud as it passed over.&lt;/p&gt;

&lt;p&gt;&lt;img alt="lots of fun playing around above the clouds" src="/assets/images/kilimanjaro_mawenzi_having_fun.jpg" /&gt;&lt;/p&gt;

&lt;h4 id="day4-hike1-131-miles-13939-finished-1045am"&gt;&lt;a href="http://rnkpr.com/a219ae6"&gt;Day4-Hike1&lt;/a&gt;, 1.31 miles, 1:39:39, finished 10:45am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day4-Hike1, 1.31 miles, 1:39:39, finished 10:45am" src="/assets/images/kilimanjaro_rk_day4_hike1-2023-10-02_0906.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day4_hike1-2023-10-02_0906.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day4_hike1-2023-10-02_0906.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="october-3-day5-wednesday"&gt;October 3 (Day5), Wednesday&lt;/h3&gt;

&lt;p&gt;5.32 miles, 215 meters gained. Camped at 4,715 meters.&lt;/p&gt;

&lt;p&gt;Arrived at Kibo Hut camp “Base camp”. So it took about 21 miles to get here. We arrived pretty early at 1pm. We napped until dinner at 5, then sleep until 11, eat a light meal and start the summit. The it should have taken 6 hours to Gillman’s point, then 2 hours to Uhuru, then 2-4 hours back down. We didn’t actually sleep in the hut, we stayed in our standard tents.&lt;/p&gt;

&lt;h4 id="day5-hike1-532-miles-33054-finished-1116am"&gt;&lt;a href="http://rnkpr.com/a219agk"&gt;Day5-Hike1&lt;/a&gt;, 5.32 miles, 3:30:54, finished 11:16am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day5-Hike1, 5.32 miles, 3:30:54, finished 11:16am" src="/assets/images/kilimanjaro_rk_day5_hike1-2023-10-03_0746.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day5_hike1-2023-10-03_0746.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day5_hike1-2023-10-03_0746.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="october-4-day6-thursday"&gt;October 4 (Day6), Thursday&lt;/h3&gt;

&lt;p&gt;So technically, I started summit at 11:56pm but include it as day 6.&lt;/p&gt;

&lt;p&gt;3 segments today, but only the first with runkeeper.&lt;/p&gt;

&lt;p&gt;3.50 miles, 1195 meters gained to summit.&lt;/p&gt;

&lt;p&gt;Phone battery died when I pulled it out at summit to end runkeeper and take a picture. Luckily I had it inside my layers and it tracked everything from Kibo hut to Uhuru.&lt;/p&gt;

&lt;p&gt;So what’s interesting is my diary has the max altitude as 5910 meters even though the actual hight is &lt;a href="https://en.wikipedia.org/wiki/Mount_Kilimanjaro"&gt;5895 meters&lt;/a&gt;. So this is wrong, but I kept it in my records as I measured altitudes consistently with the same app so kept this as the altitude for peak assuming that my app would measure wrong, consistently so at least this lets me calculate the altitude gained or lost.&lt;/p&gt;

&lt;p&gt;3.50 miles back down from summit and it took 2 hours. Then napped for an hour and got up to walk about six miles to camp at Horombu Hut, 3,720 meters, so 995 meters lost from last camp.&lt;/p&gt;

&lt;h4 id="day6-hike1-350-miles-71658-finished-712am"&gt;&lt;a href="http://rnkpr.com/a219agy"&gt;Day6-Hike1&lt;/a&gt;, 3.50 miles, 7:16:58, finished 7:12am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day6-Hike1, 3.50 miles, 7:16:58, finished 7:12am" src="/assets/images/kilimanjaro_rk_day6_hike1-2023-10-03_2356.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day6_hike1-2023-10-03_2356.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day6_hike1-2023-10-03_2356.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day6-hike2-350-miles-200-finished-916am"&gt;Day6-Hike2, 3.50 miles, ~2:00, finished 9:16am&lt;/h4&gt;

&lt;p&gt;Not sure exactly how long this took, my diary says 2 hours.&lt;/p&gt;

&lt;h4 id="day6-hike3-6-miles--finished-405pm"&gt;Day6-Hike3, ~6 miles, ???, finished ~4:05pm&lt;/h4&gt;

&lt;p&gt;Not sure how long this took, but my diary entry was at 4:05 so I guess I finished a little before then.&lt;/p&gt;

&lt;h3 id="october-5-day7-friday"&gt;October 5 (Day7), Friday&lt;/h3&gt;

&lt;p&gt;13.02 miles, 1,830 meters lost. Camped at 1890 in hotel.&lt;/p&gt;

&lt;p&gt;2 segments.&lt;/p&gt;

&lt;h4 id="day7-hike1-774-miles-31135-finished-1127am"&gt;&lt;a href="http://rnkpr.com/a219ah5"&gt;Day7-Hike1&lt;/a&gt;, 7.74 miles, 3:11:35, finished 11:27am&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day7-Hike1, 7.74 miles, 3:11:35, finished 11:27am" src="/assets/images/kilimanjaro_rk_day7_hike1-2023-10-05_0816.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day7_hike1-2023-10-05_0816.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day7_hike1-2023-10-05_0816.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="day7-hike2-528-miles-21543-finished-229pm"&gt;&lt;a href="http://rnkpr.com/a219ahl"&gt;Day7-Hike2&lt;/a&gt;, 5.28 miles, 2:15:43, finished 2:29pm&lt;/h4&gt;

&lt;p&gt;&lt;img alt="Day7-Hike2, 5.28 miles, 2:15:43, finished 2:29pm" src="/assets/images/kilimanjaro_rk_day7_hike2-2023-10-05_1204.png" /&gt;
| &lt;a href="/assets/geo/kilimanjaro_rk_day7_hike2-2023-10-05_1204.gpx"&gt;gpx&lt;/a&gt; | &lt;a href="/assets/geo/kilimanjaro_rk_day7_hike2-2023-10-05_1204.kml"&gt;kml&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="summary-table"&gt;Summary Table&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Date&lt;/th&gt;
      &lt;th&gt;Start&lt;/th&gt;
      &lt;th&gt;End&lt;/th&gt;
      &lt;th&gt;Time Walking (hours:minutes)&lt;/th&gt;
      &lt;th&gt;Distance (miles)&lt;/th&gt;
      &lt;th&gt;Altitude Gain (meters)&lt;/th&gt;
      &lt;th&gt;End Altitude (meters)&lt;/th&gt;
      &lt;th&gt;Cumulative Distance (miles)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;9/29&lt;/td&gt;
      &lt;td&gt;1101&lt;/td&gt;
      &lt;td&gt;1618&lt;/td&gt;
      &lt;td&gt;3:28&lt;/td&gt;
      &lt;td&gt;5.54&lt;/td&gt;
      &lt;td&gt;641&lt;/td&gt;
      &lt;td&gt;2,641&lt;/td&gt;
      &lt;td&gt;5.54&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;9/30&lt;/td&gt;
      &lt;td&gt;0837&lt;/td&gt;
      &lt;td&gt;1637&lt;/td&gt;
      &lt;td&gt;5:47&lt;/td&gt;
      &lt;td&gt;6.92&lt;/td&gt;
      &lt;td&gt;1,018&lt;/td&gt;
      &lt;td&gt;3,659&lt;/td&gt;
      &lt;td&gt;12.46&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/1&lt;/td&gt;
      &lt;td&gt;0902&lt;/td&gt;
      &lt;td&gt;1152&lt;/td&gt;
      &lt;td&gt;2:50&lt;/td&gt;
      &lt;td&gt;2.20&lt;/td&gt;
      &lt;td&gt;677&lt;/td&gt;
      &lt;td&gt;4,336&lt;/td&gt;
      &lt;td&gt;14.66&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/2&lt;/td&gt;
      &lt;td&gt;0906&lt;/td&gt;
      &lt;td&gt;1018&lt;/td&gt;
      &lt;td&gt;1:39&lt;/td&gt;
      &lt;td&gt;1.31&lt;/td&gt;
      &lt;td&gt;164&lt;/td&gt;
      &lt;td&gt;4,500&lt;/td&gt;
      &lt;td&gt;15.97&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/3&lt;/td&gt;
      &lt;td&gt;0746&lt;/td&gt;
      &lt;td&gt;1126&lt;/td&gt;
      &lt;td&gt;3:31&lt;/td&gt;
      &lt;td&gt;5.32&lt;/td&gt;
      &lt;td&gt;215&lt;/td&gt;
      &lt;td&gt;4,715&lt;/td&gt;
      &lt;td&gt;21.29&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/4 summit&lt;/td&gt;
      &lt;td&gt;2356&lt;/td&gt;
      &lt;td&gt;0712&lt;/td&gt;
      &lt;td&gt;7:16&lt;/td&gt;
      &lt;td&gt;3.50&lt;/td&gt;
      &lt;td&gt;1,195&lt;/td&gt;
      &lt;td&gt;5910&lt;/td&gt;
      &lt;td&gt;24.79&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/4 descent&lt;/td&gt;
      &lt;td&gt;0721&lt;/td&gt;
      &lt;td&gt;1605&lt;/td&gt;
      &lt;td&gt;???&lt;/td&gt;
      &lt;td&gt;9.50&lt;/td&gt;
      &lt;td&gt;-995&lt;/td&gt;
      &lt;td&gt;3720&lt;/td&gt;
      &lt;td&gt;34.29&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10/5&lt;/td&gt;
      &lt;td&gt;0816&lt;/td&gt;
      &lt;td&gt;1429&lt;/td&gt;
      &lt;td&gt;5:27&lt;/td&gt;
      &lt;td&gt;13.02&lt;/td&gt;
      &lt;td&gt;-1,830&lt;/td&gt;
      &lt;td&gt;1,890&lt;/td&gt;
      &lt;td&gt;37.31&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;47.31 miles total, 24.79 miles up, 22.52 down; 6.75 average per day.&lt;/p&gt;

&lt;p&gt;3910 meters of elevation gain; average of 652 across six days climbing.&lt;/p&gt;

&lt;h2 id="advice"&gt;Advice&lt;/h2&gt;

&lt;p&gt;I hope this is helpful. I wanted to know about the specifics of the hikes when I was researching and I wasn’t able to see the kind of pace that people kept.&lt;/p&gt;

&lt;p&gt;What’s interesting to me is that the time actually hiking was rather short each day. Summit day was really long with about 16 hours, but other than that it was usually just a few hours of hiking and the rest of the time hanging around at camp.&lt;/p&gt;

&lt;p&gt;I would bring less equipment next time as I think I probably had 3 or 4 backup layers and fleeces. I just wore the same thing. Maybe two copies of each to alternate them. I definitely wouldn’t bring a soft shell.&lt;/p&gt;

&lt;p&gt;I didn’t bring lightweight but warm camp shoes so I had to wear my boots around camp.&lt;/p&gt;

&lt;p&gt;Altitude was rough so I would try to train more. I live on the east coast and climbed our highest mountains, but that’s only like 5,000 feet. To do it again, I think I’d spend more time in Arusha/Marangu as it’s about 2,000 meters high. And I’d take more time than 7 days. The longer rongai sounds nice or maybe that new northern circuit trek.&lt;/p&gt;

&lt;p&gt;I’m glad that I didn’t skimp on my operator and it was worth everything. The food was nice and the campsite was always great and clean and ready to go when we got there.&lt;/p&gt;

&lt;p&gt;I’d bring more cash to tip. I think I tipped $300 total and that was all the cash I brought. But since our group was smaller than usual with only 3 people, I would have liked to tip more to make up for it.&lt;/p&gt;

&lt;h2 id="procrastination"&gt;Procrastination&lt;/h2&gt;

&lt;p&gt;So I’ve had all this data and all these notes since the trip. I’ve been thinking it over in my head for all these years. I never sat down to write and collect until today. Why did I do that? I’m interested in the trek. I like seeing all the numbers line up. But what’s been pushing this off? I’m not sure. It was hard as I think it took about 8 hours to write everything up and figure out what I wrote in my notes.&lt;/p&gt;

&lt;p&gt;I find it interesting when I know what to do and why, but just don’t do it. I hope to figure out why I do that.&lt;/p&gt;

&lt;h2 id="things-i-read--references"&gt;Things I read / References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/The_Snows_of_Kilimanjaro_(short_story)"&gt;The Snows of Kilimanjaro by Ernest Hemingway&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.amazon.com/gp/product/1852844132/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;amp;psc=1"&gt;Kilimanjaro: A Trekker’s Guide by Alex Steward (2004, Ciccerone)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.amazon.com/gp/product/1566567815/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;amp;psc=1"&gt;Michael Moushabeck’s Kilimanjaro: A Photographic Journey to the Roof of Africa&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.amazon.com/Kilimanjaro-All-one-Kilimanjaro-Trailblazer/dp/1905864957"&gt;Kilimanjaro - The Trekking Guide to Africa’s Highest Mountain by Henry Stedman&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=FTQbiNvZqaY"&gt;Toto Africa&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.tranquilkilimanjaro.com/places/mawenzi-peak-mount-kilimanjaro/"&gt;Mawenzi Peak&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Mount_Kilimanjaro"&gt;Kilimanjaro&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://peakplanet.com/kilimanjaro-group-dates-prices/"&gt;Peak Planet 2023/2024 Trek Schedule&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>PREPEND</author><pubDate>Sat, 23 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">/hiking/2023/09/quantified-kilimanjaro.html</guid></item><item><title>A Disk Usage Mystery</title><link>https://www.marginalia.nu/log/89-disk-usage-mystery/</link><description>I ran into a bit of a puzzling situation yesterday, testing some of the new index construction changes before they&amp;rsquo;re going live in a few days.
The process crashed with a pretty non-descript stack trace complaining about illegal instructions, so first glance it looked more like it was within the realm of freak JVM bug, cosmic ray, hardware error maybe.
I was doing this on my developer workstation, which also spawned a popup complaining that the hard drive it was working on had nearly run out of space, and inferred that the error probably was due to memory mapping more space onto a disk than what was possible.</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 23 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/89-disk-usage-mystery/</guid></item><item><title>Wordcount II: Introducing a cleaner C++ class hierarchy to `wc`</title><link>https://bytepawn.com/making-the-wc-state-machines-generic-cpp.html</link><description>&lt;p&gt;In this follow-up to the previous article about writing a C++ version of the Unix command-line utility &lt;code&gt;wc&lt;/code&gt;, I make the class structure more complicated to keep separate concerns and functionality in different C++ classes. The result ends up being significantly more complex than the original, but does not make the overall program easier to understand or modify.&lt;br /&gt;&lt;br /&gt; &lt;img alt="wc" src="/images/wc2.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 23 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/making-the-wc-state-machines-generic-cpp.html</guid></item><item><title>How do databases execute expressions?</title><link>http://notes.eatonphil.com/2023-09-21-how-do-databases-execute-expressions.html</link><description>&lt;p&gt;Databases are fun. They sit at the confluence of Computer
Science topics that might otherwise not seem practical in life as a
developer. For example, every database with a query language is also a
programming language implementation of some caliber. That doesn't
include all databases though of course; see: RocksDB, FoundationDB,
TigerBeetle, etc.&lt;/p&gt;
&lt;p&gt;This post looks at how various databases execute expressions in their
query language.&lt;/p&gt;
&lt;p&gt;tldr; Most surveyed databases use a tree-walking interpreter. A few
use stack- or register-based virtual machines. A couple have
just-in-time compilers. And, tangentially, a few do vectorized
interpretation.&lt;/p&gt;
&lt;p class="note"&gt;
  Throughout this post I'll use "virtual machine" as a shorthand for
  stack- or register-based loops that process a linearized
  set of instructions. I say this since it is sometimes fair to call a
  tree-walking interpreter a virtual machine. But that is not what I
  mean when I say virtual machine in this post.
&lt;/p&gt;&lt;h3 id="stepping-back"&gt;Stepping back&lt;/h3&gt;&lt;p&gt;Programming languages are typically implemented by turning an
Abstract Syntax Tree (AST) into a linear set of instructions
for a virtual machine (e.g. CPython, Java, C#) or native code
(e.g. GCC's C compiler, Go, Rust). Some of the former implementations
also generate and run Just-In-Time (JIT) compiled native code
(e.g. Java and C#).&lt;/p&gt;
&lt;p&gt;Less commonly these days in programming languages does the
implementation interpret off the AST or some other tree-like
intermediate representation. This style is often called
tree-walking.&lt;/p&gt;
&lt;p&gt;Shell languages sometimes do tree-walking. Otherwise, implementations
that interpret directly off of a tree normally do so as a short-term
measure before switching to compiled virtual machine code or JIT-ed
native code (e.g. some JavaScript implementations, GraalVM, RPython,
etc.)&lt;/p&gt;
&lt;p&gt;That is, while some major programming language implementations started
out with tree-walking interpreters, they mostly moved away from solely
tree-walking over a decade ago. See &lt;a href="https://www.webkit.org/blog/189/announcing-squirrelfish/"&gt;JSC in
2008&lt;/a&gt;, &lt;a href="https://www.infoq.com/news/2007/12/ruby-19/"&gt;Ruby
in 2007&lt;/a&gt;, etc.&lt;/p&gt;
&lt;p&gt;My intuition is that tree-walking takes up more memory and is less
cache-friendly than the linear instructions you give to a virtual
machine or to your CPU. There are &lt;a href="https://stefan-marr.de/downloads/oopsla23-larose-et-al-ast-vs-bytecode-interpreters-in-the-age-of-meta-compilation.pdf"&gt;some folks who
disagree&lt;/a&gt;,
but they mostly talk about tree-walking when you've also got a JIT
compiler hooked up. Which isn't quite the same thing. There has also
been &lt;a href="https://www.cs.cornell.edu/~asampson/blog/flattening.html"&gt;some early exploration and
improvements&lt;/a&gt;
reported when tree-walking with a tree organized as an array.&lt;/p&gt;
&lt;h4 id="and-databases?"&gt;And databases?&lt;/h4&gt;&lt;p&gt;Databases often interpret directly off a tree. (It isn't, generally
speaking, fair to say they are AST-walking interpreters because
databases typically transform and optimize beyond just an AST as
parsed from user code.)&lt;/p&gt;
&lt;p&gt;But not all databases interpret a tree. Some have a virtual
machine. And some generate and run JIT-ed native code.&lt;/p&gt;
&lt;h3 id="methodology"&gt;Methodology&lt;/h3&gt;&lt;p&gt;If a core function (in the query execution path that does something
like arithmetic or comparison) returns a value, that's a sign it's a
tree-walking interpreter. Or, if you see code that is evaluating its
arguments during execution, that's also a sign of a tree-walking
interpreter.&lt;/p&gt;
&lt;p&gt;On the other hand, if the function mutates internal state such as by
assigning a value to a context or pushing to a stack, that's a sign
it's a stack- or register-based virtual machine. If a function pulls
its arguments from memory and doesn't evaluate the arguments, that's
also an indication it's a stack- or register-based virtual machine.&lt;/p&gt;
&lt;p&gt;This approach can result in false-positives depending on the
architecture of the interpreter. User-defined functions (UDFs) would
probably accept evaluated arguments and return a value regardless of
how the interpreter is implemented. So it's important to find not just
functions that could be implemented like UDFs, but core builtin
behavior. Control flow implementations of functions like &lt;code&gt;if&lt;/code&gt; or
&lt;code&gt;case&lt;/code&gt; can be great places to look.&lt;/p&gt;
&lt;p&gt;And tactically, I clone the source code and run stuff like &lt;code&gt;git grep
-i eval | grep -v test | grep \\.java | grep -i eval&lt;/code&gt; or &lt;code&gt;git grep -i
expr | grep -v test | grep \\.go | grep -i expr&lt;/code&gt; until I convince
myself I'm somewhere interesting.&lt;/p&gt;
&lt;p&gt;Note: In talking about a broad swath of projects, maybe I've
misunderstood one or some. If you've got a correction, let me know! If
there's a proprietary database you work on where you can link to the
(publicly described) execution strategy, feel free to pass it along!
Or if I'm missing your public-source database in this list, send me a
message!&lt;/p&gt;
&lt;h3 id="survey"&gt;Survey&lt;/h3&gt;&lt;h4&gt;&lt;a href="https://github.com/cockroachdb/cockroach"&gt;Cockroach&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;Judging by functions like &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/sem/eval/expr.go#L105"&gt;&lt;code&gt;func (e *evaluator)
EvalBinaryExpr&lt;/code&gt;&lt;/a&gt;
that &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/sem/eval/expr.go#L106"&gt;evaluates the left-hand
side&lt;/a&gt;
and then &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/sem/eval/expr.go#L113"&gt;evaluates the right-hand
side&lt;/a&gt;
and returns a value, Cockroach looks like a tree walking interpreter.&lt;/p&gt;
&lt;p&gt;It gets a little more interesting though, since Cockroach also
&lt;a href="https://www.cockroachlabs.com/docs/stable/vectorized-execution"&gt;supports&lt;/a&gt;
vectorized expression execution. Vectorizing is a fancy term for
acting on many pieces of data at once rather than one at a time. It
doesn't necessarily imply SIMD. Here is an example of a &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/colexec/colexecproj/proj_non_const_ops.eg.go#L4427"&gt;vectorized
addition&lt;/a&gt;
of two int64 columns.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/ClickHouse/clickhouse"&gt;ClickHouse&lt;/a&gt; (Ruling: Tree Walker + JIT)&lt;/h4&gt;&lt;p&gt;The ClickHouse architecture is a little unique and difficult for me to
read through – likely due to it being fairly mature, with serious
optimization. But they tend to document their header files well. So
files like
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/853e3f0aa789d5b6dcb251a403276d9fdc02902c/src/Functions/IFunction.h"&gt;src/Functions/IFunction.h&lt;/a&gt;
and
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/9af9b4a08542812694f171833a7afe08f5aaaafb/src/Interpreters/ExpressionActions.h"&gt;src/Interpreters/ExpressionActions.h&lt;/a&gt;
were helpful.&lt;/p&gt;
&lt;p&gt;They have also spoken publicly about their pipeline execution model;
e.g. &lt;a href="https://presentations.clickhouse.com/meetup24/5.%20Clickhouse%20query%20execution%20pipeline%20changes/"&gt;this
presentation&lt;/a&gt;
and &lt;a href="https://github.com/ClickHouse/ClickHouse/issues/34045"&gt;this roadmap
issue&lt;/a&gt;. But it
isn't completely clear how much pipeline execution (which is broader
than just expression evaluation) connects to expression evaluation.&lt;/p&gt;
&lt;p&gt;Moreover, they have &lt;a href="https://clickhouse.com/blog/clickhouse-just-in-time-compiler-jit"&gt;publicly
spoken&lt;/a&gt;
about their support for JIT compilation for query execution. But let's
look at how execution works when the JIT is not enabled. For example,
If we take a look at how &lt;a href="https://github.com/ClickHouse/ClickHouse/blob/853e3f0aa789d5b6dcb251a403276d9fdc02902c/src/Functions/if.cpp"&gt;&lt;code&gt;if&lt;/code&gt; is
implemented&lt;/a&gt;,
we know that the &lt;code&gt;then&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt; rows must be conditionally
evaluated.&lt;/p&gt;
&lt;p&gt;In the runtime entrypoint,
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/853e3f0aa789d5b6dcb251a403276d9fdc02902c/src/Functions/if.cpp#L1048"&gt;&lt;code&gt;executeImpl&lt;/code&gt;&lt;/a&gt;,
we see the function call
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/853e3f0aa789d5b6dcb251a403276d9fdc02902c/src/Functions/if.cpp#L983"&gt;&lt;code&gt;executeShortCircuitArguments&lt;/code&gt;&lt;/a&gt;
which in turn calls
&lt;a href="https://github.com/ClickHouse/ClickHouse/blob/master/src/Columns/ColumnFunction.cpp#L280"&gt;&lt;code&gt;ColumnFunction::reduce()&lt;/code&gt;&lt;/a&gt;
which &lt;a href="https://github.com/ClickHouse/ClickHouse/blob/master/src/Columns/ColumnFunction.cpp#L299"&gt;evaluates each column vector that is an
argument&lt;/a&gt;
to the function and then calls execute on the function.&lt;/p&gt;
&lt;p&gt;So from this we can tell the non-JIT execution is a tree walker and
that it is over &lt;a href="https://twitter.com/ClickHouseDB/status/1705619463888900538"&gt;chunks of
columns&lt;/a&gt;,
i.e. vectorized data, similar to Cockroach. However in ClickHouse
execution is &lt;em&gt;always&lt;/em&gt; over column vectors.&lt;/p&gt;
&lt;p class="note"&gt;
  In the original version of this post, I had some confusion about the
  ClickHouse execution strategy. Robert Schulze from
  ClickHouse &lt;a href="https://clickhousedb.slack.com/archives/CUDSPUJ68/p1695307656700889"&gt;helped
  clarify&lt;/a&gt; things for me. Thanks Robert!
&lt;/p&gt;&lt;h4&gt;&lt;a href="https://github.com/duckdb/duckdb"&gt;DuckDB&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;If we take a look at how &lt;a href="https://github.com/duckdb/duckdb/blob/479c89e154f32012143d741c1a4f4d769f20044e/src/execution/expression_executor/execute_function.cpp#L59"&gt;function expressions are
executed&lt;/a&gt;,
we can see each &lt;a href="https://github.com/duckdb/duckdb/blob/479c89e154f32012143d741c1a4f4d769f20044e/src/execution/expression_executor/execute_function.cpp#L66"&gt;argument in the function being
evaluated&lt;/a&gt;
before being passed to the actual function. So that looks like a tree
walking interpreter.&lt;/p&gt;
&lt;p&gt;Like ClickHouse, DuckDB expression execution is always over column
vectors. You can read more about this architecture
&lt;a href="https://duckdb.org/internals/vector.html"&gt;here&lt;/a&gt; and
&lt;a href="https://www.infoq.com/articles/analytical-data-management-duckdb/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/influxdata/influxdb"&gt;Influx&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;Influx originally had a SQL-like query language called InfluxQL. If we
look at &lt;a href="https://github.com/influxdata/influxdb/blob/b3b982d746fdc34451ca44d262f83b483cd9ea33/storage/reads/influxql_eval.go#L41"&gt;how it evaluates a binary
expression&lt;/a&gt;,
it first evaluates the left-hand side and then the right-hand side
before operating on the sides and returning a value. That's a
tree-walking interpreter.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/influxdata/flux"&gt;Flux&lt;/a&gt; was the new query language
for Influx. While the Flux
&lt;a href="https://github.com/influxdata/flux/blob/master/docs/VirtualMachine.md"&gt;docs&lt;/a&gt;
suggest they transform to an intermediate representation that is
executed on a virtual machine, there's nothing I'm seeing that looks
like a stack- or register-based virtual machine. All the &lt;a href="https://github.com/influxdata/flux/blob/master/interpreter/interpreter.go#L352"&gt;evaluation
functions&lt;/a&gt;
evaluate their arguments and return a value. That looks like a
tree-walking interpreter to me.&lt;/p&gt;
&lt;p&gt;Today Influx
&lt;a href="https://www.influxdata.com/blog/the-plan-for-influxdb-3-0-open-source/"&gt;announced&lt;/a&gt;
that Flux is in maintenance mode and they are focusing on InfluxQL
again.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/MariaDB/server"&gt;MariaDB&lt;/a&gt; / &lt;a href="https://github.com/mysql/mysql-server"&gt;MySQL&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;Control flow methods are normally a good way to see how an interpreter
is implemented. The implementation of COALESCE &lt;a href="https://github.com/MariaDB/server/blob/e9573c059656d9477c2176f102f7e79d0f1ca6b0/sql/item_cmpfunc.cc#L3431"&gt;looks pretty
simple&lt;/a&gt;. We
see it &lt;a href="https://github.com/MariaDB/server/blob/e9573c059656d9477c2176f102f7e79d0f1ca6b0/sql/item_cmpfunc.cc#L3442"&gt;call
&lt;code&gt;val_str()&lt;/code&gt;&lt;/a&gt;
for each argument to COALESCE. But I can only seem to find
implementations of &lt;code&gt;val_str()&lt;/code&gt; on raw values and not
expressions. &lt;code&gt;Item_func_coalesce&lt;/code&gt; itself does not implement
&lt;code&gt;val_str()&lt;/code&gt; for example, which would be a strong indication of a tree
walker. Maybe it does implement &lt;code&gt;val_str()&lt;/code&gt; through inheritance.&lt;/p&gt;
&lt;p&gt;It becomes a little clearer if we look at non-control flow methods
like
&lt;a href="https://github.com/MariaDB/server/blob/e9573c059656d9477c2176f102f7e79d0f1ca6b0/sql/item_func.cc#L2048"&gt;&lt;code&gt;acos&lt;/code&gt;&lt;/a&gt;. In
this method we see &lt;code&gt;Item_func_acos&lt;/code&gt; itself implement &lt;code&gt;val_real()&lt;/code&gt; and
also call &lt;code&gt;val_real()&lt;/code&gt; on all its arguments. In this case it's obvious
how the control flow of &lt;code&gt;acos(acos(.5))&lt;/code&gt; would work. So that seems to
indicate expressions are executed with a tree walking interpreter.&lt;/p&gt;
&lt;p&gt;I also noticed
&lt;a href="https://github.com/MariaDB/server/blob/e9573c059656d9477c2176f102f7e79d0f1ca6b0/sql/sp_instr.cc"&gt;sql/sp_instr.cc&lt;/a&gt;. That
is scary (in terms of invalidating my analysis) since it looks like a
virtual machine. But after looking through it, I think this virtual
machine only corresponds to how stored procedures are executed, hence
the &lt;code&gt;sp_&lt;/code&gt; prefix for Stored Programs. &lt;a href="https://dev.mysql.com/doc/dev/mysql-server/latest/stored_programs.html"&gt;MySQL
docs&lt;/a&gt;
also explain that stored procedures are executed with a bytecode
virtual machine.&lt;/p&gt;
&lt;p&gt;I'm curious why they don't use that virtual machine for query
execution.&lt;/p&gt;
&lt;p&gt;As far as I can tell MySQL and MariaDB do not differ in this regard.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/mongodb/mongo"&gt;MongoDB&lt;/a&gt; (Ruling: Virtual Machine)&lt;/h4&gt;&lt;p&gt;Mongo &lt;a href="https://laplab.me/posts/inside-new-query-engine-of-mongodb/"&gt;recently
introduced&lt;/a&gt;
a virtual machine for executing queries, called Slot Based Execution
(SBE). We can find the SBE code in
&lt;a href="https://github.com/mongodb/mongo/blob/master/src/mongo/db/exec/sbe/vm/vm.cpp#L9313"&gt;src/mongo/db/exec/sbe/vm/vm.cpp&lt;/a&gt;
and the main virtual machine entrypoint
&lt;a href="https://github.com/mongodb/mongo/blob/master/src/mongo/db/exec/sbe/vm/vm.cpp#L9313"&gt;here&lt;/a&gt;. &lt;a href="https://github.com/mongodb/mongo/blob/master/src/mongo/db/exec/sbe/vm/vm.cpp#L9419"&gt;Looks
like&lt;/a&gt;
a classic stack-based virtual machine!&lt;/p&gt;
&lt;p&gt;It isn't completely clear to me if the SBE path is always used or if
there are still cases where it falls back to their old execution
model. You can read more about Mongo execution
&lt;a href="https://github.com/mongodb/mongo/blob/master/src/mongo/db/query/README.md"&gt;here&lt;/a&gt;
and &lt;a href="https://www.mongodb.com/docs/manual/reference/sbe/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;a&gt;PostgreSQL&lt;/a&gt; (Ruling: Virtual Machine + JIT)&lt;/h4&gt;&lt;p&gt;The top of PostgreSQL's
&lt;a href="https://github.com/postgres/postgres/blob/cca97ce6a6653df7f4ec71ecd54944cc9a6c4c16/src/backend/executor/execExprInterp.c#L6"&gt;src/backend/executor/execExprInterp.c&lt;/a&gt;
clearly explains that expression execution uses a virtual machine. You
see all the hallmarks: opcodes, a loop over a giant switch, etc. And
if we look at how &lt;a href="https://github.com/postgres/postgres/blob/cca97ce6a6653df7f4ec71ecd54944cc9a6c4c16/src/backend/executor/execExprInterp.c#L728"&gt;function expressions are
executed&lt;/a&gt;,
we see another hallmark which is that the function expression code
doesn't evaluate its arguments. They've already been evaluated. And
function expression code just acts on the results of its arguments.&lt;/p&gt;
&lt;p&gt;PostgreSQL also
&lt;a href="https://github.com/postgres/postgres/blob/master/src/backend/jit/README"&gt;supports&lt;/a&gt;
JIT-ing expression execution. And we can find the switch between
interpreting and JIT-compiling an expression
&lt;a href="https://github.com/postgres/postgres/blob/cca97ce6a6653df7f4ec71ecd54944cc9a6c4c16/src/backend/executor/execExpr.c#L873"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/questdb/questdb"&gt;QuestDB&lt;/a&gt; (Ruling: Tree Walker + JIT)&lt;/h4&gt;&lt;p&gt;QuestDB &lt;a href="https://questdb.io/blog/2022/01/12/jit-sql-compiler/"&gt;wrote about their execution engine
recently&lt;/a&gt;. When
the conditions are right, they'll &lt;a href="https://github.com/questdb/questdb/blob/11ac85510292596f0d21b10603e500f8edb5e486/core/src/main/java/io/questdb/griffin/SqlCodeGenerator.java#L1394"&gt;switch over to a JIT
compiler&lt;/a&gt;
and run native code.&lt;/p&gt;
&lt;p&gt;But let's look at the default path. For example, how &lt;a href="https://github.com/questdb/questdb/blob/11ac85510292596f0d21b10603e500f8edb5e486/core/src/main/java/io/questdb/griffin/engine/functions/bool/AndFunctionFactory.java#L82"&gt;&lt;code&gt;AND&lt;/code&gt; is
implemented&lt;/a&gt;. &lt;code&gt;AndBooleanFunction&lt;/code&gt;
implements &lt;code&gt;BooleanFunction&lt;/code&gt; which implements &lt;code&gt;Function&lt;/code&gt;. An
expression can be evaluated by calling a &lt;code&gt;getX()&lt;/code&gt; method on the
expression type that implements &lt;code&gt;Function&lt;/code&gt;. &lt;code&gt;AndBooleanFunction&lt;/code&gt; calls
&lt;code&gt;getBool()&lt;/code&gt; on its left and right hand sides. And if we look at the
&lt;a href="https://github.com/questdb/questdb/blob/11ac85510292596f0d21b10603e500f8edb5e486/core/src/main/java/io/questdb/griffin/engine/functions/BooleanFunction.java#L35"&gt;partial
implementation&lt;/a&gt;
of &lt;code&gt;BooleanFunction&lt;/code&gt; we'll also see it doing &lt;code&gt;getX()&lt;/code&gt; specific
conversions during the call of &lt;code&gt;getX()&lt;/code&gt;. So that's a tree-walking
interpreter.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/scylladb/scylladb"&gt;Scylla&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;If we take a look at how &lt;a href="https://github.com/scylladb/scylladb/blob/08197882074227edbd0a95f49914913e3124753d/cql3/expr/expression.cc#L2145"&gt;functions are
evaluated&lt;/a&gt;
in Scylla, we see function evaluation first &lt;a href="https://github.com/scylladb/scylladb/blob/08197882074227edbd0a95f49914913e3124753d/cql3/expr/expression.cc#L2161"&gt;evaluating all of its
arguments&lt;/a&gt;. And
the function evaluation function itself returns a
&lt;code&gt;cql3::raw_value&lt;/code&gt;. So that's a tree-walking interpreter.&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/sqlite/sqlite"&gt;SQLite&lt;/a&gt; (Ruling: Virtual Machine)&lt;/h4&gt;&lt;p&gt;SQLite's virtual machine is &lt;a href="https://www.sqlite.org/opcode.html"&gt;comprehensive and
well-documented&lt;/a&gt;. It encompasses
more than just expression evaluation but the entirety of query
execution.&lt;/p&gt;
&lt;p&gt;We can find the massive virtual machine switch in
&lt;a href="https://github.com/sqlite/sqlite/blob/8aaf63c6ac8b8292c0ecead0d2b04b68e9e6be78/src/vdbe.c#L971"&gt;src/vdbe.c&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And if we look, for example, at how &lt;code&gt;AND&lt;/code&gt; is implemented, we see it
&lt;a href="https://github.com/sqlite/sqlite/blob/8aaf63c6ac8b8292c0ecead0d2b04b68e9e6be78/src/vdbe.c#L2536"&gt;pulling its arguments out of
memory&lt;/a&gt;
(already evaluated) and assigning the result back to &lt;a href="https://github.com/sqlite/sqlite/blob/8aaf63c6ac8b8292c0ecead0d2b04b68e9e6be78/src/vdbe.c#L2545"&gt;a designated
point in
memory&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;SingleStore (Ruling: Virtual Machine + JIT)&lt;/h4&gt;&lt;p&gt;While there's no source code to link to, SingleStore &lt;a href="https://www.youtube.com/watch?v=_vloWsdPCDs&amp;amp;t=3810s"&gt;gave a talk at
CMU&lt;/a&gt; that broke
down their query execution pipeline. Their
&lt;a href="https://docs.singlestore.com/cloud/query-data/advanced-query-topics/code-generation/"&gt;docs&lt;/a&gt;
also cover the topic.&lt;/p&gt;
&lt;p&gt;&lt;img alt="SingleStore compiler pipeline" src="/assets/memsql.webp" /&gt;&lt;/p&gt;
&lt;h4&gt;&lt;a href="https://github.com/pingcap/tidb"&gt;TiDB&lt;/a&gt; (Ruling: Tree Walker)&lt;/h4&gt;&lt;p&gt;Similar to DuckDB and ClickHouse, TiDB implements vectorized
interpretation. They've &lt;a href="https://www.pingcap.com/blog/10x-performance-improvement-for-expression-evaluation-made-possible-by-vectorized-execution/"&gt;written publicly about their switch to this
method&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's take a look at how &lt;code&gt;if&lt;/code&gt; is implemented in TiDB. There is a
vectorized and non-vectorized version of &lt;code&gt;if&lt;/code&gt; (in
&lt;a href="https://github.com/pingcap/tidb/blob/3ccd09e63addddeb0d33b5b87594a2d61fffd1d8/expression/builtin_control.go"&gt;expression/control_builtin.go&lt;/a&gt;
and
&lt;a href="https://github.com/pingcap/tidb/blob/3ccd09e63addddeb0d33b5b87594a2d61fffd1d8/expression/builtin_control_vec_generated.go"&gt;expression/control_builtin_generated.go&lt;/a&gt;
respectively). So maybe they haven't completely switched over to
vectorized execution or maybe it can only be used in some conditions.&lt;/p&gt;
&lt;p&gt;If we look at the &lt;a href="https://github.com/pingcap/tidb/blob/3ccd09e63addddeb0d33b5b87594a2d61fffd1d8/expression/builtin_control.go#L599"&gt;non-vectorized version of
&lt;code&gt;if&lt;/code&gt;&lt;/a&gt;,
we see the &lt;a href="https://github.com/pingcap/tidb/blob/3ccd09e63addddeb0d33b5b87594a2d61fffd1d8/expression/builtin_control.go#L600"&gt;condition
evaluated&lt;/a&gt;. And
then the &lt;code&gt;then&lt;/code&gt; or &lt;code&gt;else&lt;/code&gt; is evaluated &lt;a href="https://github.com/pingcap/tidb/blob/3ccd09e63addddeb0d33b5b87594a2d61fffd1d8/expression/builtin_control.go#L604"&gt;depending on the result of the
condition&lt;/a&gt;. That's
a tree-walking interpreter.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;As the DuckDB team &lt;a href="https://duckdb.org/why_duckdb.html"&gt;points out&lt;/a&gt;,
vectorized interpretation or JIT compilation &lt;a href="https://www.vldb.org/pvldb/vol11/p2209-kersten.pdf"&gt;seem like the
future&lt;/a&gt; for
database expression execution. These strategies seem particularly
important for analytics or time-series workloads. But vectorized
interpretation seems to make the most sense for column-wise storage
engines. And column-wise storage normally only makes sense for
analytics workloads. Still, TiDB and Cockroach are transactional
databases that also vectorize execution.&lt;/p&gt;
&lt;p&gt;And while SQLite and PostgreSQL use the virtual machine model, it's
possible databases with tree-walking interpreters like Scylla and
MySQL/MariaDB have decided there is not significant enough gains to be
had (for transactional workloads) to justify the complexity of moving
to a compiler + virtual machine architecture.&lt;/p&gt;
&lt;p&gt;Tree-walking interpreters and virtual machines are also independent
from whether or not execution is vectorized. So that will be another
interesting dimension to watch: if more databases move toward
vectorized execution even if they don't adapt JIT compilation.&lt;/p&gt;
&lt;p&gt;Yet another alternative is that maybe as databases mature we'll see
compilation tiers similar to what &lt;a href="https://webkit.org/blog/9329/a-new-bytecode-format-for-javascriptcore/"&gt;browsers
do&lt;/a&gt;
&lt;a href="https://v8.dev/blog/sparkplug"&gt;with JavaScript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Credits: Thanks Max Bernstein, Alex Miller, and Justin Jaffray for
reviewing a draft version of this! And thanks to the #dbs channel on
&lt;a href="https://eatonphil.com/discord.html"&gt;Discord&lt;/a&gt; for instigating this
post!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I spent some time looking into how various databases execute expressions in their query language.&lt;br /&gt;&lt;br /&gt;Most of them have a tree-walking interpreter, some have a virtual machine, and some do just-in-time compilation.&lt;br /&gt;&lt;br /&gt;Let's dig into some database code to see!&lt;a href="https://t.co/BIGtHKh1X4"&gt;https://t.co/BIGtHKh1X4&lt;/a&gt; &lt;a href="https://t.co/nmhe9HmYw7"&gt;pic.twitter.com/nmhe9HmYw7&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1704936432412868725?ref_src=twsrc%5Etfw"&gt;September 21, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 21 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-09-21-how-do-databases-execute-expressions.html</guid></item><item><title>F-Zero 99</title><link>http://pxtl.ca/2023/09/19/fzero99/</link><description>&lt;p&gt;i got a little carried away with this nostalgia game.  I loved the old F-Zero and this remake lets you play the classic against 98 other players at once.&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Tue, 19 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/09/19/fzero99/</guid></item><item><title>Vim tip 32: text and indent settings</title><link>https://learnbyexample.github.io/tips/vim-tip-32/</link><description>&lt;p&gt;Here are some text and indent Vim settings that you can put in the &lt;code&gt;vimrc&lt;/code&gt; file to customize your editor. See &lt;a href="https://vimhelp.org/options.txt.html"&gt;:h options.txt&lt;/a&gt; for complete reference.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;filetype plugin indent on&lt;/kbd&gt; enables loading of &lt;code&gt;plugin&lt;/code&gt; and &lt;code&gt;indent&lt;/code&gt; files
&lt;ul&gt;
&lt;li&gt;these files become active based on the type of the file to influence syntax highlighting, indentation, etc&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:echo $VIMRUNTIME&lt;/kbd&gt; gives your installation directory (&lt;code&gt;indent&lt;/code&gt; and &lt;code&gt;plugin&lt;/code&gt; directories would be present in this path)&lt;/li&gt;
&lt;li&gt;see &lt;a href="https://vimhelp.org/usr_05.txt.html#vimrc-filetype"&gt;:h vimrc-filetype&lt;/a&gt;, &lt;a href="https://vimhelp.org/filetype.txt.html#%3Afiletype-overview"&gt;:h :filetype-overview&lt;/a&gt; and &lt;a href="https://vimhelp.org/filetype.txt.html"&gt;:h filetype.txt&lt;/a&gt; for more details&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set autoindent&lt;/kbd&gt; copy indent from the current line when starting a new line
&lt;ul&gt;
&lt;li&gt;useful for files not affected by &lt;code&gt;indent&lt;/code&gt; setting&lt;/li&gt;
&lt;li&gt;see also &lt;a href="https://vimhelp.org/options.txt.html#%27smartindent%27"&gt;:h smartindent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set textwidth=80&lt;/kbd&gt; guideline for Vim to automatically move to a new line with &lt;code&gt;80&lt;/code&gt; characters as the limit
&lt;ul&gt;
&lt;li&gt;white space is used to break lines, so a line can still be greater than the limit if there's no white space&lt;/li&gt;
&lt;li&gt;default is &lt;code&gt;0&lt;/code&gt; which disables this setting&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set colorcolumn=80&lt;/kbd&gt; create a highlighted vertical bar at column number &lt;code&gt;80&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;use &lt;code&gt;highlight ColorColumn&lt;/code&gt; setting to customize the color for this vertical bar&lt;/li&gt;
&lt;li&gt;see &lt;a href="https://vi.stackexchange.com/q/574/1616"&gt;vi.stackexchange: Keeping lines to less than 80 characters&lt;/a&gt; for more details&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set shiftwidth=4&lt;/kbd&gt; number of spaces to use for indentation (default is &lt;code&gt;8&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set tabstop=4&lt;/kbd&gt; width for the tab character (default is &lt;code&gt;8&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set expandtab&lt;/kbd&gt; use spaces for tab expansion&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set cursorline&lt;/kbd&gt; highlight the line containing the cursor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 19 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-32/</guid></item><item><title>Software 3.0 and the AI Engineer Landscape (talk notes &amp;amp; slides)</title><link>https://www.swyx.io/ai-landscape</link><description>&lt;h2&gt;2024 update&lt;/h2&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 18 Sep 2023 22:50:48 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/ai-landscape</guid></item><item><title>Thank you</title><link>https://letterstoanewdeveloper.com/2023/09/18/thank-you/</link><description>Dear reader, Welcome to the last letter. I couldn&amp;#8217;t have imagined what would happen when I penned my first letter 5 years ago. (I still think you should learn version control, for the record.) This blog outlasted two jobs. 280+ posts and 220,000+ visitors later, this project has changed my life in so many ways. &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/09/18/thank-you/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Thank you&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 18 Sep 2023 19:12:21 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/09/18/thank-you/</guid></item><item><title>Steam engine or bridge?</title><link>http://blog.onepatchdown.net/2023/09/15/steam-engine-bridge/</link><description>&lt;p&gt;When looking at the bigger picture of writing and maintaining complex software systems, I look at it and ask:  have I built the software-equivalent of a bridge, or a steam engine. While a bridge needs mainteance and inspection, after it’s built, it largely just sits there. You spend a lot of time up-front, designing and engineering it before even a single brick is laid.&lt;/p&gt;

&lt;p&gt;A steam engine on the other hand, only just sits there if you don’t want it to do what it was designed to do - be an engine. It needs a team of people working tirelessly round the clock babysitting it to cajole it into performing.&lt;/p&gt;

&lt;p&gt;‘ls’ is a bridge piece of software. It basically sits there. But SaaS companies’ backends are a complex steam engine piece of software, which is why you need SREs.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Fri, 15 Sep 2023 23:23:00 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/2023/09/15/steam-engine-bridge/</guid></item><item><title>Marginalia Search receives FUTO Grant</title><link>https://www.marginalia.nu/log/88-futo-grant/</link><description>I&amp;rsquo;m happy to announce that the generous people at FUTO have granted the project $15,000 with no strings attached to help the search engine out with some more server power.
FUTO is a young Austin, TX-based organization &amp;ldquo;dedicated to developing, both through in-house engineering and investment, technologies that frustrate centralization and industry consolidation&amp;rdquo;. It&amp;rsquo;s one to keep an eye on, I believe their heart is in the right place and they have every possibility of making a real difference.</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 15 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/88-futo-grant/</guid></item><item><title>What I'm up to - September 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-september-2023</link><description>&lt;div class="prose"&gt;
  &lt;div&gt;I'm &lt;a href="https://www.philipithomas.com/"&gt;Philip&lt;/a&gt;, and this is my monthly newsletter about what I'm up to, which &lt;a href="https://www.philipithomas.com/posts/how-to-replace-social-media-with-a-personal-newsletter"&gt;I send in place of social media&lt;/a&gt;.&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;✨ What I was up to in August&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.contraption.co/news/introducing-frctnl/"&gt;Launched FRCTNL&lt;/a&gt;, a community of fractional tech workers.&lt;/li&gt;
&lt;li&gt;Continued building &lt;a href="https://booklet.group"&gt;Booklet&lt;/a&gt;, which is the software powering FRCTNL.&lt;/li&gt;
&lt;li&gt;Returned to Mexico City, where &lt;a href="https://quintonil.com/en/home-3/"&gt;Quintonil&lt;/a&gt; reaffirmed its rank as my #2 favorite restaurant in the world.&lt;/li&gt;
&lt;li&gt;Ate a lot of &lt;a href="https://en.wikipedia.org/wiki/Tsukemen"&gt;tsukemen&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Graded at a crossword puzzle tournament.&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;&lt;strong&gt;🤔 Things to share&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles&lt;/strong&gt;: &lt;a href="https://www.washingtonpost.com/media/2023/08/01/news-avoid-depressing/"&gt;Do you avoid the news? You’re in growing company&lt;/a&gt;. (See "News Minimalist" below). &lt;a href="https://craigmod.com/ridgeline/139/"&gt;Walk for the Boredom of it All&lt;/a&gt;. &lt;a href="https://www.scanofthemonth.com/scans/coffee"&gt;Scans of coffee equipment&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books&lt;/strong&gt;: &lt;a href="https://en.wikipedia.org/wiki/Steve_Jobs_(book)"&gt;Steve Jobs by Walter Isaacson&lt;/a&gt; (re-reading after a decade). &lt;a href="https://www.amazon.com/Cold-Start-Problem-Andrew-Chen/dp/0062969749"&gt;Cold Start Problem&lt;/a&gt; (again), &lt;a href="https://www.amazon.com/Hackers-Painters-Big-Ideas-Computer-ebook/dp/B0026OR2NQ/ref=tmm_kin_swatch_0?_encoding=UTF8&amp;amp;qid=1694522305&amp;amp;sr=8-1"&gt;Hacker and Painters&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Podcast&lt;/strong&gt;: &lt;a href="https://overcast.fm/+OxeY4JR3I"&gt;Frank Slootman on The Knowledge Project&lt;/a&gt; (a refreshing take on high-performance cultures).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps&lt;/strong&gt;: &lt;a href="https://www.newsminimalist.com/about"&gt;News Minimalist&lt;/a&gt; (now the only news I'm reading).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video&lt;/strong&gt;: &lt;a href="https://www.youtube.com/watch?v=3-Jrp6it9Ss"&gt;Tunnel Vision: An Unauthorized BART Ride&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other&lt;/strong&gt;: &lt;a href="https://nomaprojects.com/"&gt;Noma Projects&lt;/a&gt; finally ships to the USA. &lt;a href="https://once.com"&gt;Once&lt;/a&gt;, a new project from 37Signals.Thinking about &lt;a href="https://www.dwell.com/article/joseph-eichler-homes-integration-fair-housing-policy-2cab8157"&gt;Eichler homes&lt;/a&gt;. "&lt;a href="https://semilshah.com/2023/09/09/seed-market-evolution-during-a-downturn/"&gt;Where founders live matters again&lt;/a&gt;."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trend:  &lt;/strong&gt;PE for startups (such as &lt;a href="https://thirdsouth.capital/"&gt;Third South Capital&lt;/a&gt; and &lt;a href="https://www.tiny.com/"&gt;Tiny&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffees I'm drinking: &lt;/strong&gt;&lt;a href="https://www.lacabra.dk/products/elida-23"&gt;La Cabra Elida&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;&lt;strong&gt;📫 What I'm up to in September&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Making a couple of brief trips for weddings&lt;/li&gt;
&lt;li&gt;Hoping to open Booklet access more broadly&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;&lt;strong&gt;📍 Where I'll be &lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Sept 21-25: Stockholm 🇸🇪&lt;/li&gt;
&lt;li&gt;Sept 29-Oct 1: Chicago 🏢&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;em&gt;(Let me know if we overlap)&lt;/em&gt;&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;📸 Photo&lt;/strong&gt;&lt;/h2&gt;&lt;div&gt;
&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdEJoIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7332d592a3b26b9f1aa598549a8896c3516f0315/5BB4689B-E4CD-4E0D-BDFA-68A9CF982765_1_201_a.jpeg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Bugs were a (tasty) theme in Mexico&lt;/figcaption&gt;
&lt;/figure&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Tue, 12 Sep 2023 15:54:53 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-september-2023</guid></item><item><title>More rigid</title><link>http://pxtl.ca/2023/09/12/more-rigid/</link><description>&lt;p&gt;If you watch "Well There's Your Problem" podcast (about engineering disasters),
Alice has an iconoclastic opinion on things like expansion plates and swivel
joints and the general flexibility of good engineering: "No, make it more
rigid!" I have taken that to heart with some recent home improvement projects.&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Tue, 12 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2023/09/12/more-rigid/</guid></item><item><title>CallerArgumentExpression and extension methods don’t mix</title><link>https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/</link><description>&lt;p&gt;This post is for the C# developers out there and takes a look at the interesting conjunction of [CallerArgumentExpression] and static extension methods – a mix that at first seems too convenient to pass up. A quick recap: [CallerArgumentExpression] landed &amp;#8230; &lt;a href="https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/"&gt;CallerArgumentExpression and extension methods don’t mix&lt;/a&gt; first appeared on &lt;a href="https://neosmart.net/blog"&gt;The NeoSmart Files&lt;/a&gt;.</description><author>The NeoSmart Files</author><pubDate>Mon, 11 Sep 2023 20:17:55 GMT</pubDate><guid isPermaLink="true">https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/</guid></item><item><title>CLI tip 33: manipulating string case with GNU sed</title><link>https://learnbyexample.github.io/tips/cli-tip-33/</link><description>&lt;p&gt;&lt;code&gt;sed&lt;/code&gt; provides escape sequences to change the case of replacement strings, which might include backreferences, shell variables, etc.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Sequence&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\E&lt;/code&gt;&lt;/td&gt;&lt;td&gt;indicates the end of case conversion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\l&lt;/code&gt;&lt;/td&gt;&lt;td&gt;convert the next character to lowercase&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\u&lt;/code&gt;&lt;/td&gt;&lt;td&gt;convert the next character to uppercase&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\L&lt;/code&gt;&lt;/td&gt;&lt;td&gt;convert the following characters to lowercase (overridden by &lt;code&gt;\U&lt;/code&gt; or &lt;code&gt;\E&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;\U&lt;/code&gt;&lt;/td&gt;&lt;td&gt;convert the following characters to uppercase (overridden by &lt;code&gt;\L&lt;/code&gt; or &lt;code&gt;\E&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;First up, changing case of only the immediate next character after the escape sequence.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# match only the first character of a word
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use &amp;amp; to backreference the matched character
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# \u would then change it to uppercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hello there. how are you?' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\b\w/\u&amp;amp;/g'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Hello There&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;How Are You&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# change the first character of a word to lowercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HELLO THERE. HOW ARE YOU?' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\b\w/\l&amp;amp;/g'
&lt;/span&gt;&lt;span&gt;hELLO tHERE. hOW aRE yOU?
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# match lowercase followed by underscore followed by lowercase
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# delete the underscore and convert the 2nd lowercase to uppercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'_fig aug_price next_line' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/([a-z])_([a-z])/\1\u\2/g'
&lt;/span&gt;&lt;span&gt;_fig augPrice nextLine
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, changing case of multiple characters at a time.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# change all alphabets to lowercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HaVE a nICe dAy' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/.*/\L&amp;amp;/'
&lt;/span&gt;&lt;span&gt;have a nice day
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# change all alphabets to uppercase
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HaVE a nICe dAy' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/.*/\U&amp;amp;/'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;HAVE A NICE DAY
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# \E will stop further conversion
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig_ aug_price next_line' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/([a-z]+)(_[a-z]+)/\U\1\E\2/g'
&lt;/span&gt;&lt;span&gt;fig_ &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;AUG_price NEXT_line
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# \L or \U will override any existing conversion
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HeLLo:bYe gOoD:beTTEr' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/([a-z]+)(:[a-z]+)/\L\1\U\2/Ig'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;hello:&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;BYE &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;good:&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;BETTER
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, examples where escapes are used next to each other.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# uppercase first character of a word
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# and lowercase rest of the word characters
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note the order of escapes used, \u\L won't work
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HeLLo:bYe gOoD:beTTEr' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/[a-z]+/\L\u&amp;amp;/Ig'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;Hello:&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Bye &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;Good:&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Better
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# lowercase first character of a word
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# and uppercase rest of the word characters
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'HeLLo:bYe gOoD:beTTEr' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/[a-z]+/\U\l&amp;amp;/Ig'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;hELLO:&lt;/span&gt;&lt;span&gt;bYE &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;gOOD:&lt;/span&gt;&lt;span&gt;bETTER
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/learn_gnused"&gt;CLI text processing with GNU sed&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 11 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-33/</guid></item><item><title>Wordcount I: Implementing the Unix command-line tool `wc` in modern C++</title><link>https://bytepawn.com/implementing-the-unix-command-line-tool-wc-in-modern-cpp.html</link><description>&lt;p&gt;After reading the excellent book Beautiful C++ about the language's latest features, I implement the Unix command-line tool &lt;code&gt;wc&lt;/code&gt; in modern C++.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Beautiful C++" src="https://m.media-amazon.com/images/I/51ByxdWleSL.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 10 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/implementing-the-unix-command-line-tool-wc-in-modern-cpp.html</guid></item><item><title>Swyx's Simple Guide to Singapore</title><link>https://www.swyx.io/sg-guide</link><description>&lt;p&gt;A personal guide to Singapore for foreign friends visiting.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 07 Sep 2023 19:59:56 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/sg-guide</guid></item><item><title>Python tip 33: sorting iterables based on multiple conditions</title><link>https://learnbyexample.github.io/tips/python-tip-33/</link><description>&lt;p&gt;In an &lt;a href="https://learnbyexample.github.io/tips/python-tip-21/"&gt;earlier tip&lt;/a&gt;, you learned how to sort iterables based on a key. You can use a sequence like &lt;code&gt;list&lt;/code&gt; or &lt;code&gt;tuple&lt;/code&gt; to specify a tie-breaker condition when two or more items are deemed equal under the primary sorting rule.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;books &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mage Errant'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Piranesi'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Weirkey Chronicles'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mistborn'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# sorts based on the number of words
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# retains original order for items with the same number of words
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;sorted&lt;/span&gt;&lt;span&gt;(books, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;key&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=lambda &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;b&lt;/span&gt;&lt;span&gt;: b.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;count&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Piranesi'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mistborn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mage Errant'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Weirkey Chronicles'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# items with the same number of words are further sorted in alphabetic order
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;sorted&lt;/span&gt;&lt;span&gt;(books, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;key&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=lambda &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;b&lt;/span&gt;&lt;span&gt;: (b.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;count&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt;), b))
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mistborn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Piranesi'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mage Errant'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Weirkey Chronicles'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To sort in descending order, usually the &lt;code&gt;reverse=True&lt;/code&gt; keyword argument is used. But what if the primary and secondary rules are opposites? If one of the rule is numerical in nature, you can simply negate the number to reverse the order.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# descending order based on the number of words
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# ascending alphabetic order for items with the same number of words
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; sorted(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;books,&lt;/span&gt;&lt;span&gt; key=lambda b: (-b.count(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt;), b))
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Weirkey Chronicles'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mage Errant'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mistborn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Piranesi'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# reverse the above result
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; sorted(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;books,&lt;/span&gt;&lt;span&gt; key=lambda b: (-b.count(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt;), b)&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;,&lt;/span&gt;&lt;span&gt; reverse=True)
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Piranesi'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mistborn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mage Errant'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Weirkey Chronicles'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://docs.python.org/3/howto/sorting.html"&gt;docs.python HOWTOs: Sorting&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 05 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-33/</guid></item><item><title>Brag post: Hacker News Front Page entries</title><link>https://learnbyexample.github.io/mini/hacker-news-front-page-brag/</link><description>&lt;p&gt;In case you haven't yet read this nice post &lt;a href="https://jvns.ca/blog/brag-documents/"&gt;&amp;quot;Get your work recognized: write a brag document&amp;quot;&lt;/a&gt; by Julia Evans, please do that first.&lt;/p&gt;
&lt;p&gt;I definitely found it nice to collect which of my content have reached Hacker News front page over the past 4 years. As I wrote in &lt;a href="https://learnbyexample.github.io/my-book-writing-experience/"&gt;my book writing experience post&lt;/a&gt;, the responses I got for my GNU awk one-liners collection was one of the stepping stones towards my career as a technical author.&lt;/p&gt;
&lt;p&gt;Here's the list so far, ordered by oldest first:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=15549318"&gt;Learn to use Awk with hundreds of examples&lt;/a&gt; — &lt;em&gt;478 points, Oct 2017, 116 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=20212622"&gt;Show HN: I wrote a book on GNU grep and ripgrep&lt;/a&gt; — &lt;em&gt;182 points, June 2019, 53 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=20645319"&gt;Show HN: I wrote a book on Python regular expressions&lt;/a&gt; — &lt;em&gt;193 points, Aug 2019, 50 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=22758217"&gt;Show HN: An eBook with hundreds of GNU Awk one-liners&lt;/a&gt; — &lt;em&gt;539 points, April 2020, 48 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=24637797"&gt;Show HN: Ruby One-Liners Cookbook&lt;/a&gt; — &lt;em&gt;191 points, Sept 2020, 36 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=25006829"&gt;Perl One-Liners Cookbook&lt;/a&gt; — &lt;em&gt;126 points, Nov 2020, 47 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=26076721"&gt;Show HN: &amp;quot;100 Page Python Intro&amp;quot; eBook&lt;/a&gt; — &lt;em&gt;107 points, Feb 2021, 26 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=26356095"&gt;Paying my bills with 'free' ebooks&lt;/a&gt; — &lt;em&gt;85 points, Mar 2021, 22 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=28798095"&gt;Show HN: &amp;quot;Command line text processing with GNU Coreutils&amp;quot; eBook&lt;/a&gt; — &lt;em&gt;117 points, Oct 2021, 20 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=29391107"&gt;Show HN: Improve your Python regex skills with 75 interactive exercises&lt;/a&gt; — &lt;em&gt;175 points, Nov 2021, 12 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=29837543"&gt;Vim prank: alias vim='vim -y'&lt;/a&gt; — &lt;em&gt;341 points, Jan 2022, 259 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=30684232"&gt;Vim Reference Guide&lt;/a&gt; — &lt;em&gt;244 points, Mar 2022, 110 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=33931677"&gt;Show HN: Interactive exercises for Linux CLI text processing commands&lt;/a&gt; — &lt;em&gt;69 points, Dec 2022, 7 comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=37290356"&gt;CLI text processing with GNU awk&lt;/a&gt; — &lt;em&gt;419 points, Aug 2023, 129 comments&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As is the case with other social media platforms, being an active participant on Hacker News definitely helps. Apart from commenting on other topics, I also post links to projects and resources that I felt were useful. The number of such links reaching front page outnumbers my own content links.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 05 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/mini/hacker-news-front-page-brag/</guid></item><item><title>It’s not magic (part 2)</title><link>https://letterstoanewdeveloper.com/2023/09/04/its-not-magic-part-2/</link><description>Dear new developer, When you see a senior engineer who knows the code base like the back of her hand maneuver around it at the speed of thought, it sure looks like magic. When a mysterious bug pops up and you spend a couple of hours trying to replicate it, but then an old hand &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/09/04/its-not-magic-part-2/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;It&amp;#8217;s not magic (part&amp;#160;2)&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 04 Sep 2023 15:16:47 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/09/04/its-not-magic-part-2/</guid></item><item><title>AI: The Somnium Files review</title><link>https://burakku.com/blog/ai-the-somnium-files-review/</link><description>&lt;p&gt;&lt;img alt="AI: The Somnium Files" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Eye spy with my little AI.&lt;/p&gt;
&lt;p&gt;AI: The Somnium Files is an interactive murder mystery story. You play as Date Kaname, an amnesiac special agent trying to track down a mysterious serial killer using an AI-powered eyeball and a machine that allows invading the dreams of investigation targets to unlock the secrets of their minds by solving puzzles.&lt;/p&gt;
&lt;p&gt;But despite being a murder mystery, it kinda doesn't feel like one during a good chunk of it. The mood of it is kinda all over the place and quite a vast chunk of the game is spent engaging in various kinds of comedy routines. I imagine the humour won't land on everyone – it is quite Japanese and trends towards lower than higher end of the brow. I did generally like the comedic bits but it does induce a level of mood whiplash as you go from fawning over a porn magazine to gruesome murder in the span of 20 minutes. Thankfully it does manage to stay serious during the more serious and somber moments of the story, so it's not incredibly jarring.&lt;/p&gt;
&lt;p&gt;&lt;img alt="X-ray vision" src="xray.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The Somnium Files features a relatively unique way of branching the storyline. Taking certain actions in the game puts you in a different branch in the story, and some of the branches lock you out in middle of it, telling you to go finish other branches to unlock it. It even unlocks a route, lets you read through it for a bit and then locks it up again in order to throw you onto another, now-unlocked route. At first I wasn't really sure what to think of it, but now in hindsight, I do like the implementation. It does a good job at keeping you guessing for a good chunk of the story, preventing you from reaching the juiciest bits first while simultaneously dropping hints across the different branches.&lt;/p&gt;
&lt;p&gt;As for the story itself, I really liked it. It's interesting, intriguing and engaging. It was fun to gather the clues throughout the story and work together the connections. And boy, are there connections. This thing is more interconnected than the World Wide Web. I'm proud that I was able to sleuth some of the plot points but I still didn't figure out the true culprit until the big reveal. As a novice to mysteries, the solution felt a bit out of left field, but not in a way that felt unsatisfying or unfair. And once you had the solution, a lot of the story prior to that just started clicking into places. Even some offhand remarks, made seemingly in jest, fit into the puzzle.&lt;/p&gt;
&lt;p&gt;Beyond just the murder mystery, I also enjoyed overall character stories. Most of the early endings don't end you with solving the crime but still had satisfying and emotional finales to them. I may have even shed a tear or two for this perverted detective adventure. That being said, the story isn't all that realistic. You can probably figure that out within minutes when they tell you that you have an artificial eyeball that contains a highly sophisticated artificial intelligence. So there's definitely a degree of sci-fi going on here. However, it also has other very unrealistic elements too for you to suspend your disbelief. You might have a little girl with the power to take down a bear with her bare hands, or have a squadron of gunmen shooting worse than the freaking Stormtroopers. But if this kind of a thing doesn't faze you, there's a fun and interesting story waiting for you.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Internet history" src="internet-history.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Most of the game is spent in interactive point-and-click segments where you explore the world and talk to the various characters. I don't think there's really any way to fail these, so it plays more like a visual novel with some leeway as to how many things you want to interact with. You also have some action bits with quick-time events and interrogation events where you need to pick evidence to show to a suspect to reel them in. The core gameplay is quite simple without really anything to particularly like or dislike, although I would've liked to be able to see what objects had new dialogue and which didn't. Could've saved me from clicking on Boss' computer for the ninth time to see if there was dialogue hidden behind it.&lt;/p&gt;
&lt;p&gt;The other part of the game, and arguably the most video game part of the whole thing, are the titular Somnium segments. These are puzzle segments where you need to figure out how to unlock the secrets of the target's mind before you run out of time. But counter to this description, these are relatively slow affairs. You have 360 seconds to spare and each action you can take inside the dream takes up an arbitrary amount of time, and idling in place happens in super-slow motion. Running towards a door and kicking it open might be just a second, or it might be closer to a minute, depending on the stage and conditions. And if you run out of time, you'll fail and have to replay through most if not all of it again. These dream sequences are also where the actual branching happens, so you won't need a walkthrough for routing.&lt;/p&gt;
&lt;p&gt;I'm a bit of two minds about the Somnium segments. On one hand, they were an interesting and unique aspect of the game, with the occasional moment of hilarity. And while some of the puzzles I could determine by remembering what clues I'd gathered during the normal gameplay and applying them in the dream, some of the aspects required just utterly pure guesswork. How am I supposed to know whether to stick my head or my hand inside a hole? And if you guess wrong and run out of time, you're gonna waste a bunch of time running through the same already-solved puzzles in order to progress through. These puzzles do definitely add to the game, but I just wish they didn't waste as much of my time if my guesses didn't work out. After a fail, it just becomes an exercise of mechanical repetition and holding down the skip button.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Oh hey, me too." src="internet-famous.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The graphics are so-so. The world detail leaves a lot of room for improvement but the character models are mostly good with excellent designs. Some of the facial animations look a bit iffy at times, but I did quite like the overall 3D character look. There's a sense of charm about it. I wasn't necessarily expecting that since I've usually preferred 2D sprites over 3D models in other games. Iris and Mizuki were both very cute in the game. And of course, with everyone being a 3D model, you could also have properly animated cutscenes in the game. Well, cutscene animation was also a bit strange at times too, but it's still a more visual affair than most visual novels.&lt;/p&gt;
&lt;p&gt;No complaints on the sound side. Opting for the original Japanese voices, the voice acting was pretty good. Not its greatest asset but still good. The soundtrack was nothing special but it did its job providing the mood.&lt;/p&gt;
&lt;p&gt;The technical side however is a bit sketchy. This is quite clearly a console port and has some basic graphics options inside a separate launcher that don't all even work properly. It also forces you to choose between keyboard/mouse and controller input in the launcher, even though you can still use a controller with keyboard/mouse controls. You just don't get the proper button prompts for a controller. Annoyingly it also ignored my choice for Japanese voice acting for the ending movie for reasons unknown. Granted, it never crashed on me or anything, but a bit more polish on the technical front would've been nice.&lt;/p&gt;
&lt;p&gt;I would definitely recommend reading through AI: The Somnium Files. Its gameplay won't win it any recognition but story is interesting, rather fun, occasionally touching and most likely will keep you guessing until the end. I'm definitely going to be checking out the sequel, &lt;em&gt;AI: The Somnium Files - Nirvana Initiative&lt;/em&gt;, at some point. I imagine I also need to check out other Uchikoshi production too.&lt;/p&gt;</description><author>ブラック</author><pubDate>Mon, 04 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/ai-the-somnium-files-review/</guid></item><item><title>Practice and Experience</title><link>https://blog.bayindirh.io/blog/practice-and-experience/</link><description>&lt;p&gt;During my lifetime, I have met with people who had certain skills, like reading the weather, or knowing a technical subject inside out, or being able to learn pretty quickly while being thorough. I have admired these people, and wanted to learn what made them tick. The main question was "How is this possible, and what can I learn from these people", and the feeling accompanying this question was always a healthy wonder and awe, not envy or anger.&lt;/p&gt;
&lt;p&gt;Moreover, I noticed that these people didn't boast their skills, in any way. A neighbor may make a passing remark about tomorrow's weather, or a friend will casually note a pitfall I may encounter during implementing something. Moreover, things progress exactly the way they noted earlier. This like being among a band of oracles, and both feels awesome and makes you wonder about how this is possible.&lt;/p&gt;
&lt;p&gt;As life progressed, and I tried to quench my thirst for learning, a pattern started to emerge. I noticed that I carry a big body of knowledge about a couple of subjects, without trying for it. Interestingly, the dialogues would progress the same. A casual remark about a potential problem followed by a "I didn't think of this" look. These occurrences surprised me, generating more questions about the process, converting main question to "How I did this?"&lt;/p&gt;
&lt;p&gt;The question prompted me to observe myself more intently and closely.&lt;/p&gt;
&lt;p&gt;This observation lead me to two revelations about the process. i.e. How practice and experience works, and how adding time to these changes things.&lt;/p&gt;
&lt;p&gt;The first revelation was how practice and experience work in tandem. It's always said that "practice makes perfect", and to perfect ourselves, we practice. Practice is irreplaceable and invaluable all in itself. It sharpens the skill and the mind, reducing the mental cost of the skill while increasing the quality of the result. However, it's the experience you gain adds the subtle and impeccable parts to your knowledge and how you handle things. The edge cases, possible problems, the smell of how things are going can't be obtained without experience. A textbook, a mentor, or a course may give you a direction or a simple sense, but sharpening that is up to you and it's only possible with the experience you get over time. Because, even if you practice deliberately, you sharpen what you know. What you don't know can't be practiced.&lt;/p&gt;
&lt;p&gt;However, experience does not make you better at that instant. I t needs time. Time is required to let the experience sink in, get introduced into your practice, and get considered during the next real-world scenario requires that knowledge. When you take that step, and get experience on top of the existing one, because you need to go step by step, the knowledge will start to accumulate.&lt;/p&gt;
&lt;p&gt;This accumulation is slow and not recognizable, but when you need to tap into that knowledge, it'll show itself.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Mon, 04 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/practice-and-experience/</guid></item><item><title>Eight years of organizing tech meetups</title><link>http://notes.eatonphil.com/eight-years-of-tech-meetups.html</link><description>&lt;p&gt;This is a collection of random personal experiences. So if you don't
want to read everything, feel free to skip to the end for takeaways.&lt;/p&gt;
&lt;p&gt;I write because I'd like to see more high-quality meetups. And maybe
my little bit of experience will help someone out.&lt;/p&gt;
&lt;h3 id="2015:-philadelphia"&gt;2015: Philadelphia&lt;/h3&gt;&lt;p&gt;I first tried to organize a meetup in Philly in 2015. I was
contracting at the time and I figured a meetup might be a good way to
source contracts or just meet interesting people. I created the
"Philadelphia Software in Business" (or some other similarly vaguely
named) group on Meetup.com.&lt;/p&gt;
&lt;p&gt;I didn't have any network; the first companies I worked for were not
in Philly. But Meetup.com got me a few tens of people joining the
group.&lt;/p&gt;
&lt;p&gt;My first challenge was finding a place to meet. I didn't know what I
was doing so I looked at restaurants, bars, and cafes for dedicated
event space. Needless to say, renting space was expensive on its
own. And there was always an additional required minimum dollar spent
per attendee.&lt;/p&gt;
&lt;p&gt;I ultimately found a place near the Schuylkill River. Maybe it was a
community event space. Maybe I paid for it. I can't remember.&lt;/p&gt;
&lt;p&gt;The first and only time I hosted an event for the group, I got a
surprising number of people for such a vague topic. There were maybe 6
of us. I was the youngest by far (I was 20), they were middle
age. Excel users and one visionary type.&lt;/p&gt;
&lt;p&gt;There was no real point to the meetup and I didn't continue doing
it.&lt;/p&gt;
&lt;h3 id="2016---2017:-linode"&gt;2016 - 2017: Linode&lt;/h3&gt;&lt;p&gt;While I was at Linode, I organized "hack nights". I didn't ask for
anyone's approval before starting it. I just said I'd be ordering
pizza for anyone interested in staying after work to hack on
Linode-related projects. I was willing to pay for the pizza, in part
because I didn't want to risk being shut down by asking. But caker
paid for it each time.&lt;/p&gt;
&lt;p&gt;I was nervous because people would show up and ask for pizza and not
want to hack. It was company-provided under the aspiration of doing
Linode-related work. Maybe I mentioned this or not. I can't
remember. I'm pretty sure they got their pizza.&lt;/p&gt;
&lt;p&gt;Aside from myself, developers at Linode didn't really attend. The
folks who attended were support staff or folks from the technical
writing team who wanted more experience coding.&lt;/p&gt;
&lt;p&gt;I ran this for maybe 3 to 5 Wednesdays before not continuing. It was
pretty fun! But staying after work for a few hours each Wednesday lost
its charm.&lt;/p&gt;
&lt;h4 id="book-club"&gt;Book Club&lt;/h4&gt;&lt;p&gt;Another time at Linode I started a book club. I was very torn about
attempting to make the book club open to anyone in the area or just to
Linode employees.&lt;/p&gt;
&lt;p&gt;I knew I'd probably get more people to attend if I made it public. But
I wasn't sure if Linode would be cool with having external folks in
the office. Before they moved to the Old City office, visitors weren't
really a thing.&lt;/p&gt;
&lt;p&gt;So I made it private to Linode. And I started with the most obvious
book for your average developer: Practical Common Lisp.&lt;/p&gt;
&lt;p&gt;I am pretty sure I learned one big trick by this time though. When I
announced I'd be starting the book club I said something like this:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Hey folks! I'm thinking of starting a book club. A book I have in
mind to start with is Practical Common Lisp. If I get at least one
other person to join in then I'll move forward!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I ended up getting two folks: one developer and one support staff
member. We held the book club for 30 minutes once a week, covering one
chapter each week. I was the only one who read anything I think, but
the other two guys faithfully showed up for discussion.&lt;/p&gt;
&lt;p&gt;I didn't ask for permission to do this either. And this time we met
during company time. I think it was 2-2:30PM.&lt;/p&gt;
&lt;p&gt;It was fun. We finished the book. But Practical Common Lisp probably
wasn't a good choice. And I don't think I started a second book.&lt;/p&gt;
&lt;h3 id="2017---2020:-false-starts"&gt;2017 - 2020: False starts&lt;/h3&gt;&lt;p&gt;I moved to NYC and joined a small startup (~20 employees). Linode was
100+ employees.&lt;/p&gt;
&lt;p&gt;We were in a WeWork so I considered starting a book club that was
public to the WeWork. I had learned by then the law of numbers: I
probably wouldn't get anyone from my company to join.&lt;/p&gt;
&lt;p&gt;I considered putting up posters around the WeWork to advertise. But in
the end, I didn't end up going through with anything.&lt;/p&gt;
&lt;p&gt;I did present at a few meetups in NYC during this time. But I didn't
organize anything.&lt;/p&gt;
&lt;p&gt;And then the pandemic hit and everything disappeared.&lt;/p&gt;
&lt;h3 id="2021---2022:-virtual"&gt;2021 - 2022: Virtual&lt;/h3&gt;&lt;p&gt;In 2021 I started contracting again, thinking about starting a
company. I wanted a community to be at the center.&lt;/p&gt;
&lt;p&gt;So I started a &lt;a href="https://eatonphil.com/discord.html"&gt;Discord focused on software
internals&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had a bit more of a network at this point so I posted about the
Discord on Twitter and got 100 likes or something and slowly started
gaining folks in the Discord.&lt;/p&gt;
&lt;p&gt;I knew it was going to do better if I was pretty active in it so I
made sure to post interesting blog posts at some regular
interval. About compilers or databases or something.&lt;/p&gt;
&lt;p&gt;The Discord didn't turn out to help me out much in the
starting-a-company front. Or I didn't use it effectively for that.&lt;/p&gt;
&lt;p&gt;I wanted more of an independent Discord of cool people who like to
learn about systems internals. And that's what I got.&lt;/p&gt;
&lt;p&gt;This turned out to be ok though because I stopped working on that
company and the Discord is still around and I still get to hang out
with cool people.&lt;/p&gt;
&lt;p&gt;This Discord is still around and hit 1,700 members recently. Among
other things, it has developers from many different database companies
in it these days. They hang out and help out the noobs like me learn
about database internals.&lt;/p&gt;
&lt;p&gt;I culled inactive members recently, so today the total is around
1,100.&lt;/p&gt;
&lt;h4 id="hacker-nights"&gt;Hacker Nights&lt;/h4&gt;&lt;p&gt;During the pandemic I became frustrated that all the good meetups
disappeared so I decided to start an online one that would be somewhat
tied to the Discord and be about software internals.&lt;/p&gt;
&lt;p&gt;I would find 2 or 3 people to present for 10-20 minutes each on
anything to do with software internals. We'd meet once a month at 8PM
NY time I think.&lt;/p&gt;
&lt;p&gt;To get speakers I'd mostly DM people who I saw do interesting things
on Twitter or Hacker News. I was lucky to have &lt;a href="https://www.philipotoole.com/"&gt;Philip
O'Toole&lt;/a&gt; (author of rqlite), &lt;a href="https://sirupsen.com/"&gt;Simon
Eskildsen&lt;/a&gt; (author of the Napkin Math blog),
&lt;a href="https://rsms.me/"&gt;Rasmus Andersson&lt;/a&gt;, and many other excellent folks
speak.&lt;/p&gt;
&lt;p&gt;You can find &lt;a href="https://www.youtube.com/playlist?list=PL2t91m2Rvccpg2q2o_8lfuTYUhoP3AMwq"&gt;videos of these talks on
YouTube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The events were organized on Meetup.com. The group grew quickly
and I'd have about 100 people RSVP to each event. 10-20 normally
showed up.&lt;/p&gt;
&lt;p&gt;I'd post a Zoom link on Meetup.com. Sometimes Meetup.com crashed right
as the meetup started, so no one could get a link. That was fun.&lt;/p&gt;
&lt;p&gt;On two different nights I had Zoom bombers show up and play crazy
music or impersonate other members of the call and act weirdly (Zoom
lets you change your name after you've joined the call).&lt;/p&gt;
&lt;p&gt;I learned a little bit about how to administrate a Zoom meeting.&lt;/p&gt;
&lt;p&gt;I ran Hacker Nights for 5 months. It was tiring to
find speakers, tiring to deal with Zoom bombers. It was thankless and
I wasn't really enjoying it.&lt;/p&gt;
&lt;p&gt;I was proud though that I was offering a channel for developers to
learn about software internals of compilers, databases, etc. And it
was great to meet many interesting speakers and attendees.&lt;/p&gt;
&lt;h3 id="2023:-designing-data-intensive-applications"&gt;2023: Designing Data Intensive Applications&lt;/h3&gt;&lt;p&gt;A month ago I put out a call on Twitter for folks in NYC interested in
reading through the book Designing Data Intensive Applications.&lt;/p&gt;
&lt;p&gt;I'd read the book before and while it was challenging, I knew it was
immensely useful to any developer who works with data or an API.&lt;/p&gt;
&lt;p&gt;By this time I'd learned my second trick: not asking for public
responses.&lt;/p&gt;
&lt;p&gt;I said something like:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Hey folks! I'm thinking of starting a book club meeting in Midtown
NYC reading through Designing Data Intensive Applications. DM me if
you'd be interested! If I get 2 other interested folks this will be on!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I got maybe 40 DMs and 20 of them were based in NYC. Attendence thus
would have been higher if I made the book club virtual. But virtual
events take about as much effort as in-person events and somehow feel
less rewarding. So I went through with the NYC group.&lt;/p&gt;
&lt;p&gt;I'm sure I could have gotten some company to provide us space, but
this would just mean more negotation for me and tedium for everyone
involved (bring your ID to be checked in, make sure you're registered,
etc.).&lt;/p&gt;
&lt;p&gt;The group would meet every 2 weeks and cover 2 chapters at a
time. We'd meet for 30 minutes. To avoid needing to find a place to
meet, we'd meet in public at Bryant Park. (There turns out to be
plenty of available seating on Fridays at 9AM in Bryant Park. When it
rains we meet online.)&lt;/p&gt;
&lt;p&gt;I wanted to keep the overhead minimal and the timeline slightly
aggressive. We'd be through the book in only 3 months. No crazy
commitment.&lt;/p&gt;
&lt;p&gt;We've meet twice now and are 25% done the book. Attendance has been
around 7 to 9 people each time so far, or a little less than 50%.&lt;/p&gt;
&lt;p&gt;They're almost all software developers, with one manager I think, who
work for a variety of large and small tech companies.&lt;/p&gt;
&lt;p&gt;I'm loving it so far. And if it continues to go well, I'll probably
continue running in-person book clubs.&lt;/p&gt;
&lt;p&gt;But it would only meet a few months a year, giving me a few month
breaks from running it.&lt;/p&gt;
&lt;h3 id="takeaways:-the-meh"&gt;Takeaways: The meh&lt;/h3&gt;&lt;p&gt;Organizing any event takes effort. Meetups are especially hard because
you need to find a place to run the meetup, you probably want to
provide food, and you need to find speakers.&lt;/p&gt;
&lt;p&gt;Often you can find a single place to host the meetup, but you have to
constantly search for new speakers. Even one of the greatest meetups
in NYC, Papers We Love, seems to be struggling to find speakers.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://db.cs.cmu.edu/seminar2023/"&gt;CMU Database Group&lt;/a&gt; and the
&lt;a href="http://charap.co/category/reading-group/"&gt;Distributed Systems Reading
Group&lt;/a&gt; seem to have the
right idea though. They only run sessions part of the year, and they
plan out all sessions in advance (including speakers).&lt;/p&gt;
&lt;p&gt;However, they are both virtual. And I'm not so interested in running
virtual events anymore.&lt;/p&gt;
&lt;h3 id="takeaways:-the-good"&gt;Takeaways: The good&lt;/h3&gt;&lt;p&gt;For one, meetups are an awesome way to meet random people and expand
your network.&lt;/p&gt;
&lt;p&gt;Two, they're educational. Even beyond the content you are meeting
about, there's the discussion alongside it you wouldn't get by
yourself. And you, as organizer, get to pick the topic.&lt;/p&gt;
&lt;p&gt;These work out great for me. I love to meet people, and I love to
learn.&lt;/p&gt;
&lt;h4 id="tricks"&gt;Tricks&lt;/h4&gt;&lt;p&gt;Starting something new is embarassing because you're putting yourself
out there. Maybe no one in your network shares your interests (to the
degree or in the direction you do).&lt;/p&gt;
&lt;p&gt;My tricks are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Most importantly: keep things low key! Don't stress people
out. Before learning and networking, the point of meetups is (or should
be) fun.&lt;/li&gt;
&lt;li&gt;Saying you are "thinking about X" is a lightweight way to gauge
interest. As compared to just saying you're "starting X", which gives
you less room to back out if there turns out not to be interest.&lt;/li&gt;
&lt;li&gt;Asking people to DM you with interest is less embarrassing than
asking for people to respond in public. Not everyone would want to
respond in public. If there's interest in private, you can share the
interest in public later on. But if you only ask for responses in
public and there are no responses, that can feel embarassing.&lt;/li&gt;
&lt;li&gt;Indicating success criteria can help people understand how big
you're thinking of. I'm normally fine with doing something as small
as only two other people, so I say that. It's kind of like how
Kickstarters work with minimum funding levels.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These ideas apply to corporate planning too. I think about them when
I'm sharing some new idea in company Slack as much as when I share on
Twitter.&lt;/p&gt;
&lt;p&gt;A note on attendance rates: 10-20% actual attendance versus RSVP seems
normal. If you get a higher percentage of people actually attending
versus RSVP-ing you're doing pretty well!&lt;/p&gt;
&lt;h4 id="finding-sponsors"&gt;Finding sponsors&lt;/h4&gt;&lt;p&gt;One final idea is about paying for space or paying for food. Companies
with space and money for food are often willing to partner with folks
willing to do the work to run an event.&lt;/p&gt;
&lt;p&gt;Running your own event in a company's space is advertising for
them. They get to be associated with cool tech. It's a chance for them
to pitch their open positions.&lt;/p&gt;
&lt;p&gt;Obviously this happens often when you start a meetup hosted by your
own company. But you can also find other companies to host space.&lt;/p&gt;
&lt;p&gt;The kind of people to find to make this happen are senior
developers or engineering managers, often on Twitter and sometimes on
LinkedIn.&lt;/p&gt;
&lt;p&gt;I haven't done this myself yet because I'm not ready to commit to
running a meetup. But I see it happen. And it's the approach I'd take
if I were to run a real meetup again.&lt;/p&gt;
&lt;p&gt;Though now that I've got some time off there are a few talks I'd like
to do myself.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Wrote a post on my experience organizing tech meetups of various stripes over the years. And a few things I've learned.&lt;br /&gt;&lt;br /&gt;&amp;quot;meetups&amp;quot; taken pretty broadly to include online communities, book clubs, and actual speaker events.&lt;a href="https://t.co/xnd0LTneup"&gt;https://t.co/xnd0LTneup&lt;/a&gt; &lt;a href="https://t.co/w1oEaSNDHb"&gt;pic.twitter.com/w1oEaSNDHb&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1698793650036031753?ref_src=twsrc%5Etfw"&gt;September 4, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 04 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/eight-years-of-tech-meetups.html</guid></item><item><title>CLI tip 21: inplace file editing with GNU awk</title><link>https://learnbyexample.github.io/tips/cli-tip-21/</link><description>&lt;p&gt;You can use the &lt;code&gt;-i&lt;/code&gt; option with &lt;code&gt;GNU awk&lt;/code&gt; to load libraries. The &lt;code&gt;inplace&lt;/code&gt; library comes by default with the &lt;code&gt;GNU awk&lt;/code&gt; installation. Thus, you can use &lt;code&gt;-i inplace&lt;/code&gt; to modify the original input itself. Make sure to test that the code is working as intended before using this option.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat table.txt
&lt;/span&gt;&lt;span&gt;brown bread mat cake &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span&gt;blue cake mug shirt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;7
&lt;/span&gt;&lt;span&gt;yellow banana window shoes &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3.14
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# retain only the first and third fields
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;i inplace &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{print $1, $3}'&lt;/span&gt;&lt;span&gt; table.txt
&lt;/span&gt;&lt;span&gt;$ cat table.txt
&lt;/span&gt;&lt;span&gt;brown mat
&lt;/span&gt;&lt;span&gt;blue mug
&lt;/span&gt;&lt;span&gt;yellow window
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can provide a backup extension by setting the &lt;code&gt;inplace::suffix&lt;/code&gt; special variable. For example, if the input file is &lt;code&gt;ip.txt&lt;/code&gt; and &lt;code&gt;inplace::suffix='.orig'&lt;/code&gt; is used, the backup file will be named as &lt;code&gt;ip.txt.orig&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat marks.txt
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Name    Physics  Maths
&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Moe  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;76  82
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Raj  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;56  64
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;i inplace &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v inplace::suffix=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'.bkp' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;OFS&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'&lt;/span&gt;&lt;span&gt; marks.txt
&lt;/span&gt;&lt;span&gt;$ cat marks.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Physics&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Maths
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Moe&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;76&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;82
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Raj&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;56&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;64
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# original file is preserved in 'marks.txt.bkp'
&lt;/span&gt;&lt;span&gt;$ cat marks.txt.bkp
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Name    Physics  Maths
&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Moe  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;76  82
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Raj  &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;56  64
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; Earlier versions of &lt;code&gt;GNU awk&lt;/code&gt; used &lt;code&gt;INPLACE_SUFFIX&lt;/code&gt; variable instead of &lt;code&gt;inplace::suffix&lt;/code&gt;. Also, you can use &lt;code&gt;inplace::enable&lt;/code&gt; variable to dynamically control whether files should be inplaced or not. See &lt;a href="https://www.gnu.org/software/gawk/manual/gawk.html#Extension-Sample-Inplace"&gt;gawk manual: Enabling In-Place File Editing&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; See &lt;a href="https://unix.stackexchange.com/q/749645/109046"&gt;this unix.stackexchange thread&lt;/a&gt; for details about security implications of using the &lt;code&gt;-i&lt;/code&gt; option and workarounds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my &lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;CLI text processing with GNU awk&lt;/a&gt; ebook if you are interested in learning about the &lt;code&gt;GNU awk&lt;/code&gt; command in more detail.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Sat, 02 Sep 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-21/</guid></item><item><title>2023–09–02: Linux repository changes</title><link>https://xnux.eu/log/#094</link><author>megi's PinePhone Development Log</author><pubDate>Sat, 02 Sep 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#094</guid></item><item><title>On Grind - from Investor to Creator to Founder</title><link>https://www.swyx.io/on-grind</link><description>&lt;p&gt;&lt;a href="https://twitter.com/saranormous/status/1696740417671442796"&gt;Sarah&lt;/a&gt; asks a provoking question that has been on my mind a lot as I transition from &lt;a href="https://www.swyx.io/part-time-creator-manifesto"&gt;part time creator&lt;/a&gt; to founder:&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 31 Aug 2023 09:21:01 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/on-grind</guid></item><item><title>Absurd Success</title><link>https://www.marginalia.nu/log/87_absurd_success/</link><description>So&amp;hellip; I&amp;rsquo;ve had the most unreal week of coding. Zero exaggeration, I&amp;rsquo;ve halved the RAM requirements of the search engine, removed the need to take the system offline during an upgrade, removed hard limits on how many documents can be indexed, and quadrupled soft limits on how many keywords can be in the corpus.
It&amp;rsquo;s been a long term goal to keep it possible to run and operate the system on low-powered hardware, and so far improvements have been made, to the point where my 32 Gb RAM developer machine feels spacey rather than cramped, but this set of changes takes it several notches further.</description><author>Weblog on marginalia.nu</author><pubDate>Wed, 30 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/87_absurd_success/</guid></item><item><title>Zürich's new 74 bus fast charging station</title><link>https://sschueller.github.io/posts/74-eletric-bus-charging-station/</link><description>&lt;div class="featured-image"&gt;
                &lt;img src="/posts/74-eletric-bus-charging-station/P_20230828_174539.jpg" /&gt;
            &lt;/div&gt;&lt;p&gt;VBZs new 74 Electric Bus charging setup.&lt;/p&gt;</description><author>Stefan Schüller</author><pubDate>Tue, 29 Aug 2023 19:00:16 GMT</pubDate><guid isPermaLink="true">https://sschueller.github.io/posts/74-eletric-bus-charging-station/</guid></item><item><title>Vim tip 31: mark frequently used locations</title><link>https://learnbyexample.github.io/tips/vim-tip-31/</link><description>&lt;p&gt;You can save frequently visited locations using marks for quicker navigation to those positions in the file. You can also pair marks with motion commands for tasks like copying, deleting, etc.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;ma&lt;/kbd&gt; mark location in the file using the alphabet &lt;code&gt;a&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;you can use any of the 26 alphabets&lt;/li&gt;
&lt;li&gt;use lowercase alphabets to work within the current file&lt;/li&gt;
&lt;li&gt;use uppercase alphabets to work from any file&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:marks&lt;/kbd&gt; will show a list of the existing marks&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;`a&lt;/kbd&gt; move to the exact location marked by &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;'a&lt;/kbd&gt; move to the first non-blank character of the line marked by &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;'A&lt;/kbd&gt; move to the first non-blank character of the line marked by &lt;code&gt;A&lt;/code&gt; (this will work for any file where the mark was set)&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;d`a&lt;/kbd&gt; delete from the current character to the character marked by &lt;code&gt;a&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;marks can be paired with any command that accept motions like &lt;code&gt;d&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; Motion commands that take you across lines (for example, &lt;kbd&gt;10G&lt;/kbd&gt;) will automatically save the location you jumped from in the default &lt;code&gt;`&lt;/code&gt; mark. You can move back to that exact location using &lt;code&gt;``&lt;/code&gt; or the first non-blank character using &lt;code&gt;'`&lt;/code&gt;. Note that the arrow and word motions aren't considered for the default mark even if they move across lines.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://vimhelp.org/motion.txt.html#mark-motions"&gt;:h mark-motions&lt;/a&gt; for more ways to use marks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 29 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-31/</guid></item><item><title>DataEngBytes 2023</title><link>https://boyter.org/posts/dataengbytes-2023/</link><description>&lt;p&gt;I was recently selected to present at &lt;a href="https://dataengconf.com.au/"&gt;DataEngBytes 2023&lt;/a&gt; after submitting an idea for a talk based on my blog post &lt;a href="https://boyter.org/posts/an-informal-survey-of-10-million-github-bitbucket-gitlab-projects/"&gt;Processing 40 TB of code from ~10 million projects with a dedicated server and Go for $100&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I think it went well, other than not having the slides able to present for the first 10 minutes. After 5 mins or so I decided to just plough on since it was less of a visual talk anyway. I did have a few people come to me afterwards and thank me for it which was nice.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Sat, 26 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/dataengbytes-2023/</guid></item><item><title>Better be first 99% of the time than second 100% of the time</title><link>https://bytepawn.com/better-be-first-99-percent-of-the-time-than-second-100-percent-of-the-time.html</link><description>&lt;p&gt;A review of the Donald MacKenzie's book &lt;em&gt;Trading at the Speed of Light&lt;/em&gt;, which gives an excellent history and inside-peek of the world of High Frequency Trading, or HFT.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Trading at the Speed of Light" src="/images/trading-at-the-speed-of-light.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 25 Aug 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/better-be-first-99-percent-of-the-time-than-second-100-percent-of-the-time.html</guid></item><item><title>Quickly make any LÖVE app programmable from within the app</title><link>http://akkartik.name/post/love-repl</link><description>&lt;p&gt;
It's a very common workflow. Type out a &lt;a href="https://love2d.org"&gt;LÖVE&lt;/a&gt;
app. Try running it. Get an error, go back to the source code.

&lt;p&gt;
How can we do this &lt;em&gt;from within the LÖVE app&lt;/em&gt;? So there's nothing to
install?

&lt;p&gt;
This is a story about a hundred lines of code that do it. I'm probably not the
first to discover the trick, but I hadn't seen it before and it feels a bit
magical.

&lt;p&gt;
&lt;a href="https://forum.malleable.systems/t/adding-malleability-to-any-love-app/90"&gt;Read more&lt;/a&gt;</description><author>Kartik Agaram</author><pubDate>Tue, 22 Aug 2023 21:51:16 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/love-repl</guid></item><item><title>Sleeping at Night</title><link>https://www.marginalia.nu/log/86-sleep/</link><description>I&amp;rsquo;ve started going on a long walk each morning immediately as I wake up, and it&amp;rsquo;s had the unexpected side-effect of fixing my broken circadian rhythm.
For decades, as long as I can remember, I&amp;rsquo;ve been what you might consider a serious night owl. Regardless of how long I slept or when I woke up, I would get nothing requiring any sort of thought done until sometime after lunch, and it wasn&amp;rsquo;t really until late at night that my brain really kicked into gear.</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 22 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/86-sleep/</guid></item><item><title>It’s not magic (part 1)</title><link>https://letterstoanewdeveloper.com/2023/08/21/its-not-magic-part-1/</link><description>Dear new developer, I remember the first time I pulled up MapQuest in the 2000s. I looked for directions to a place I&amp;#8217;d never been. A custom map popped up, which was cool. But even more impressive was the turn by turn directions. I printed them both out and followed the instructions. That felt like &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/08/21/its-not-magic-part-1/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;It&amp;#8217;s not magic (part&amp;#160;1)&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 21 Aug 2023 15:05:12 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/08/21/its-not-magic-part-1/</guid></item><item><title>CLI tip 32: text processing between two files with GNU awk</title><link>https://learnbyexample.github.io/tips/cli-tip-32/</link><description>&lt;p&gt;&lt;code&gt;awk&lt;/code&gt; is handy to compare records and fields between two or more files. The &lt;em&gt;key&lt;/em&gt; features used in the solution below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For two files as input, &lt;code&gt;NR==FNR&lt;/code&gt; will be &lt;code&gt;true&lt;/code&gt; only when the first file is being processed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next&lt;/code&gt; will skip rest of the script and fetch the next record&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a[$0]&lt;/code&gt; by itself is a valid statement. It will create an uninitialized element in array &lt;code&gt;a&lt;/code&gt; with &lt;code&gt;$0&lt;/code&gt; as the key (assuming the key doesn't exist yet)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$0 in a&lt;/code&gt; checks if the given string (&lt;code&gt;$0&lt;/code&gt; here) exists as a key in the array &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat colors_1.txt
&lt;/span&gt;&lt;span&gt;teal
&lt;/span&gt;&lt;span&gt;light blue
&lt;/span&gt;&lt;span&gt;green
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;$ cat colors_2.txt
&lt;/span&gt;&lt;span&gt;light blue
&lt;/span&gt;&lt;span&gt;black
&lt;/span&gt;&lt;span&gt;dark green
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# common lines
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'NR==FNR{a[$0]; next} $0 in a'&lt;/span&gt;&lt;span&gt; colors_1.txt colors_2.txt
&lt;/span&gt;&lt;span&gt;light blue
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# lines from colors_2.txt not present in colors_1.txt
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'NR==FNR{a[$0]; next} !($0 in a)'&lt;/span&gt;&lt;span&gt; colors_1.txt colors_2.txt
&lt;/span&gt;&lt;span&gt;black
&lt;/span&gt;&lt;span&gt;dark green
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; Note that the &lt;code&gt;NR==FNR&lt;/code&gt; logic will fail if the first file is empty, since &lt;code&gt;NR&lt;/code&gt; wouldn't get a chance to increment. You can set a flag after the first file has been processed to avoid this issue. See &lt;a href="https://unix.stackexchange.com/a/237110/109046"&gt;this unix.stackexchange thread&lt;/a&gt; for more workarounds.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# no output
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'NR==FNR{a[$0]; next} !($0 in a)' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;null &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# gives the expected output
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'!f{a[$0]; next} !($0 in a)' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;null f=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's an example of comparing specific fields instead of whole lines. When you use a &lt;code&gt;,&lt;/code&gt; separator between strings to construct the array key, the value of &lt;code&gt;SUBSEP&lt;/code&gt; is inserted. This special variable has a default value of the non-printing character &lt;code&gt;\034&lt;/code&gt; which is usually not used as part of text files.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat marks.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Dept    Name    Marks
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;ECE     Raj     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;53
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;ECE     Joel    &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;72
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;EEE     Moi     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;68
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;CSE     Surya   &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;81
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;EEE     Tia     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;59
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;ECE     Om      &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;92
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;CSE     Amy     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;67
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ cat dept_name.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;EEE Moi
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;CSE Amy
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;ECE Raj
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'NR==FNR{a[$1,$2]; next} ($1,$2) in a'&lt;/span&gt;&lt;span&gt; dept_name.txt marks.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;ECE     Raj     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;53
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;EEE     Moi     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;68
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;CSE     Amy     &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;67
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;CLI text processing with GNU awk&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 21 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-32/</guid></item><item><title>Cyberpunk 2077 review</title><link>https://burakku.com/blog/cyberpunk-2077-review/</link><description>&lt;p&gt;&lt;img alt="Cyberpunk 2077" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;My favourite game of the 2020s (so far).&lt;/p&gt;
&lt;p&gt;I've poured about 400 hours towards Cyberpunk 2077 across all the platforms but I'm still finding it quite hard to write a review of the game. Cyberpunk 2077 has famously been kind of a mess, so why have I dedicated so much of my time towards it? I'm not sure if I know the answer.&lt;/p&gt;
&lt;p&gt;I think one of the reasons why I like Cyberpunk 2077 is just the world. Night City is dense and grandiose stage that is essentially one of the main characters of the game with its overwhelming presence. There might be some issues when it comes to AI to make it appear like the city was full of real people going about their real lives akin to recent Grand Theft Auto games, it is still a sublime place to be in. I at least do not remember having as much fun exploring Los Santos in GTA V as I did when exploring the various alleys and roofs of Kabuki. Night City is called The City of Dreams and its design truly embodies it.&lt;/p&gt;
&lt;p&gt;What didn't feel that strong was traversing the world though. You can fast travel between points you have already discovered, but I used it sparingly because I actually wanted to spend time in Night City. Sprinting around the city was actually a really valid option for moving short distances and I probably used it more than you'd think. However, for longer distances, you really need some kind of a motorised device. And the car physics in Cyberpunk 2077 are not the greatest. I've definitely encountered worse driving games but this isn't Grand Theft Cyberpunk.&lt;/p&gt;
&lt;p&gt;However, I did discover the ultimate way to travel in Night City: the Yaiba Kusanagi CT-3X. While cars have a very funky handling model, zipping around on a motorcycle is actually super fun and the Kusanagi is the best of them. And in addition to being fun to drive, you can use the Kusanagi to go basically anywhere and cut through the Night City congestion. Really the only downside of the Kusanagi is that it's so much fun that it overshadows the rest of the vehicles. Kind of a shame, since the game is full of fun and cool vehicle designs. One of the cars even talks to you!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Yaiba Kusanagi CT-3X" src="kusanagi.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;While exploring that world, it also becomes pretty evident that Cyberpunk 2077 is a part of a larger franchise. There's a shocking amount of lore stuffed into the game. So much so that I imagine that only few players are actually going to go through all of it. But even if you're not going to be engaging with all of it, it still has the effect of making Cyberpunk 2077 feel like it's an actual world and not just shallow set dressing. I've definitely taken an interest in the Cyberpunk franchise after playing through 2077. If only I had some friends to play the TTRPG with.&lt;/p&gt;
&lt;p&gt;Another strength of the game is the writing. I feel like a lot of games these days have quite unnatural writing to them. My guess would be that they're trying to emulate the kind of "witty" writing that is present in current Hollywood blockbusters. Thankfully Cyberpunk 2077 doesn't feel like that even though they hired a Hollywood actor to appear in it. The dialogue seems a bit weird or otherworldly when compared to standard English though but I feel like that might be a desired effect. And I'm not referring to the specific 2077 slang words that are used. It would be kinda boring if people living in an alternative future spoke English exactly like people in our regular 2020 did.&lt;/p&gt;
&lt;p&gt;I quite enjoyed the cast of characters too. You cannot have a huge influence over the kind of a person V is but I liked her (female V forever). She also meshes well with your main sidekick that's with you for most of the game and I enjoyed the interactions between the two. There's also quite a large array of interesting secondary characters and even tertiary characters have some characterisation. The only characters that I hated, I hated because they were awful people that could only be fixed with a bullet to the skull, not because they were badly written characters. I guess the only issue I had with the characters was that the voice actors were often voicing several characters, so you might encounter a shopkeeper that sounds exactly like your girlfriend.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Johnny Silverhand" src="silverhand.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The main story of the game is good although not necessarily amazing. I also feel like it's relatively short for a single-player roleplaying game. I think a main story only playthrough will only eat about 20 hours of your time. Perhaps it was kept short to give players a chance to finish it since relatively few people ever actually complete games. Personally I think I could've done with additional fleshing of the main story. I'm not sure if the Cyberpunk 2077 story is the most interesting story possible in the Cyberpunk universe, since there is quite a lot of material available, but it was interesting enough to make me want more of it.&lt;/p&gt;
&lt;p&gt;However, to complement the relative brevity of the main story, the game has quite a lot of optional content. There's questlines for the main secondary characters, various side missions, gigs by the local fixers, cyberpsychos to subjugate and requests to "keep the peace" (neutralise targets) from the police. And the optional content is much higher quality than many other open world games where the additional content is mainly just collectibles. Some of my favourite missions in the game are in fact optional. Even the police requests, which are the lowest effort side content in the game, still have some amount of setting and variation to them. None of optional content felt like a chore or a lazy collectible like in some other games. In fact, I might take a detour while traveling from one story mission to another to just clear out a couple of NCPD murder requests.&lt;/p&gt;
&lt;p&gt;The talent system allows quite personalised ways of playing as V. For my first playthrough, I went for a pretty generic jack of all trades gunner build that didn't really strike me in any way. After that I went for a more stealthy builds, which were much more to my liking. And on my fourth playthrough, I opted for a katana-wielding Sandevistan ninja build, which I really enjoyed. Never really tried out a serious gunner build nor have I tried a netrunner build, as those didn't seem as interesting. Maybe when I decide that it's time to complete Cyberpunk 2077 for a fifth time that I might experiment with those.&lt;/p&gt;
&lt;p&gt;&lt;img alt="V" src="v.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The way that you specialise your V alters the core gameplay quite a bit, and since I've never tried out all of the possible ways to play the game, I can't comment on all of them. But I do generally like the gunplay, the melee combat and the stealth mechanics. Even though I never committed properly to be a gunner, I always found it to be satisfying enough. And I quite liked the combination of stealth and hacking abilities to be able to circumvent cameras and people. And the Sandevistan katana build was just visceral catharsis with the way you could overpower your opponents.&lt;/p&gt;
&lt;p&gt;I do find that the talent system does contribute to quite an uneven difficulty curve though. I always found the game to start off way harder than it ended, since at the start, you have so much worse gear, so much worse cybernetics and lacked all talents. When playing on the hard difficulty, it was quite easy to get yourself killed if you made any mistakes. And after you'd obtained your equipment and talents towards the end of the game, it was so much harder to die to a point of being a rare event. This is not exactly a rare effect with games built around equipment and talents, but for the sequel, I hope that CDPR flattens out the difficulty curve a bit.&lt;/p&gt;
&lt;p&gt;Beyond just trying out different sorts of builds, Cyberpunk 2077 offers pretty good replayability in general. I should know, I've played it through four times already! I'm pretty sure I've discovered something new every time I've played through the game, and not just because they've kept patching the game ever since it launched in 2020. There's always been something new to discover in the environments, some kind of a way to complete a quest that I hadn't tried before, or something completely else. And judging by the stuff I've encountered on YouTube and various websites, there's still stuff that I've never seen before. There's usually a couple of ways to complete any given quest, so you might opt to go for a guns blazing approach on your first try and for a stealthy approach on the second. The various builds also unlock new ways to approach some of the quests.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Night City" src="nightcity.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The game is gorgeous too. I think this is still the apex of gaming graphics after two years of its release, and the system requirements really match that. Cranking the settings to their highest (ignoring path tracing because it's just a technology preview), I was able to get around 80 FPS at 1440p with an RTX 4090. Thankfully you can tweak the settings for a more optimised result on weaker systems, but this is really a game that benefits from beefy hardware. The beautiful graphics really just further enhance the beauty of Night City. Soundtrack of the game is also rather good, and I find myself listening to many of the tracks outside of the game too. Never Fade Away in particular is currently topping the charts on my Last.fm profile. I also loved the voice acting for female V, and really none of the cast really disappointed me with their performance.&lt;/p&gt;
&lt;p&gt;The game isn't perfect though, as was quite widely covered when the game launched. Far from it. There were so many different glitches and bugs when the game came out that it was hard to go ten minutes without encountering something. Thankfully there's been over two and a half years of effort put towards the game to polish it, but it's still not quite enough. Luckily the effort has managed to fix the worst game-breaking issues that were present in the beginning. The kind of bugs that would prevent progress or otherwise really make you miserable. But you will definitely still encounter people continuing to walk around after being decapitated or see a car plow through a wall. Bugs that break your immersion but not your progression. I imagine that Cyberpunk 2077 will never truly be jank-free.&lt;/p&gt;
&lt;p&gt;It also feels like they've managed to fix worst of the crashing too, since I think I only encountered a single crash on my new gaming PC. With my previous rig, I remember encountering several crashes in a single session. Not sure if that was an issue with the PC/installation I had at the time or if it was just the older versions. Hopefully newcomers to the game can enjoy it with only an occasional crash – since really no AAA game on the PC is truly 100% stable.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bugs" src="bugs.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I'm not really sure how relevant my review is going to be though. I'm writing this review just a few weeks before the new expansion, Phantom Liberty, comes out and overhauls the game. I haven't actually looked at a detailed list on all of the changes because I want to experience them with fresh eyes once they come out, but the little I've seen online suggests that they're actually doing quite a lot of changes. Hopefully the changes are just for the better and don't ruin anything that I currently love in the game. But since Cyberpunk 2077 has been quite the enjoyable experience for me, I do have a level of trust in CD Projekt Red to not muck it up at this point.&lt;/p&gt;
&lt;p&gt;Cyberpunk 2077 is kind of a rough product even after its polish, but one that has always captivated me and kept me playing, even though I still have a hard time explaining why. It's a fun game with a good story and writing, and it'd be hard to not recommend such a game.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sun, 20 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/cyberpunk-2077-review/</guid></item><item><title>2023–08–20: QuartzPro 64</title><link>https://xnux.eu/log/#093</link><author>megi's PinePhone Development Log</author><pubDate>Sun, 20 Aug 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#093</guid></item><item><title>Bomb Rush Cyberfunk review</title><link>https://burakku.com/blog/bomb-rush-cyberfunk-review/</link><description>&lt;p&gt;&lt;img alt="Bomb Rush Cyberfunk" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Underwhelming spiritual successor to Jet Set Radio.&lt;/p&gt;
&lt;p&gt;While I may not be the biggest Jet Set Radio fan out there, I still find it to be a pretty fun action/platform game. And when I saw that someone was going to create a Jet Set Radio style game for the modern age, I did get quite excited. However, having dipped my toes into the game now, I feel rather disappointed.&lt;/p&gt;
&lt;p&gt;The movement in Bomb Rush Cyberfunk feels noticeably worse than what Jet Set Radio offered. And I think that's a two-pronged issue, one part being controls and one part being level design. On the control side, I had trouble getting my character to control as I wish. Feels like the characters can cut far too sharp in a direction when coming from a jump, and like it happens in an unpredictable fashion. And the control issues are not helped by the fact that the controls are more intricate than Jet Set Radio's, with many more movement possibilities. I could've taken less if the counterbalance was that it felt more intuitive to move.&lt;/p&gt;
&lt;p&gt;And the early levels feel very sparse in comparison to Jet Set Radio with large open areas without any parts for grinding or jumping. In Jet Set Radio, if you saw a ramp going upwards, you could most likely grind against it. But in Bomb Rush Cyberfunk, it feels like I was constantly trying to grind against level elements that just didn't support it. And having to second-guess what parts of the level work for movement and which don't really kills the flow. The levels really didn't feel like they were designed to allow for stylishly combining various tricks to your heart's contents as long as you had the skills. Maybe I'd learn the movement controls in due time but I'm not sure if the level design would ever allow the same kind of free-flowing movement that Jet Set Radio had.&lt;/p&gt;
&lt;p&gt;The approach to levels seems very different between the two. Jet Set Radio would throw you into a level with a map of all tag locations and big honking arrows pointing to the tags inside the game. Bomb Rush Cyberfunk meanwhile throws you into a map without any tag locations, no arrows and tells you to go spray enough of them to unlock the next objective. Jet Set Radio was more of a time attack whereas Bomb Rush Cyberfunk seems to be more of an exploration. And I don't think exploration really meshes well with the kind of movement-based gameplay associated with Jet Set Radio.&lt;/p&gt;
&lt;p&gt;After I managed to clear the first couple of reputation gates by spraying tags, the gameplay really slowed down as I was now just trying to hunt down any tags I'd missed instead of doing stylish grinds and jumps towards my next objective. The only way to tell apart done and undone tags is with this small shimmering effect on top of the sprays, which is much harder to spot in movement. The minimap would only show you the location of the reputation gate, which would angrily send you back because you hadn't done enough spraying yet. You can't even use the spray button prompt to figure out if you've done a tag, since for some reason you can spray the same tag multiple times. As far as I could tell, there was no benefit in doing so, but it was for some reason still possible.&lt;/p&gt;
&lt;p&gt;The spraying itself also makes little sense. Jet Set Radio showed you exactly what kind of joystick movements you should do, and if you screwed up the sequence of moves, you'd lose valuable spray paint and would need to go collect more. Here, you seem to be able to just wiggle the joystick around for about a second and you're done. No sense of accomplishment for a job well done, no punishment for spraying with your eyes closed. At least I was never able to screw up a single tag by just wiggling my joystick around without paying attention. You might as well just have the spraying happen automatically.&lt;/p&gt;
&lt;p&gt;Bomb Rush Cyberfunk also differs from its spiritual forefather by having a combat system. A combat system that doesn't really feel fun. You might be thrown into a stage with a handful of mob cops and one flying cop, who will fly around and then deliver you heaven's punishment from above. And the game doesn't really feel like giving you any helping hand to figuring out where this flying cop is relative to your position, making it much harder to dodge the incoming attacks. And trying to take down the flying cop is also painful due to lacking any kind of a lock-on system, which seems like something a game throwing many opponents at you at once should have. And even when you are successfully taking down your opponents, it doesn't feel satisfying. More like just some flailing around. If the combat was completely optional, maybe in the style of Mirror's Edge, I might give it a pass, but as far as I can tell, some combat sections are not optional.&lt;/p&gt;
&lt;p&gt;The combat sections also seem to suffer from the same lack of direction as the spraying part of the game. I got thrown into a combat section, and I didn't really know what was my objective. Should I just outrun and escape the cops? Should I beat up every single cop coming my way? Or should I just focus on the flying cop that was in the cutscene? Once again, the only direction that the game seemed to be telling me was "go earn more reputation". Guidance that was not really helpful when I was being hunted by the jetpack police.&lt;/p&gt;
&lt;p&gt;The game does very much pull off the Jet Set Radio style. It does very much look like it was just another Jet Set Radio game released on the Dreamcast, for better and for worse. But while the graphics do look old, it is a very functional design. That was probably to be expected considering how well Jet Set Radio has aged. I might not like the levels from a gameplay perspective, I did enjoy them from an aesthetics perspective. Just maybe if they didn't feel so empty. Soundtrack seemed fine too, although it doesn't rival Jet Set Radio in the sound department. Then again, really no game can. Jet Set Radio's soundtrack was a just succession of bangers, whereas Bomb Rush Cyberfunk has good songs and then it has less good songs.&lt;/p&gt;
&lt;p&gt;I really would've liked to love this game, but I don't think I can as it is. For the 40-euro price tag, it feels pretty underwhelming and undercooked. Some of the issues I had with the game could probably be improved, but I'm not sure if the core gameplay could be improved to a degree that made this feel like worth its price tag. Perhaps once it hits the bargain bin, I might give it another chance. But right now Bomb Rush Cyberfunk is unable to meet my expectations and I opted to refund my copy of the game instead. Not sure if I've ever opted to do that on Steam before. A shame.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sat, 19 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/bomb-rush-cyberfunk-review/</guid></item><item><title>Portal with RTX review</title><link>https://burakku.com/blog/portal-with-rtx-review/</link><description>&lt;p&gt;&lt;img alt="Portal with RTX" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;This thing is horrendous.&lt;/p&gt;
&lt;p&gt;I'd heard that Portal with RTX didn't run great, but I thought that when people said that, it meant that their RTX 3060 couldn't run the game. Thankfully, I have an RTX 4090, so I can just brute force through any bad optimisations. What I didn't expect was that the game runs like dogshit on whatever piece of technology you throw at it.&lt;/p&gt;
&lt;p&gt;I launched the game and alt-tabbed before I even loaded in the first stage. The game crashed.&lt;/p&gt;
&lt;p&gt;Relaunched the game and cranked the graphical settings. The game crashed.&lt;/p&gt;
&lt;p&gt;Started the game for the third time, played through enough to get the half-powered portal gun and a bit more, and then alt-tabbed. The game crashed.&lt;/p&gt;
&lt;p&gt;This was all within 10 minutes of playing. I cannot remember if I have ever had a game behave this badly on me before.&lt;/p&gt;
&lt;p&gt;My 1% FPS during the start of the game was also around ten. With a 7800X3D and a 4090. I could run maxed out Cyberpunk 2077 without DLSS smoother than this game. If this kind of hardware cannot run this game without massive stuttering, there is surely no gaming PC on this planet that can run this game smoothly.&lt;/p&gt;
&lt;p&gt;It's also not decisively better graphically, even though that's the only reason for this thing to exist. The environment lighting does look more natural than the 2007 version, but it still very much looks like a PlayStation 3 era game. They did change some assets but not everything, so you are still seeing 2007 quality, just with improved lighting. I guess the improvements are nice enough that were this a working product that I would do my Portal replay in this version. But considering how busted this version is, it's so not worth it to even try.&lt;/p&gt;
&lt;p&gt;Thankfully this is just a free tech demo. Had they charged me even 50 cents for this experience, I would've asked for a refund. The only valid use for this game is to generate screenshots and video clips for Nvidia marketing material. Otherwise it's just an embarrassing and damaging showcase for their RTX Remix.&lt;/p&gt;</description><author>ブラック</author><pubDate>Fri, 18 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/portal-with-rtx-review/</guid></item><item><title>250k downloads of Latent Space Pod</title><link>https://www.swyx.io/latent-space-250k</link><description>&lt;p&gt;I last talked about my Latent Space adventures in &lt;a href="https://www.swyx.io/ranking-1-on-hn-in-mid-april"&gt;April&lt;/a&gt; and &lt;a href="https://www.swyx.io/lspace-2022"&gt;last December&lt;/a&gt;. Even as a well regarded developer-&lt;a href="https://www.swyx.io/part-time-creator-manifesto"&gt;part-time-creator&lt;/a&gt;, the Latent Space Newsletter + Pod has done much better than I usually do. Here are the stats as of today:&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 17 Aug 2023 10:24:44 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/latent-space-250k</guid></item><item><title>DEATHLOOP review</title><link>https://burakku.com/blog/deathloop-review/</link><description>&lt;p&gt;&lt;img alt="DEATHLOOP" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;A first-person game about trying to break out of a time loop.&lt;/p&gt;
&lt;p&gt;When DEATHLOOP was first shown to public audiences, I figured that it'd be a shooter game focused around on a time loop. And while it does involve both of those things, it's not really that. It's more of a repeating exploration game, where you go around in circles, collecting clues so that you can solve the temporal puzzle and bust your way out of the titular loop.&lt;/p&gt;
&lt;p&gt;After laying down the premise and teaching you your basic abilities in a linear sequence of gameplay, the game becomes very non-linear by giving you some clues and telling you go to figure out how to solve things. It feels a bit daunting at first, and I waffled around exploring unnecessarily a bit before I understood that I could really just ignore everything but the visionary clues and I'd be golden. Once you understand to just follow those, the game doesn't feel like you're just supposed to explore around and take a stab in the dark. Trying to just blindly explore your way out of it is just gonna bury you in information. I still have absolutely no idea what I was supposed to do with some of the non-plot discoveries I made.&lt;/p&gt;
&lt;p&gt;The story isn't really much anything. There's some exposition about what happened previously to begin the loop, but it's not really important and the game doesn't put much effort into it. Well, that or I just couldn't find the scraps of paper and tape recorders that explain the backstory. The few real revelations of the story also took place right before the end for me, since I guess I just played the non-linear story in that order. The meat of the game's story is really just the clues themselves, and not much anything. Granted, the overall puzzle is an enjoyable mystery but it's not a deep and involved plot. The ending is kinda weird and abrupt too.&lt;/p&gt;
&lt;p&gt;The cast of characters is mainly you and the villains that you need to kill, with one villain being particularly chatty for plot reasons. The less chatty villains are basically all just massive unlikable narcissists for you to hate. This is probably for the best, since if you actually plan on finishing the game, you'll probably have a hard time counting how many times you've killed them. I don't want to claim that they're completely shallow or undeveloped, but quite a lot of them never really registered to me as actual people. More like objectives with legs. And the only non-villain character left over is your player character, who is actually a pretty distinct character and all around fun fella with a penchant for talking to himself.&lt;/p&gt;
&lt;p&gt;For exploration, the main activity of the game, there's four different maps and four different times of day when you can go into them. And while the maps themselves don't really change, the intel you can gather out of them changes with the time of day. I don't think a fresh player can beat this game without coming and going to every single map so much that they should just install a revolving door. And I can't say that I'm really a fan of this. It just kinda feels like lazy level design to make us pour through the same four, relatively small maps with a fine comb. The maps really just aren't that big so you'll definitely get intimate with them in 5–10 hours of gameplay.&lt;/p&gt;
&lt;p&gt;The gunplay in DEATHLOOP doesn't feel amazing. It's fine but not really anything more of that. I think that's because the game actually wants you to stealth through everything. The game does actually suggest to you that you're free to go through the maps as you please, including the possibility of going guns blazing, but the way things blow up in your face if you use firearms suggests that they would really prefer you to sneak around instead. And with the gunplay being what it is, it never really felt like "time for action, baby" as much as it felt like "oh, I fucked up, can I get out of this with these guns and the couple of bullets left in my pocket?" Also felt kinda weird that the magic powers you can get in the game seem very combat-focused when that's really second fiddle to stealth.&lt;/p&gt;
&lt;p&gt;Much like the gunplay, stealth gameplay is "fine". It's not the best nor the worst stealth experience I've had in a game. My chance to go by undetected did get significantly better when I got access to the silenced SMG and I actually had the possibility of deleting enemies without groping their butt. Actually, the silenced SMG is so good that a more pro-stealth player might consider it overpowered, since it allows for insta-kills via precise headshots, and if you fuck up, silent rapid fire onto an enemy until it stops moving. Supposedly there were other silenced weapons to also find, but I didn't really need anything more than the SMG.&lt;/p&gt;
&lt;p&gt;What actually felt a lot more satisfying than sneaking around was taking the high ground with a sniper rifle and taking potshots at the enemies from the distance. Position yourself far away enough and they won't be able to hear the gunshots, leaving them unable to hunt you down and helpless to protect their tender little heads. It also feels more in character as a big burly black ex-military dude to snipe down your enemies than try to sneak your way around them.&lt;/p&gt;
&lt;p&gt;&lt;img alt="I have the high ground." src="boom.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Occasionally the chatty villain also decides to invade the map you're in. The first time this happened, I flew into panic since I had no idea where I was getting shot at and had a hard time fighting her off. Pretty quickly after that I started to figure out her patterns and it became more of an annoyance than a massive threat. I even managed to sneak around her with invisibility, turn off my invisibility for a bit to lure her in, then go invisible again to knife her in the back for some sweet insta-kill revenge. You also need to go do some busywork to unlock your escape route after this invasion, so that really just doubled the annoyance of it all. Not really a big fan of this particular feature.&lt;/p&gt;
&lt;p&gt;There's also a multiplayer aspect where you can play as the invader, but I never touched it. I want to play a single-player campaign that I can put on pause if I needed to go pee. Thankfully opting out of the multiplayer aspect is very much possible, although I believe it did automatically start off in online mode.&lt;/p&gt;
&lt;p&gt;Possibly my favourite part of the game is just the presentation. DEATHLOOP is dripping with this cartoony 1960s vibe and it's fantastic. The sound design is also great. The stealth music is very good at building a tense atmosphere, and the shift in music when you go from stealth to combat, or from combat to stealth, also works well to indicate your status, so it also works from a gameplay perspective. Colt's voice acting is also good, although I'm not so sure about the rest of the voice acting. It at least didn't feel as strong. But the overall presentation of the game is still to its benefit.&lt;/p&gt;
&lt;p&gt;Technically the game seems sound. I get pretty low system utilisation when running max settings at 1440p with a 240 FPS cap, so I assume that this game runs pretty fine on low-end hardware as well. Not a lot of bugs either, although I did manage to softlock several times in the inventory screen, and a couple of times an enemy body ragdolled wildly into space after I've killed them. I also read somewhere that the loading times were tied to your FPS. Can't really confirm that claim, but if that's true, then it sounds kinda bad. Thankfully my loading times were pretty decent with a 7800X3D, an RTX 4090 and a fast SSD. Overall, I don't have a lot of things to gripe about the technical side – a lot of games are put out in far worse state.&lt;/p&gt;
&lt;p&gt;DEATHLOOP is kind of a mixed bag for me. The story didn't feel that special and the gameplay didn't feel that special either. The time loop mechanic, even though it was an interesting concept, didn't really make the game feel that special either. But despite none of the aspects of the game really grabbing me, I still felt compelled to pour about 23 hours into the game to solve the puzzle and reach the ending, so I guess DEATHLOOP was doing something right. I'd give the game a cautious recommendation if you enjoy exploration and stealth, but I wouldn't say that it's a must play title.&lt;/p&gt;</description><author>ブラック</author><pubDate>Wed, 16 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/deathloop-review/</guid></item><item><title>Python tip 32: positive lookarounds</title><link>https://learnbyexample.github.io/tips/python-tip-32/</link><description>&lt;p&gt;Lookarounds help to create custom anchors and add conditions within a regex definition. These assertions are also known as &lt;strong&gt;zero-width patterns&lt;/strong&gt; because they add restrictions similar to anchors and are not part of the matched portions. Negative lookarounds were discussed in &lt;a href="https://learnbyexample.github.io/tips/python-tip-29/"&gt;this post&lt;/a&gt;. The syntax for positive lookarounds is shown below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(?=pat)&lt;/code&gt; positive lookahead assertion&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(?&amp;lt;=pat)&lt;/code&gt; positive lookbehind assertion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some examples:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;s &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 apple-5, fig3; x-83, y-20: f12'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# extract digits only if it is followed by ,
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note that end of string doesn't qualify as this is a positive assertion
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;,)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, s)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'5'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'83'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# extract digits only if it is preceded by - and followed by ; or :
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;-)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?=[:;]&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, s)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'20'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace 'par' as long as 'part' occurs as a whole word later in the line
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;par(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?=.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*\b&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;part&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[&lt;/span&gt;&lt;span style="text-decoration: underline; font-style: italic; color: #d2a8a1;"&gt;\g&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;lt;0&amp;gt;]'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'par spare part party'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[par] s[par]e part party'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With lookbehind assertion (both positive and negative), the pattern used for the assertion cannot &lt;em&gt;imply&lt;/em&gt; matching variable length of text. Fixed length quantifier is allowed. Different length alternations are not allowed, even if the individual alternations are of fixed length.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;s &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'pore42 tar3 dare7 care5'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# not allowed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;tar&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;dare)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, s)
&lt;/span&gt;&lt;span&gt;re.error: look&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;behind requires fixed&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;width pattern
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# workaround for r'(?&amp;lt;!tar|dare)\d+'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;!&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;tar)(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;!&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;dare)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, s)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'5'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# workaround for r'(?&amp;lt;=tar|dare)\d+'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(?:(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;tar)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;=&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;dare))&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, s)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'3'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'7'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; The third-party &lt;code&gt;regex&lt;/code&gt; module (&lt;a href="https://pypi.org/project/regex/"&gt;https://pypi.org/project/regex/&lt;/a&gt;) offers advanced features like variable-length lookbehinds, subexpression calls, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; and &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebooks.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 16 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-32/</guid></item><item><title>Working Backwards Book Summary</title><link>https://svedic.org/economy/working-backwards-book-summary</link><description>Why read the Working Backwards book? Amazon is not very popular right now, with many media criticizing its treatment of workers and its cut-throat culture. Still, love them or hate them, there are some undeniable achievements. Amazon is a tech &amp;#8230; &lt;a href="https://svedic.org/economy/working-backwards-book-summary"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Svedic.org</author><pubDate>Tue, 15 Aug 2023 14:39:15 GMT</pubDate><guid isPermaLink="true">https://svedic.org/economy/working-backwards-book-summary</guid></item><item><title>Thinking about functional programming</title><link>http://notes.eatonphil.com/2023-08-15-thinking-about-functional-programming.html</link><description>&lt;p&gt;Someone on Discord asked about how to learn functional programming.&lt;/p&gt;
&lt;p&gt;The question and my initial tweet on the subject prompted an
interesting
&lt;a href="https://twitter.com/ShriramKMurthi/status/1691548254331092992"&gt;discussion&lt;/a&gt;
with &lt;a href="https://twitter.com/ShriramKMurthi"&gt;Shriram
Krishnamurthi&lt;/a&gt; and other folks.&lt;/p&gt;
&lt;p&gt;So here's a slightly more thought out exploration.&lt;/p&gt;
&lt;p&gt;And just for backstory sake: I spent a few years a while
ago &lt;a href="https://github.com/eatonphil/ponyo"&gt;programming in
Standard ML&lt;/a&gt; and I wrote
a &lt;a href="https://github.com/eatonphil/bsdscheme"&gt;chunk of a Scheme
  implementation&lt;/a&gt;. I'm not an expert, but I have a bit of background.&lt;/p&gt;
&lt;p&gt;Hey, this is a free opinion.&lt;/p&gt;
&lt;h3 id="concepts-from-functional-programming"&gt;Concepts from functional programming&lt;/h3&gt;&lt;p&gt;When people talk about functional programming, I think of a few key
choices you can make while programming:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Immutability by default&lt;/li&gt;
&lt;li&gt;(Tail) recursion by default&lt;/li&gt;
&lt;li&gt;First-class functions (and the suite of tools that go along with it. e.g. map, reduce/fold)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if you have experience as a programmer, you either get the basic
gist of these tenets or you can easily read about the basics.&lt;/p&gt;
&lt;p&gt;That is, while most programmers I've met understood the basics, most
programmers I've met were not particularly &lt;em&gt;comfortable&lt;/em&gt; or &lt;em&gt;fluent&lt;/em&gt;
expressing programs with these ideas.&lt;/p&gt;
&lt;p&gt;For myself, the only way I got comfortable expressing code with these
ideas was lots of practice (as I mentioned above). And yet,
even after I did a bunch of programming in Standard ML and Scheme, I
really didn't see a particular benefit to practicing in a language
other than one with which I wa already generally comfortable.&lt;/p&gt;
&lt;p&gt;You have to learn a lot of other random things when you pick up Scheme
or Standard ML that aren't just: practice immutability by default,
recursion, and first-class functions.&lt;/p&gt;
&lt;p&gt;So I think it's kind of misguided when person A asks how to learn
functional programming and person B responds that they should learn
Haskell or OCaml or whatever. I see this happen pretty often online.&lt;/p&gt;
&lt;p class="note"&gt;
  Beyond any "language for functional programming" as a recommendation
  in general, Haskell is a particularly egregious suggestion to make
  in my opinion because not only are you trying to practice functional
  programming tenets but you're also dealing with a complex type
  system and lazy evaluation.
&lt;/p&gt;&lt;p&gt;Instead, &lt;a href="https://notes.eatonphil.com/practicing-recursion.html"&gt;practice immutability, recursion,
map/reduce&lt;/a&gt; in
whatever language you like.&lt;/p&gt;
&lt;h3 id="programming-languages"&gt;Programming languages&lt;/h3&gt;&lt;p&gt;If you want to study programming languages, that's awesome. However,
functional programming doesn't really have any direct connection to
studying programming languages.&lt;/p&gt;
&lt;p&gt;Languages are all over the place. Scheme, Standard ML, and Haskell are
worlds apart, even within the functional programming family.&lt;/p&gt;
&lt;p&gt;And modern languages have mostly adapted the aspects of functional
programming that used to be unique 20 years ago.&lt;/p&gt;
&lt;p&gt;Moreover, there are many other worthwhile families of languages to
learn about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Imperative/C-like (ok, you probably already know these)&lt;/li&gt;
&lt;li&gt;Stack-based (JVM, x86 assembly sort of, Forth)&lt;/li&gt;
&lt;li&gt;Array-oriented (APL, J)&lt;/li&gt;
&lt;li&gt;Declarative (CSS, SQL, TLA+, Prolog)&lt;/li&gt;
&lt;li&gt;Data (HTML, JSON, YAML)&lt;/li&gt;
&lt;li&gt;Proof assistants (Isabelle/HOL, Coq)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The list isn't exhaustive, and the variations within families can be
massive. But the point is that functional programming doesn't mean
crazy programming languages or crazy programming ideas. Functional
programming is a &lt;em&gt;subset&lt;/em&gt; of crazy programming languages and crazy
programming ideas.&lt;/p&gt;
&lt;p&gt;If you want to learn about crazy programming languages and crazy
programming ideas, you should! Go for it!&lt;/p&gt;
&lt;h3 id="introduction-to-computer-science"&gt;Introduction to Computer Science&lt;/h3&gt;&lt;p&gt;SICP is famous as the (former) introductory textbook for computer
science at MIT, and for its use of Scheme and the &lt;a href="https://en.wikipedia.org/wiki/Meta-circular_evaluator"&gt;Metacircular
Evaluator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I don't have any experience teaching beginners how to program so I
don't have thoughts on if this made sense. That's for folks like
&lt;a href="https://twitter.com/ShriramKMurthi/status/1691548254331092992"&gt;Shriram&lt;/a&gt;
to think about.&lt;/p&gt;
&lt;p&gt;However, I'm a half-decent programmer and I can't make it through this
book. If you liked the book or want to read it, that's great! But I &lt;a href="https://notes.eatonphil.com/recommending-a-book.html"&gt;don't
recommend&lt;/a&gt; it to
anyone.&lt;/p&gt;
&lt;p&gt;And many introductory Computer Science textbooks just don't make much
sense to give to experienced programmers. For an experienced
programmer, they can be quite slow!&lt;/p&gt;
&lt;p&gt;Most of the folks I see asking about how to learn functional
programming are experienced programmers.&lt;/p&gt;
&lt;h3 id="do-whatever-you-feel-like-doing"&gt;Do whatever you feel like doing&lt;/h3&gt;&lt;p&gt;I don't mean to overanalyze things, or get you overanalyzing
things. If you want to learn functional programming by writing
Haskell, that's awesome, you should go for it.&lt;/p&gt;
&lt;p&gt;Wanting to do something is basically the best motivation there is.&lt;/p&gt;
&lt;p&gt;The only reason I write this sort of post is so that folks who think
that using Haskell or Standard ML or Scheme or reading SICP is the
only way to learn functional programming see those ideas aren't
necessarily true.&lt;/p&gt;
&lt;h3 id="write-a-scheme!"&gt;Write a Scheme!&lt;/h3&gt;&lt;p&gt;Finally, for folks with time and motivation wanting to seriously work
out their functional programming muscles, writing a Scheme
implementation with a decent chunk of the standard library can be an
immensely enjoyable project.&lt;/p&gt;
&lt;p&gt;You'll learn a lot about languages and compilers and algorithms and
data structures. It's leetcode with meaning.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Wrote a short post on this idea about different things to think about when talking about learning functional programming&lt;br /&gt;&lt;br /&gt;1. Core concepts (immutability, first-class functions, recursion)&lt;br /&gt;&lt;br /&gt;2. Exploring programming languages&lt;br /&gt;&lt;br /&gt;3. Teaching CS to students&lt;a href="https://t.co/k4LzvnHbNs"&gt;https://t.co/k4LzvnHbNs&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1691617741764018430?ref_src=twsrc%5Etfw"&gt;August 16, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 15 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-08-15-thinking-about-functional-programming.html</guid></item><item><title>Free Interior Design With Sweet Home 3D and SketchUp</title><link>https://svedic.org/personal/free-interior-design-with-sweet-home-3d-and-sketchup</link><description>If you need a do-it-yourself interior design for your flat, house, basement, or dictator mansion—with completely free software—you are at the right place. Below is my experience with choosing software, finding 3D furniture models, finding textures, rendering, and furnishing the &amp;#8230; &lt;a href="https://svedic.org/personal/free-interior-design-with-sweet-home-3d-and-sketchup"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Svedic.org</author><pubDate>Sat, 12 Aug 2023 19:39:26 GMT</pubDate><guid isPermaLink="true">https://svedic.org/personal/free-interior-design-with-sweet-home-3d-and-sketchup</guid></item><item><title>Life in 1080p</title><link>https://www.marginalia.nu/log/84_life_in_1080p/</link><description>I recently got a smaller computer screen. It&amp;rsquo;s actually not that small, it&amp;rsquo;s 27&amp;quot;, but the resolution is modest compared to what is available. And in short, it&amp;rsquo;s fantastic. It&amp;rsquo;s not an expensive screen, it&amp;rsquo;s not a fancy screen; but it&amp;rsquo;s comparatively a small screen.
For a few years I was using a 34&amp;quot; ultra-wide monitor, which has been causing me nothing but grief. It&amp;rsquo;s sort of crept up on me that so many small annoyances in my computer-use all originated from using this screen.</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 12 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/84_life_in_1080p/</guid></item><item><title>Message Queues, State Machines, Actors, UI</title><link>https://www.marginalia.nu/log/85-mq_sm_actor_ui/</link><description>This is a bit of an what I&amp;rsquo;ve been working on style of post. It&amp;rsquo;s also a bit of a complement for the release notes of the upcoming release which should be dropping in a week or so. There&amp;rsquo;s some spit and polish still missing from these things, but if I don&amp;rsquo;t write about them now too much will have been ejected from the cache to make a well written post about it.</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 12 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/85-mq_sm_actor_ui/</guid></item><item><title>Releasing my tools under the MIT License was probably a mistake</title><link>https://donatstudios.com/License-Grumbles</link><description>&lt;p&gt;For basically as long as I can remember, I have been a staunch defender of permissive licenses over the more copyleft variety. I &lt;em&gt;want&lt;/em&gt; you to use things I've written. On top of that I don't believe it's my place to force you to then open source things you have written that expand upon my source code. Hence my extensive use of the MIT License.&lt;/p&gt;
&lt;p&gt;The MIT License is very simple. It basically says you can do whatever you want with the code, but you have to include the license and copyright when redistributing the code itself. It does not however require you to open source any modifications you make to the code, nor provide attribution to the non-code products of the work.&lt;/p&gt;
&lt;p&gt;With my libraries, this has served me very well. It's great, people use and contribute fixes back, and often donate back to me. I love it. I want you to use my code in your proprietary software. I open sourced it so you could do that. I specifically build open source libraries because I solved a problem I thought other people might face and wanted to save them time. Lifting the sea for all boats and all that. I have zero problem continuing to use the MIT License on my libraries.&lt;/p&gt;
&lt;p&gt;I also open sourced the tools I host on my website, including but not limited to &lt;a href="https://donatstudios.com/PixelCircleGenerator"&gt;Pixel Circle / Oval Generator&lt;/a&gt; and &lt;a href="https://donatstudios.com/RewriteRule_Generator"&gt;Batch RewriteRule Generator&lt;/a&gt;. The MIT License seemed like the natural choice because I've been using it for everything I've released since the early 2000's.&lt;/p&gt;
&lt;p&gt;I'd considered these as just &lt;em&gt;part of my website&lt;/em&gt; far more so than something I thought people would reuse. My intentions open sourcing these tools, unlike my libraries, was to promote the community helping me to improve them - and they certainly have done so. I have gotten so much great feedback and contributions to these tools, and I am very happy with that. Massive shout out to &lt;a href="https://github.com/Tyson453"&gt;Tyson453&lt;/a&gt; for his insanely awesome work improving the speed of the Pixel Circle / Oval Generator.&lt;/p&gt;
&lt;p&gt;Against my well meaning intentions however, websites re-hosting my tools have been popping up like weeds. In some cases, they are even beating me in search results for my own tools. With noted exception, they don't credit me as the author or provide any sort of link back. Many of them have made minor or major modifications to the tools, and next to none provide the source to those modifications. A number of them even have the gall to post links advertising them in the comments of my own tools.&lt;/p&gt;
&lt;p&gt;Most irksome of all, in a fair number of cases they sit centrally on pages covered in ads and SEO keywords. My tools are being associated with a genuinely bad user experience.&lt;/p&gt;
&lt;p&gt;This is all perfectly legal and allowed under the MIT License. That's on me. It just is not in the spirit of what I intended. I didn't have the foresight to see this coming. I didn't think people so lacked in the spirit of open source. I wanted to promote community contributions, not to have them monetized by other people who &lt;strong&gt;don't even provide the source to their modifications&lt;/strong&gt;. I wanted to grow the tools as a community, not have closed source forks of them overtake my own open source versions.&lt;/p&gt;
&lt;p&gt;There's not a lot I can do. I could try to contact the owners of these sites and ask them to provide attribution, but there's no obligation for them to do so. I could try to contact the owners of these sites and ask them to provide the source to their modifications, but again there is no obligation for them to do so. I really wish to see the changes shared alike, and the vain part of me would like attribution as the original author. At the end of the day though I chose the MIT License and it is what it is. It simply does not require any of that.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;On top of everything else, until very recently and for completely unclear reasons, my website Donat Studios was completely blocked from Bing search results. This in turn meant I didn't show up in DuckDuckGo or Yahoo results either. This meant that for a time the only results when searching these sites for my tools were these re-hosted versions.&lt;/p&gt;
&lt;p&gt;I actually managed to reach a human at Bing and get this resolved, but it took a lot of work and I'm still not sure why it happened in the first place.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I am considering relicensing my tools under some sort of Attribution-ShareAlike license similar to the &lt;a href="https://creativecommons.org/licenses/by-sa/3.0/"&gt;BY-SA&lt;/a&gt; the content on this site is licensed under. It would not apply to current and past versions of the tools as that code is licensed MIT and you can't un-ring that bell. It would however apply to future versions. This would still promote community contributions, but would also require that any modifications be shared alike meaning that the modified source would have to be provided and I would have to be credited as the original author.&lt;/p&gt;
&lt;p&gt;At the very least I will definitely think twice about the license I choose for future tools. I don't want to be a jerk about it, but I also don't want to see them monetized by others without even a link back to my website.&lt;/p&gt;</description><author>Donat Studios</author><pubDate>Fri, 11 Aug 2023 08:15:30 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/License-Grumbles</guid></item><item><title>Update on minimalism</title><link>https://blog.bayindirh.io/blog/update-on-minimalism/</link><description>&lt;p&gt;As a part of my minimalism journey, I'm trying to reduce my digital footprint. This means I'm using less services, less devices, less applications. In other words, trying to do more, with less.&lt;/p&gt;
&lt;p&gt;This concerted effort has created a set of dynamics and side-effects which I didn't anticipate. While it feels strange, the resulting outcome is nice.&lt;/p&gt;
&lt;p&gt;Before embarking on this journey, I used to have a desktop computer with lots of disk capacity, a nice sound system, and a nice monitor. My laptop computer was a mobile replacement of it, carrying the most essential things, plus the whole development toolchain I used to have on my desktop computer. As a result, I used my desktop computer almost all the time, while using the laptop while on the go.&lt;/p&gt;
&lt;p&gt;Now, I have a single laptop computer with a much smaller disk. My office provides me a desktop and another laptop. This means, I am using my laptop much more, and need to adjust to its capabilities and limitations.&lt;/p&gt;
&lt;p&gt;Talking of adjusting, it turned out that my desktop computer was doing much more than I anticipated. Since it had more than enough storage and I used it almost everyday, I never felt the need for the cloud. Secondly, I unknowingly created and perfected workflows around that machine. Removing it created a strange void and disorganization on its wake. I'm still working on adapting these workflows to what I have. This part is not going fast.&lt;/p&gt;
&lt;p&gt;Another part of this adjusting came from the applications I used to use on the desktop computer, especially for my photography and music collections. Over the years, I have chosen a set of applications and used them to their limits, so that I know their behavior like my name. Losing these applications would be bad, however the most important ones are working on macOS. This saved me a ton of time both in the short and long time.&lt;/p&gt;
&lt;p&gt;The last part of this adjustment process is handling and organizing massive amount of files coming from decades of computing history. These files are currently being moved to a cloud provider. After they are consolidated there, they will be organized slowly. Some of these files will be live on offline storage (i.e. portable external hard drives), some will stay in the cloud, and some will be deleted. Remaining ones will be backed up regardless of their location.&lt;/p&gt;
&lt;p&gt;It becomes clearer that I have deployed myself an "invisible infrastructure" over the years, without even noticing, as I change the way I accomplish things. Currently, almost none of this infrastructure is in place, but a better, smaller yet more efficient version is being built step by step. Let's see how it goes and where it arrives.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Fri, 11 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/update-on-minimalism/</guid></item><item><title>How to allow popups in dynamically created webviews in Electron.js</title><link>https://www.swyx.io/electron-webview-popups</link><description>&lt;p&gt;My &lt;a href="https://github.com/smol-ai/menubar"&gt;smol menubar&lt;/a&gt; project utilizes Electron's special &lt;a href="https://www.electronjs.org/docs/latest/api/webview-tag"&gt;webview tag&lt;/a&gt; to dynamically generate a list of sub browser windows for chat. For the last couple months I've had an issue with the SSO popups in this, namely that they just don't work at all, presumably because Electron blocks popups for you by default.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Wed, 09 Aug 2023 10:41:27 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/electron-webview-popups</guid></item><item><title>Vim tip 30: some general Vim settings</title><link>https://learnbyexample.github.io/tips/vim-tip-30/</link><description>&lt;p&gt;Here are some general Vim settings that you can put in the &lt;code&gt;vimrc&lt;/code&gt; file to customize your editor. See &lt;a href="https://vimhelp.org/options.txt.html"&gt;:h options.txt&lt;/a&gt; for complete reference.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;set history=200&lt;/kbd&gt; increase default history from 50 to 200
&lt;ul&gt;
&lt;li&gt;there are separate history lists for &lt;code&gt;:&lt;/code&gt; commands, search patterns, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set nobackup&lt;/kbd&gt; disable backup files&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set noswapfile&lt;/kbd&gt; disable swap files&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;colorscheme murphy&lt;/kbd&gt; a dark theme
&lt;ul&gt;
&lt;li&gt;you can use &lt;kbd&gt;:colorscheme&lt;/kbd&gt; followed by a space and then press &lt;kbd&gt;Tab&lt;/kbd&gt; or &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;d&lt;/kbd&gt; to get a list of the available color schemes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set showcmd&lt;/kbd&gt; show partial Normal mode command on Command-line and character/line/block-selection for Visual mode&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;set wildmode=longest,list,full&lt;/kbd&gt; use Bash-like tab completion
&lt;ul&gt;
&lt;li&gt;first tab will complete as much as possible&lt;/li&gt;
&lt;li&gt;second tab will provide a list&lt;/li&gt;
&lt;li&gt;third and subsequent tabs will cycle through the completion options&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; &lt;kbd&gt;:h 'history'&lt;/kbd&gt; will give you the documentation for the given option (note the use of single quotes).&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; You can use these settings from the Command-line mode as well, but will be active for the current Vim session only. Settings specified in the &lt;code&gt;vimrc&lt;/code&gt; file will be loaded automatically at startup.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 08 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-30/</guid></item><item><title>The Benefits of Sticking Around</title><link>https://letterstoanewdeveloper.com/2023/08/07/the-benefits-of-sticking-around/</link><description>Dear new developer, Over the last twenty years, I&amp;#8217;ve had two work modes. I&amp;#8217;ve either been at a company for a number of years (3, 8, 2, and 3) or been a contractor/consultant working for clients for weeks or months. Both modes have upsides; I wrote before about the benefits of a small consulting company. &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/08/07/the-benefits-of-sticking-around/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;The Benefits of Sticking&amp;#160;Around&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 07 Aug 2023 17:33:00 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/08/07/the-benefits-of-sticking-around/</guid></item><item><title>The Legacy of Bram Moolenaar</title><link>https://j11g.com/2023/08/07/the-legacy-of-bram-moolenaar/</link><description>This weekend we learned that Bram Moolenaar had passed away at the age of 62. And this news affected me more than I expected. Like so many: I did not know Bram personally. But I&amp;#8217;ve been using a tool made by Bram for more than half my life — at least weekly, sometimes daily. That [&amp;#8230;]</description><author>Jan van den Berg</author><pubDate>Mon, 07 Aug 2023 09:29:16 GMT</pubDate><guid isPermaLink="true">https://j11g.com/2023/08/07/the-legacy-of-bram-moolenaar/</guid></item><item><title>Walk code repositories respecting .gitignore files in Go</title><link>https://boyter.org/posts/walk-code-respecting-gitignore-go/</link><description>&lt;p&gt;Since I maintain a few projects that deal with source code one of the things I needed badly was a way to parse and understand gits .gitignore and .ignore files in order to get as much accuracy as possible.&lt;/p&gt;
&lt;p&gt;I had previously tried using code I lifted from &lt;a href="https://github.com/monochromegane/the_platinum_searcher"&gt;The Platinum Searcher&lt;/a&gt; albeit with some fixes to avoid crashes. Annoyingly however it never implemented glob&amp;rsquo;s correctly. I tried searching around for another implementation but none appeared to work as expected.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 07 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/walk-code-respecting-gitignore-go/</guid></item><item><title>Control vs Acceptance</title><link>https://healthydev.substack.com/p/control-vs-acceptance</link><description>An axis for judging your actions and reactions</description><author>The Healthy Dev</author><pubDate>Sat, 05 Aug 2023 11:35:24 GMT</pubDate><guid isPermaLink="true">https://healthydev.substack.com/p/control-vs-acceptance</guid></item><item><title>Colo vs Cloud</title><link>http://blog.onepatchdown.net/2023/08/04/cloud-vs-colo/</link><description>&lt;p&gt;what is the cheapest real actual server you can buy, new, with a support contract from a reputable vendor like HP/Dell/etc? (Assume we’re running both the db and web-server on one server.) And what’s the cost of sitting it in a reputable colo? And then multiply that by, let’s say two, to get redundancy in a us-west and us-east region. Ideally, we’d want more regions to serve the entire world, but let’s just go with two for now.&lt;/p&gt;

&lt;p&gt;Let’s say $10k for a server w/ support contract, x2, plus $400/mo for colo from Hurricane Electric (https://he.net/colocation.html) in us-west. It’s a promo deal, and only in us-west, but let’s just go with that price. $400*60 for 5 years (standard lifetime of computer equipment) = $24k. So $34k per server * 2 servers / 60 months ~= $1,130/month amortized. Thankfully, we’re not in cloud, so that’s the cost whether it’s 1 request/second, or 1,000 requests/second.&lt;/p&gt;

&lt;p&gt;A Raspberry Pi or old laptop in your basement with one consumer-grade ISP, and not factoring in the cost of electricity does not count here. Those are obviously going to be massively cheaper, but they’re not remotely suitable for enterprise.&lt;/p&gt;

&lt;p&gt;Meanwhile, Lambda gives you 1M requests and 3.2 million compute-seconds a month for free. This works out to be 0.4 rps (requests/second) or 22 requests / minute if spread out evenly. Which isn’t, like, a lot, but you haven’t paid a single cent to AWS for this yet. Let’s say you want to get to 40 rps (to keep the math easy) or 100M requests over the month. According to the AWS calculator (https://calculator.aws/#/addService/Lambda), this’ll run you $120/month, or $120*24 ~= $3k for 2 years.&lt;/p&gt;

&lt;p&gt;But that doesn’t count hosting for your static files with CloudFront, or does it count the RDS instance backing it, and we’d use AWS Cognito for user-auth since we’ve accepted AWS vendor lock-in. (Which isn’t great, but that’s the state of the industry.)&lt;/p&gt;

&lt;p&gt;CloudFront: Free Tier 1TB, 10M requests/month. 10 TB/month will run you $1k/month https://calculator.aws/#/addService/CloudFront&lt;/p&gt;

&lt;p&gt;RDS: This will vary greatly. A teeny tiny db.m6g.large instance w/ 2 CPUs and 8 GiB of RAM is going to run you $250/month, but a big one fat juicy db.r5d.24xlarge will run you closer to $20k/month. https://calculator.aws/#/addService/RDSMySQL&lt;/p&gt;

&lt;p&gt;Cognito: With 1,000 active users, this’ll be $50/month; 10,000 users is $500/mo, and 100,000 users is $5k/month.&lt;/p&gt;

&lt;p&gt;Between Lambda, CloudFront, RDS, and Cognito you could easily blow past that $1,130/month estimate for a proper colo’d pair of servers, if your service gets at all popular. But if you stick within the free tier for Lambda and Cloudfront, then you’re only looking at RDS and Cognito costs, which can vary greatly. Anywhere from $500/month to $26k/month, or more.&lt;/p&gt;

&lt;p&gt;Except to get the same service in a colo as a $26k/month AWS bill, you’d need to spend way more and have much more than the single server in a rack that I started with. Thankfully, with something like Equinix Smart Hands, no one has to drive to the colo to futz with a server when it develops a bad hard drive.&lt;/p&gt;

&lt;p&gt;Graph it out for your particular use case which one is better, but cloud infrastructure has the benefit of opex vs capex, as well as the opportunity cost of time. AWS Amplify will let you stand up a whole platform hacking on a Saturday before Dell can even quote you a price on a server or Equinix can answer the phone.&lt;/p&gt;

&lt;p&gt;I also did reject a raspberry pi or old laptop server early on, but that’s not to be underestimated. If I’ve already got some server running at home for my house, running a service on that, fronted by Cloudflare can take you quite far.&lt;/p&gt;

&lt;p&gt;It depends on your use case. The big thing is that AWS/Cloud charges for egress, so if you’re making a media distribution compnay, aka the Netflix model, you’re better off building your own, but the cloud is actuallly cost competitive.&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Fri, 04 Aug 2023 04:33:00 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/2023/08/04/cloud-vs-colo/</guid></item><item><title>What I’m up to - August 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-august-2023</link><description>&lt;div class="prose"&gt;
  &lt;div&gt;I'm &lt;a href="https://www.philipithomas.com"&gt;Philip&lt;/a&gt;, and this is my monthly newsletter about what I'm up to, which &lt;a href="https://www.philipithomas.com/posts/how-to-replace-social-media-with-a-personal-newsletter"&gt;I send in place of social media&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;✨ What I was up to in July&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;&lt;a href="https://booklet.community"&gt;Booklet&lt;/a&gt; is a product I've been building to help communities communicate in a more structured and readable way. This month I finally had some people besides me start to use it. It's been fun to get the product into the wild, and now I'm iterating rapidly based on their feedback ahead of a more public release. &lt;strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;I finally got to reading the &lt;a href="https://bookshop.org/p/books/the-noma-guide-to-fermentation-including-koji-kombuchas-shoyus-misos-vinegars-garums-lacto-ferments-and-black-fruits-and-vegetables-david-zilber/6900391"&gt;Noma Guide to Fermentation&lt;/a&gt; after visiting there last summer, and I've been doing a lot of experiments with lacto-fermentation. (See some photos below). It's been fun to go to the farmer's market every week and to cook with what's local and seasonal.&lt;br /&gt; &lt;br /&gt;I recently celebrated one full year of working full-time on &lt;a href="https://contraption.co"&gt;Contraption Company&lt;/a&gt;. Building new ways to help people work online has been a fun, rewarding, and creatively fulfilling journey. I'm proud of our commitment to keeping the business intentionally small and independent, and I'm excited about what the next year holds.&lt;br /&gt; &lt;strong&gt;&lt;br /&gt;&lt;br /&gt;🤔 Things to share&lt;/strong&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles&lt;/strong&gt;: &lt;a href="https://avc.com/2015/02/the-carlota-perez-framework/"&gt;The Carlota Perez Framework&lt;/a&gt; for thinking about technology cycles. &lt;a href="http://paulgraham.com/greatwork.html"&gt;How to Do Great Work&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books&lt;/strong&gt;: &lt;a href="https://www.amazon.com/gp/product/3967040976/ref=ppx_yo_dt_b_asin_title_o05_s00?ie=UTF8&amp;amp;psc=1"&gt;Designing Coffee&lt;/a&gt; looks at interior design of cafes around the world. &lt;a href="https://www.amazon.com/Ren%C3%A9-Redzepi-Work-Progress/dp/0714866911/ref=sr_1_10?qid=1691074764&amp;amp;refinements=p_27%3ARen%C3%A9+Redzepi&amp;amp;s=books&amp;amp;sr=1-10&amp;amp;text=Ren%C3%A9+Redzepi"&gt;A Work in Progress: A Journal &lt;/a&gt;documents a fascinating creative process. &lt;a href="https://bookshop.org/p/books/the-algebra-of-happiness-notes-on-the-pursuit-of-success-love-and-meaning-scott-galloway/12102427?ean=9780593084199"&gt;Algebra of Happiness&lt;/a&gt; has some good advice. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Podcast&lt;/strong&gt;: &lt;a href="https://lithub.com/robin-sloan-on-social-media-after-twitter/"&gt;Robin Sloan on Social Media After Twitter&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps&lt;/strong&gt;: &lt;a href="https://collective.com"&gt;Collective&lt;/a&gt; automates and optimizes the back office for one-person LLCs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trend: &lt;/strong&gt;Location sharing between friends - with granularity of either "City" or "Exact."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffees I'm drinking: &lt;/strong&gt;&lt;a href="https://www.prologcoffee.com/collections/buy-our-coffee/products/aquiares-natural-anaerobic?variant=46807797334344"&gt;Prolog Aquiares Anaerobic Natural&lt;/a&gt; was yummy. &lt;a href="https://getprodigal.com/products/finca-soledad-guatemala-washed"&gt;Prodigal Finca La Soledad Geisha&lt;/a&gt; was clean. &lt;a href="https://process.coffee/collections/coffee/products/nestor-lasso-ombligon-comp-lot"&gt;Process Nestor Lasso Ombligon&lt;/a&gt; was funky.&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;strong&gt;&lt;br /&gt;📫 What I'm up to in August&lt;/strong&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Traveling back to Mexico City, where I'm excited to try some new restaurants and cafes.&lt;/li&gt;
&lt;li&gt;Welcoming more Booklet users.&lt;/li&gt;
&lt;li&gt;Launching a couple of my own communities with Booklet.&lt;br /&gt;&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;strong&gt;📍 Where I'll be &lt;/strong&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Aug 3-7: Mexico City 🇲🇽&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;
&lt;em&gt;(Let me know if we overlap)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/em&gt;&lt;strong&gt;📸 Photos&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;A fermentation in three photos . . .&lt;br /&gt;&lt;br /&gt;&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbGhkIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--15b53ff92fe4b81e6eca12ea2673863f97818f80/IMG_8964%20(1).jpeg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Beginning - a haul from the farmer's market&lt;/figcaption&gt;
&lt;/figure&gt; &lt;br /&gt;&lt;br /&gt;&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbGxkIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--73698d099fb17954c2230ca45f6b0dcb32d8e37f/IMG_8965%20(1).jpeg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Middle - setting up the fermentation, which for berries takes about a week&lt;/figcaption&gt;
&lt;/figure&gt;&lt;br /&gt;&lt;br /&gt;&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbGRkIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7a12e6c01282eb4356a05b460efbf2a7beb18251/IMG_9104%20(2).jpeg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;End - fermented cherry gastrique with roasted chicken and purple potatoes&lt;/figcaption&gt;
&lt;/figure&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Thanks for reading! If we haven't chatted in a while, I'd love to hear from you. Send an email to say hi, share your thoughts, or just catch up. &lt;br /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Thu, 03 Aug 2023 18:17:07 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-august-2023</guid></item><item><title>How to Blow Up a Category - Netlify's New Era and The JAMstack Endgame</title><link>https://www.swyx.io/netlify-era-jamstack-end</link><description>&lt;blockquote&gt;
&lt;p&gt;note - this is a hasty written braindump of feelings as emotions as I don't have the time to polish this essay up to my usual standards, but still wanted to capture this important moment and end in my life. pardon any poorly phrased and organized thoughts. The JSJam guys did a roundup of reactions &lt;a href="https://www.javascriptjam.com/august-1-2023/"&gt;here&lt;/a&gt; and Ricmac &lt;a href="https://thenewstack.io/is-jamstack-toast-some-developers-say-yes-netlify-says-no/"&gt;did one on The New Stack&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</description><author>swyx's site RSS Feed</author><pubDate>Wed, 02 Aug 2023 08:12:50 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/netlify-era-jamstack-end</guid></item><item><title>2023–08–02: Update on the Pinephone Pro U-Boot build</title><link>https://xnux.eu/log/#092</link><author>megi's PinePhone Development Log</author><pubDate>Wed, 02 Aug 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#092</guid></item><item><title>Muv-Luv Alternative: Total Eclipse review</title><link>https://burakku.com/blog/muv-luv-alternative-total-eclipse-review/</link><description>&lt;p&gt;&lt;img alt="Muv-Luv Alternative: Total Eclipse" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The Total Eclipse anime was actually my first experience with the entire Muv-Luv universe – which I imagine was the case for a lot of current anime-watching Muv-Luv fans. I actually don't remember anything about it other than the first two episodes (which are not even covered in the VN). And while it has been eleven years since the anime premiered, having a forgettable story is not exactly a good sign for the VN. Then again, this meant that I came to the visual novel version of Total Eclipse with pretty fresh eyes.&lt;/p&gt;
&lt;p&gt;The visual novel story is entirely kinetic and feels a bit meandering, with the focus switching between political intrigue, life or death action, or high school romantic comedy, with a good chunk of the female cast competing who can have the most obnoxious teenage crush on the protagonist. Political intrigue across the major powers of the Alternative timeline does take the driver's seat for most of the story though. Even the action scenes are drenched in geopolitics.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Takamura Maniac" src="takamura.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Ostensibly the main objective of the story is bringing the XFJ Program to completion but it doesn't really feel like it. Rather, it feels like the story is about whatever is the current crisis that is currently unfolding and which may or may not relate to the XFJ program. I'm also not sold on the ending as it felt like the villain had better motives than the protagonist. Of course, the ending converges to work out somehow at the end because the writers didn't want to write a story where the protagonist does dumb things and meets a dumb end.&lt;/p&gt;
&lt;p&gt;There's also some questions raised during the story that were not really ever answered. Perhaps the idea was to leave room to further explore some of the factions in the future. But since Total Eclipse as a franchise is already over 15 years old, I doubt we'll ever get some kind of a satisfactory explanation to some stuff. At the moment it just feels like they're introducing unknown characters of unknown factions with unknown motives and you just have to deal with it.&lt;/p&gt;
&lt;p&gt;In general though, I felt at least engaged with the story overall and even actually captivated during certain parts of the story. And despite the ending involving some questionable developments, it does thankfully hit some good emotional tones and is ultimately something I felt somewhat satisfied with.&lt;/p&gt;
&lt;p&gt;To anyone interested in Total Eclipse, I'd say that the requirement for actually enjoying the experience is a general interest in the Muv-Luv Alternative universe – the story takes place just before the events of Muv-Luv Alternative and only expands on a tiny part of the Alternative story. Standing on its own two feet, it's not really a strong story, making the decision of making Total Eclipse the first animated Muv-Luv story questionable. But as an additional story in the Muv-Luv Alternative universe for Muv-Luv fans, it's decent enough to recommend.&lt;/p&gt;
&lt;p&gt;I did notice that the character arc for a lot of the characters followed the same pattern. They introduce a character as supremely unlikeable, throw them into the ringer for a bit and then have them calm down and become somewhere between tolerable and likeable. I noticed that they did it for the protagonist, Takamura, Cryska, the Chinese girl, and the other Japanese-American test pilot. Thankfully the protagonist and Takamura calm down relatively fast, otherwise the experience might've been quite a slog. I think they might've even tried doing it with Tarisa but she's far too adorable to be unlikeable. But despite the repeating character development pattern, the characters of Total Eclipse are ultimately likeable and interesting.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Here lies Tarisa, a rabid midget brought down by mere words." src="tarisa.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Presentation in Total Eclipse is the best that Muv-Luv has yet to offer. I did initially feel a bit weirded out by the colour palette, as it's very muted in comparison to many other entries in the series. However, after getting used to it, I can say that the art is very good. Both the sprites and CGs are high quality, and there's a good amount of CGs to boot with 195 entries in the album. Only very occasionally I saw something that looked weird or low quality.&lt;/p&gt;
&lt;p&gt;They're also utilising a good amount of animation for the sprites in order to wring out a bit more life out of static images. And for the first time ever, Total Eclipse also utilises a decent amount of actual 3D animated cutscenes during the story. While they managed to give a good kinetic feeling to the mech action in Alternative, it's completely different to see TSF manoeuvre when it's given a fully animated short cutscene. The only downside of the 3D cutscenes is that there's not that many of them and it feels like they mostly happen during the first half. Hopefully the level of animation shown in Total Eclipse is the new standard for new Muv-Luv stories, as I am quite a fan of it.&lt;/p&gt;
&lt;p&gt;On the sound side, there's not that much innovation. Yuuya is kind of a weird protagonist in that he's mostly not voiced but still is given a voice from time to time. At first I thought they only voiced him during flashbacks and when the story is not from his point-of-view, but later in the story, his lines are sometimes voiced and sometimes not. Not really sure the rationale for this, as THE DAY AFTER had Hibiki fully voiced. At least it's not super distracting. Otherwise the cast and voices are solid even if I can't really find anything special to say about it. What I did particularly enjoy however was the usage of insert songs during some bigger action events. I'm a huge sucker for insert songs for action scenes.&lt;/p&gt;
&lt;p&gt;No issues with general ergonomics and display. A much better reading experience than with the old rUGP titles. I read through Total Eclipse exclusively on my Steam Deck and the performance was what I've come to expect from titles using the Ages Mark 2 engine, which was mostly solid. Like with the other games, my Steam Deck exhibited the same kind of audio crackling after resuming the game from sleep. And even though this is a Steam Deck verified game, there's still no official controller layout.&lt;/p&gt;
&lt;p&gt;I finally however decide to test the &lt;a href="https://github.com/popsUlfr/SDH-PauseGames"&gt;Pause Games&lt;/a&gt; plugin and it seemed to make the whole Steam Deck audio experience a lot more solid. After installing the plugin and enabling the "pause before suspend" feature, it felt like I could resume Total Eclipse from sleep a lot better than previously without having massive and continuous audio crackling. Highly recommend this tweak to anyone experiencing audio crackling in Ages Mark 2 games. Wish I'd tried it back when I read through THE DAY AFTER series.&lt;/p&gt;
&lt;p&gt;All in all, Total Eclipse is a decent 25-hour delve into the geopolitical and weapon development politics of the Muv-Luv Alternative world, with occasional hot-blooded action to balance things out. Just don't make the same mistake âge did and try to make it your introduction to the Muv-Luv world.&lt;/p&gt;</description><author>ブラック</author><pubDate>Tue, 01 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/muv-luv-alternative-total-eclipse-review/</guid></item><item><title>CLI tip 31: concatenate files column wise</title><link>https://learnbyexample.github.io/tips/cli-tip-31/</link><description>&lt;p&gt;The &lt;code&gt;paste&lt;/code&gt; command is typically used to merge two or more files column wise. By default, &lt;code&gt;paste&lt;/code&gt; adds a tab character between corresponding lines of input files.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat colors_1.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Blue
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Brown
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Orange
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Purple
&lt;/span&gt;&lt;span&gt;$ cat colors_2.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Black
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Blue
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Green
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Orange
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ paste colors_1.txt colors_2.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Blue    Black
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Brown   Blue
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Orange  Green
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Purple  Orange
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use the &lt;code&gt;-d&lt;/code&gt; option to change the delimiter between the columns. The separator is added even if the data has been exhausted for some of the input files.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ paste &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'|' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4 5&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6 8&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;7
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;||&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;8
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note that the space between -d and empty string is necessary here
&lt;/span&gt;&lt;span&gt;$ paste &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;d &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6 8&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;16
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;27
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;38
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use newline separator to interleave file contents
&lt;/span&gt;&lt;span&gt;$ paste &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;11 12&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;101 102&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;11
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;101
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;12
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;102
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use empty files to get multicharacter separation between the columns. The &lt;code&gt;pr&lt;/code&gt; command is better suited for this task.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ paste &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' : ' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;null &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;null &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4 6&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;: &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;: &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;: &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ pr &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;mts&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' : ' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4 6&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;: &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;: &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;: &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://learnbyexample.github.io/cli_text_processing_coreutils/paste.html"&gt;paste command&lt;/a&gt; chapter from my &lt;a href="https://github.com/learnbyexample/cli_text_processing_coreutils"&gt;Command line text processing with GNU Coreutils&lt;/a&gt; ebook for more details.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 01 Aug 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-31/</guid></item><item><title>2023–07–31: My new U-Boot builds</title><link>https://xnux.eu/log/#091</link><author>megi's PinePhone Development Log</author><pubDate>Mon, 31 Jul 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#091</guid></item><item><title>2023–07–31: rk2aw released along with new U-Boot builds</title><link>https://xnux.eu/log/#090</link><author>megi's PinePhone Development Log</author><pubDate>Mon, 31 Jul 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#090</guid></item><item><title>Inside your head.</title><link>http://blog.onepatchdown.net/thoughts/2023/07/30/in-your-head/</link><description>&lt;p&gt;People sometimes say a problem is “inside your head”, but the idiotic thing of it is, of course everyone lives “inside their head”. It’s where consciousness lies (unless you’re an ancient Egyptian and believe it comes from the heart, which is “just” the muscle that pumps blood around, and isn’t packed full of neurons). So it’s a stupid statement to begin with. I live, as do you, and everyone else, “inside my head”. I literally can’t leave it. 24/7, I’m in my head even when I’m sleeping, I can’t escape it. And neither can you. All of my perceptions, everything I see, touch, or smell is filtered through my brain which is in my head.
What’s odious then, isn’t the statement itself, but the implication that I could just “shake it off” with just the right set of magical thinking. The problem then, is everybody who claims “it’s all in your head”, proclaims the problem solved, and walks off with their fingers in their ears, not listening to people suffering, not helping them “get out of their heads”, not even trying to help get to that place of magical thinking that would fix everything.&lt;/p&gt;

&lt;p&gt;They say the solution is “in your head”. If it is, they’re a right shite bunch of bastards for keeping this magical thinking to themselves and letting the rest of us suffer. If instead we focused on listening to the people suffering, and have identified that it’s in their heads, then how do we get them out of it? How do we expand their consciousness to be “outside” their heads? How do we modify their brain so the head they’re in is different? We have all sorts of drugs for that, both legal and illegal, as well as a host of new therapies, some approved by the FDA, as in “has a scientifically proven effect”, using magnetic fields applied to the brain, eg TMS (Transcranial Magnetic Stimulation), to help people get their heads, which they live 24/7 in, to be different ones than the ones that are giving them so much trouble!&lt;/p&gt;

&lt;p&gt;Thanks to doctors actually listening to patients (which is a revolutionary concept, I know. Not all doctors are super geniuses, but the ones who are able to listen to people who’s problems are “in their heads” are all smarter than those who dismiss people’s problems for being “in their heads”. Especially neurologists, who have dedicated their career to the study of what’s inside heads.), people are getting better, healing the brain, and once again living fulfilling lives, with more than 3 spoons a day of energy. At the worst of it, watching Netflix was too exhausting. Concentrating enough to write code was out of the question. After TMS, powerful psychiatric medication, a course of psilocybin and other off label courses to help me “get out of my head”, and a huge amount of talk therapy on top, I’d now consider myself cured of whatever lingering after effects of Covid and the pandemic I suffered. It took a lot of work, I still live “in my head” but I’m able to leave my bed enough day of the week, and even leave my house most of them as well.&lt;/p&gt;

&lt;p&gt;Feel free to tell me my problems are in my head so long as you’re going to help me get out of it; help me pay for doctors, help me get to the doctor, help me pay for therapies and therapists and actually get to them, help me navigate insurance and get on disability while I need it, help me with housework while it’s too much for me to manage.&lt;/p&gt;

&lt;p&gt;“It’s in your head” is the beginning of helping to figure out how to solve the problem, not the solution it and of itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=36868226"&gt;via HN&lt;/a&gt;&lt;/p&gt;</description><author>Blogity blog blog. Journal</author><pubDate>Sun, 30 Jul 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">http://blog.onepatchdown.net/thoughts/2023/07/30/in-your-head/</guid></item><item><title>What are the types of happiness?</title><link>https://jodavaho.io/posts/what-are-the-types-of-happiness.html</link><description>&lt;p&gt;As I sit in a hotel lobby at 4:00 AM drinking awful coffee (story for another time), I&amp;rsquo;m reflecting on:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If happiness from a boon or gain or accomplishment is just the loss of the pain of not having that gain, then maybe we can just short circuit that process and stop wanting the thing in the first place&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Of course, this is a well-studied idea&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;. But what gets me is: &lt;em&gt;Is that what happiness is?&lt;/em&gt; Can I &lt;em&gt;really&lt;/em&gt; be happy by removing desire (which, presumptively, prevents happiness by showing me a future I don&amp;rsquo;t have). Certainly, some &lt;em&gt;class&lt;/em&gt; of happiness can be &amp;ldquo;solved&amp;rdquo; by this anti-FOMO strategy. But it seems so zero-sum.&lt;/p&gt;
&lt;p&gt;This idea assumes that I &lt;em&gt;believe&lt;/em&gt; I will be &lt;em&gt;happier&lt;/em&gt; with that thing in my life, and therefore I am &lt;em&gt;less happy&lt;/em&gt; for not having it. I don&amp;rsquo;t think this is the case at all. I &lt;em&gt;want&lt;/em&gt; to look forward to things. I &lt;em&gt;want&lt;/em&gt; to feel closer every day to a goal, for example. This &amp;ldquo;&lt;strong&gt;happiness of progress&lt;/strong&gt;&amp;rdquo; seems to be totally different.&lt;/p&gt;
&lt;h2 id="example-kids"&gt;example: Kids&lt;/h2&gt;
&lt;p&gt;Nobody could ever have told me I&amp;rsquo;d be happy as a father. It was something I wanted, but my happiness was not realized when the kids were born. &lt;em&gt;They themselves&lt;/em&gt; bring the happiness with their delightful little goblin selves every day. This sort of &amp;ldquo;lost the sadness of not having kids&amp;rdquo; does not adequately explain the joy of being a father&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;p&gt;But what kind of happiness is that? It seems to be the happiness of &lt;em&gt;relationship&lt;/em&gt;, a totally different kind.&lt;/p&gt;
&lt;h2 id="types-of-happiness"&gt;Types of happiness&lt;/h2&gt;
&lt;p&gt;Where this train of thought leads me to is: What are the types of happiness, and what are the viable strategies for preserving or achieving them. I know there are &lt;a href=""&gt;types of love&lt;/a&gt; but without looking for it, I&amp;rsquo;m motivated to try to try and derive this list myself first.&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a rough list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The loss of persistent discomfort&lt;/li&gt;
&lt;li&gt;The end of a journey&lt;/li&gt;
&lt;li&gt;The joy of laziness, or The contentment of having no obligations&lt;/li&gt;
&lt;li&gt;Discovery and creation&lt;/li&gt;
&lt;li&gt;Personal connection&lt;/li&gt;
&lt;li&gt;The joy of being&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And of course, there are many that are derived or from combinations of these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the joy of collaboration is roughly &amp;ldquo;personal connection&amp;rdquo; plus &amp;ldquo;creation&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think this is an incomplete list, but it allows one (me only?) to not feel too guilty about desire, while at the same time feeling happy in the moment for various reasons. This feels less absolutist and more adult.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;. See any old philosophy. Or, I suggest read &lt;a href="https://www.amazon.com/The-Happiness-Hypothesis-audiobook/dp/B07D5JCWLD/ref=sr_1_1"&gt;Happiness Hypothesis&lt;/a&gt; for a decent summary.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Side note: The stoics would say I should relish this feeling, but face the prospect of losing it every day, to keep the feeling fresh and sweet. Yes, thinking of the kids being sully parent-hating teenagers that love speeding and hate school &amp;hellip; well that does make these days all the sweeter.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Where even might we find this list? Perhaps an evolutionary biologist would say that &amp;ldquo;Status&amp;rdquo; is the most important thing, and all other derive from this.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Sun, 30 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/what-are-the-types-of-happiness.html</guid></item><item><title>Your Code is not Yours</title><link>https://healthydev.substack.com/p/your-code-is-not-yours</link><description>But our tools make it easy to think it is</description><author>The Healthy Dev</author><pubDate>Thu, 27 Jul 2023 23:38:44 GMT</pubDate><guid isPermaLink="true">https://healthydev.substack.com/p/your-code-is-not-yours</guid></item><item><title>Things I hate about my Tesla</title><link>https://jodavaho.io/posts/things-i-hate-about-my-tesla.html</link><description>&lt;p&gt;I own a 2022 Tesla Model Y. Or rather, my powerhouse wife does. Regardless, I&amp;rsquo;ve driven approximately 10,000 miles in one, and have formed a long list of opinions.&lt;/p&gt;
&lt;h1 id="the-good"&gt;The Good&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;The performance&lt;/li&gt;
&lt;li&gt;The cold / icy driving experience is &lt;em&gt;fantastic&lt;/em&gt;. It&amp;rsquo;s the easiest to drive in slippery conditions that I&amp;rsquo;ve owned &lt;em&gt;except&lt;/em&gt; my old toyota 4x4 SUV, but that&amp;rsquo;s a high bar.&lt;/li&gt;
&lt;li&gt;The adaptive cruise control eliminates 90% of the annoyances from highway driving (though the industry has caught up)&lt;/li&gt;
&lt;li&gt;The 360 cameras and 360 range sensors make driving in tight confines very easy.&lt;/li&gt;
&lt;li&gt;The incident reports. This is the video replay of any person or vehicle approaching or touching your car. I&amp;rsquo;ve already seen it save an insurance report and aid in accident reporting.&lt;/li&gt;
&lt;li&gt;The appearance&lt;/li&gt;
&lt;li&gt;The superchargers are &lt;em&gt;awesome&lt;/em&gt;. It&amp;rsquo;s absolutely never been a problem to visit one the 3 to 6 times per year we have to use them. We charge at home overnight, and have not set foot in a gas station except to pick up Diet Coke since we bought the Tesla. It seems crazy we don&amp;rsquo;t all do this, honestly. I can&amp;rsquo;t wait for gasoline to be a speciality product.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="the-bad"&gt;The Bad&lt;/h1&gt;
&lt;h2 id="entering-the-car"&gt;Entering the car&lt;/h2&gt;
&lt;p&gt;Someday there will be a zombie movie where a minor character is running from zombies, turns the corner to see their pristine Tesla, &lt;em&gt;thank god&lt;/em&gt;, and will sadly be eaten while they fumble with the app, key cards, and door locking mechanisms (see next section).&lt;/p&gt;
&lt;p&gt;When approaching the car to drive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;10% of the time it does nothing. I stand confused as the dash reads &amp;ldquo;Cameras on&amp;rdquo;. Then I take out my keycard, wipe it over the window frame trying to find the magic, unlabelled &amp;ldquo;unlock&amp;rdquo; spot, and eventually enter.&lt;/li&gt;
&lt;li&gt;30% of the time it unlocks just fine, I enter, sit down, and drive away - except my legs are cramped and some techno is blaring because it selected my wife&amp;rsquo;s seat and radio settings&lt;/li&gt;
&lt;li&gt;10% of the time it unlocks just fine, I enter, sit down, and it won&amp;rsquo;t let me drive. It insists I place a keycard under the armrest to drive.&lt;/li&gt;
&lt;li&gt;50% of the time it unlocks just fine, I enter, sit down, and drive away fine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously I created those percentages, but it seems about right.&lt;/p&gt;
&lt;h3 id="special-case-using-the-trunk"&gt;Special case: Using the trunk&lt;/h3&gt;
&lt;p&gt;Using the trunk is a special case. The &amp;ldquo;right&amp;rdquo; way is to go through the above process to enter the car, but instead of driving, you use the tablet (see next section) to unlock the trunk.&lt;/p&gt;
&lt;p&gt;But, idiot that I am, I assume it will auto-unlock, and I&amp;rsquo;ll be able to open the trunk by pressing the open button on the trunk itself. According to above, there&amp;rsquo;s a 90% chance it&amp;rsquo;ll unlock. But this is wrong because the Tesla doesn&amp;rsquo;t unlock when you approach from behind, only when you approach the cab it seems.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;20% of the time, the car unlocks so I can open the trunk fine&lt;/li&gt;
&lt;li&gt;40% of the time I have to go up to the cab to get the car to unlock, but the trunk won&amp;rsquo;t unlock. I have to &lt;em&gt;open the driver&amp;rsquo;s side door&lt;/em&gt; to, I guess, &lt;em&gt;fully&lt;/em&gt; unlock the &lt;em&gt;trunk&lt;/em&gt;, at which time I can go back and open it.&lt;/li&gt;
&lt;li&gt;40% of the time I just open the driver&amp;rsquo;s side door (see prev section!) and then open trunk using console.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="walk-away-lock"&gt;Walk-away lock&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s no easy way to temporarily disable walk-away lock. This means that when you&amp;rsquo;re loading kids, luggage, or anything, and it&amp;rsquo;ll take multiple trips, and you don&amp;rsquo;t want the car to lock, turn off climate control, and shutdown, you have to leave a door open. You don&amp;rsquo;t even have to walk away, just shutting the door while you&amp;rsquo;re not in the driver&amp;rsquo;s seat will shut the car down.&lt;/p&gt;
&lt;p&gt;And you &lt;em&gt;can&lt;/em&gt; enable climate control remotely, but as soon as you open then close the door (e.g., you put a kid in), the whole car will shut down regardless of if you walked away or not. Imagine the Tesla locking your kids in the car, and oops, you drop your phone.&lt;/p&gt;
&lt;h2 id="the-app"&gt;The app&lt;/h2&gt;
&lt;p&gt;All of this is fine if I can open the app to do any of this. But the app is useless. No matter where I am or where the car is, there&amp;rsquo;s a 90% chance I cannot connect my app to the car. I suspect my wife disabled my app access at this point.&lt;/p&gt;
&lt;h2 id="that-fucking-tablet"&gt;That fucking tablet.&lt;/h2&gt;
&lt;p&gt;OK here we go. This is the big one.&lt;/p&gt;
&lt;p&gt;In the tesla the UX is awful. You can do exactly one thing at a time, and transitioning between things to do takes at least a click and swipe. Think about all the things you might want to do in a car:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open the glove box&lt;/li&gt;
&lt;li&gt;Turn on/off the windshield wipers&lt;/li&gt;
&lt;li&gt;Change the radio station&lt;/li&gt;
&lt;li&gt;Stream via spotify&lt;/li&gt;
&lt;li&gt;Pair a phone&lt;/li&gt;
&lt;li&gt;Open the trunk&lt;/li&gt;
&lt;li&gt;Adjust the speakers&lt;/li&gt;
&lt;li&gt;Enable air conditioning / heat&lt;/li&gt;
&lt;li&gt;Lock the windows&lt;/li&gt;
&lt;li&gt;Turn on/off seat warmers&lt;/li&gt;
&lt;li&gt;Enable rear fans&lt;/li&gt;
&lt;li&gt;Check the range to your destination&lt;/li&gt;
&lt;li&gt;View the upcoming turns in navigation&lt;/li&gt;
&lt;li&gt;Input a new destination for your navigation&lt;/li&gt;
&lt;li&gt;&lt;em&gt;anything else&lt;/em&gt; &amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yes, you need to use the tablet to do that. Nope, you cannot do any of those at the same time. Yes, you cannot adjust the winshield wipers without taking your eyes off the road and looking at the bottom corner of the tablet.&lt;/p&gt;
&lt;p&gt;There are almost no buttons in the car - just the tablet. Each thing you want to do  requires a full-screen interaction, meaning if the passenger wants to do any of this they block the entire in-car display with a fancy popup modal. And don&amp;rsquo;t get me started on how annoying it is when the FM tuner is left up and blocks my navigation instructions.&lt;/p&gt;
&lt;p&gt;This is far and away my biggest complaint about the car. They were arrogant enough to just believe they could make a functional UI from a tablet bolted onto their dashboard. It is a major distraction to safe driving to do anything in that car.&lt;/p&gt;
&lt;h2 id="the-cold-weather-performance-is-not-good"&gt;The cold weather performance is not good.&lt;/h2&gt;
&lt;p&gt;While in general the range is good, the cold weather range is awful. We can drive about half as far as normal, and any statements about pre-conditioning the battery alleviating that are &lt;strong&gt;false&lt;/strong&gt;. You &lt;em&gt;can&lt;/em&gt; precondition to gain back some range, but still, a trip to our parents&amp;rsquo; place in the country is doable in summer (if you dont run AC), but requires a stop at a supercharger in winter.&lt;/p&gt;
&lt;h1 id="the-wtf"&gt;The WTF&lt;/h1&gt;
&lt;h2 id="when-you-are-using-cruise-control-you-cannot-disable-windshield-wipers"&gt;When you are using cruise control you cannot disable windshield wipers.&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m not kidding. Why? Because Sir Elon insisted that this car is made using cameras only, and the camera is behind the windshield, so to do adaptive speed control it needs to have a pristine windshield. Most other manufacturers use radar. Going forward, I expect more lidar.&lt;/p&gt;
&lt;h2 id="the-adaptive-cruise-control-will-slam-on-the-brakes-for-no-apparent-reason"&gt;The adaptive cruise control will slam on the brakes for no apparent reason.&lt;/h2&gt;
&lt;p&gt;About once every 50 miles, it will execute a pretty dangerous-feeling braking. Why? No idea, but most likely the cameras detected a phantom car or obstacle. It once got real nervous about a bridge that was paved a lighter color than the road we were driving on. It will also slow down if a car &lt;em&gt;looks&lt;/em&gt; like it&amp;rsquo;s in front of it when we&amp;rsquo;re all turning on a highway. I find these things to be dangrous enough that I question the purchase regularly.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Thu, 27 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/things-i-hate-about-my-tesla.html</guid></item><item><title>Why I am starting a hardcore tech company in my 50s</title><link>https://iamnotarobot.substack.com/p/why-i-am-starting-a-hardcore-tech</link><description>Or at least this is the story I tell myself about it</description><author>I Am Not a Robot</author><pubDate>Wed, 26 Jul 2023 20:48:02 GMT</pubDate><guid isPermaLink="true">https://iamnotarobot.substack.com/p/why-i-am-starting-a-hardcore-tech</guid></item><item><title>Using the SonicWall Connect Tunnel with Firefox on a Chromebook</title><link>https://j11g.com/2023/07/26/using-the-sonicwall-connect-tunnel-with-firefox-on-a-chromebook/</link><description>Yes, you read that correctly. Firefox on a Chromebook! Without tricks. Or at least, not many tricks. Why? When you want to use the SonicWall Connect Tunnel software (from the SMA 1000 Series) on your Chromebook the suggested SonicWall Mobile Connect app does not work properly. I don&amp;#8217;t know why, but there is a solution. [&amp;#8230;]</description><author>Jan van den Berg</author><pubDate>Wed, 26 Jul 2023 20:21:49 GMT</pubDate><guid isPermaLink="true">https://j11g.com/2023/07/26/using-the-sonicwall-connect-tunnel-with-firefox-on-a-chromebook/</guid></item><item><title>Python tip 31: next() function</title><link>https://learnbyexample.github.io/tips/python-tip-31/</link><description>&lt;p&gt;The &lt;a href="https://docs.python.org/3/library/functions.html#next"&gt;&lt;code&gt;next()&lt;/code&gt;&lt;/a&gt; builtin function can be used on an iterator (but not iterables) to retrieve the next item. Once you have exhausted an iterator, trying to get another item will result in a &lt;code&gt;StopIteration&lt;/code&gt; exception. Here's an example:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;names &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;(m &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;m &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;dir&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;tuple&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'__' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;not in &lt;/span&gt;&lt;span&gt;m)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;next&lt;/span&gt;&lt;span&gt;(names)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'count'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;next&lt;/span&gt;&lt;span&gt;(names)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'index'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;next&lt;/span&gt;&lt;span&gt;(names)
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Traceback &lt;/span&gt;&lt;span&gt;(most recent call last):
&lt;/span&gt;&lt;span&gt;  File &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, line &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &amp;lt;&lt;/span&gt;&lt;span&gt;module&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;StopIteration
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's a practical example to get a random item from a &lt;code&gt;list&lt;/code&gt; without repetition:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; import &lt;/span&gt;&lt;span&gt;random 
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;names &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Jo'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Ravi'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Joe'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Raj'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Jon'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;random.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;shuffle&lt;/span&gt;&lt;span&gt;(names)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;random_name &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;iter&lt;/span&gt;&lt;span&gt;(names)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;next&lt;/span&gt;&lt;span&gt;(random_name)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Jon'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;next&lt;/span&gt;&lt;span&gt;(random_name)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Ravi'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can set a default value to be returned instead of the &lt;code&gt;StopIteration&lt;/code&gt; exception. Here's an example:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; letters &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;iter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; next(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;letters, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'f'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; next(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;letters, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'i'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; next(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;letters, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'g'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; next(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;letters, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; next(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;letters, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 25 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-31/</guid></item><item><title>Correctly configuring incoming SPF in Exim on Debian</title><link>https://j11g.com/2023/07/24/correctly-configuring-incoming-spf-in-exim-on-debian/</link><description>The Debian documentation is sparse on how to correctly configure incoming SPF checks in the Debian Exim package. It is sparse in the sense that it tells you what to install (spf-tools-perl) but it is not clear WHERE to put the very important macro. It only says: This is provided via the macro&amp;#160;CHECK_RCPT_SPF, set it [&amp;#8230;]</description><author>Jan van den Berg</author><pubDate>Tue, 25 Jul 2023 00:17:31 GMT</pubDate><guid isPermaLink="true">https://j11g.com/2023/07/24/correctly-configuring-incoming-spf-in-exim-on-debian/</guid></item><item><title>How to deal with fatal: bad object HEAD in git</title><link>https://www.swyx.io/solve-git-bad-object-head</link><description>&lt;p&gt;as a cheapo who uses Apple iCloud (the world's &lt;a href="https://www.swyx.io/what-to-do-when-icloud-is-stuck-on-uploading-items"&gt;worst&lt;/a&gt; sync service) as a sync service for my &lt;a href="https://www.swyx.io/obsidian-brain"&gt;Obsidian Second Brain&lt;/a&gt;, I have recently run into this issue a lot:&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 24 Jul 2023 16:57:34 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/solve-git-bad-object-head</guid></item><item><title>What We Mean When We Say “Software Engineer”</title><link>https://letterstoanewdeveloper.com/2023/07/24/what-we-mean-when-we-say-software-engineer/</link><description>This is a guest post from Doug Durham. Enjoy. Dear new developer, It’s very easy to throw terms around in our (or any) industry. In our hurried culture, we latch onto words or phrases that may not fully encapsulate their original intent. I’m afraid that is what has happened to the label of “software engineer”. &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/07/24/what-we-mean-when-we-say-software-engineer/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;What We Mean When We Say &amp;#8220;Software&amp;#160;Engineer&amp;#8221;&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 24 Jul 2023 15:15:00 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/07/24/what-we-mean-when-we-say-software-engineer/</guid></item><item><title>Introduction to Marketing Mix Modeling</title><link>https://bytepawn.com/introduction-to-marketing-mixed-modeling.html</link><description>&lt;p&gt;I describe the concept of Marketing Mix Modeling using Google's LightweightMMM library.&lt;br /&gt;&lt;br /&gt;&lt;img alt="MMM attribution" src="/images/mmm_1.png" style="width: 600px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 23 Jul 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/introduction-to-marketing-mixed-modeling.html</guid></item><item><title>Creation is our higher purpose</title><link>https://jodavaho.io/posts/creation-is-divine.html</link><description>&lt;h3 id="building-things-is-our-higher-purpose"&gt;Building things is our higher purpose&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Ergo, the most shameful thing is not &lt;em&gt;not finish&lt;/em&gt; a creation&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I feel deep in my bones that making things is the higher purpose of humanity.
It&amp;rsquo;s our secret weapon against devolving into tribes. The simplest creations
improve ones life, and that&amp;rsquo;s what makes us human: Having agency to improve our
lot. The true strength of our gift is making for others. Making something
desireable from something less desireable underpins every part of cities, is the
foundation of medicine, and a basic requirement of our economy. It will make us
an interstellar species, because we can make it so if we want. We can make
&lt;em&gt;anything&lt;/em&gt; so, if we &lt;em&gt;really&lt;/em&gt; want it. The wanting is the hardest part.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://jodavaho.io/img/divine-creation.png" width="70%" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;credit: Midjourney&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Everything made is in service of this divine. So, I&amp;rsquo;ve realized that the most
shameful thing I do is &lt;em&gt;not finish&lt;/em&gt;. I haven&amp;rsquo;t created until it&amp;rsquo;s made
available for myself or others.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve made a life decision: No more projects until my current top 3 are done.
When one is done, I sub it out and can sub one in. My current top 3 are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://jodavaho.io/posts/hfopt-intro.html"&gt;Highfleet Ship Optimizer&lt;/a&gt;: Which has taken so long I doubt anyone will be playing the game by the time I finish it. Should be done soon. Using SCIP, Python on &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt;, and a frontend hosted for free on &lt;a href="https://vercel.com"&gt;Vercel&lt;/a&gt;. Nice and cheap at a bout a buck a month.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Personal finance automation. Not quite automated trading, but at least real-time monitoring and some basic savings deposits. Gonna be a CLI integration with &lt;a href="https://plaid.com/"&gt;Plaid&lt;/a&gt; to fetch balances. Might be a nice open-source project. I&amp;rsquo;d love a nice 80s terminal aesthetic to it for funzies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gunplay-heavy, cyberpunk-like tabletop rpg system, to include better statistics from rolling, a more systematic skill tree, increased lethality over typical games, and better system for tracking influence, connections, and wealth. A friend and I are play-testing it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The backlog is long, but I&amp;rsquo;ll feel better if I can finish a few here and there
rather than letting them all wait around and catching the latest idea.&lt;/p&gt;
&lt;p&gt;Note: I copied this from a letter I wrote to a friend. I hope he won&amp;rsquo;t mind.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Fri, 21 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/creation-is-divine.html</guid></item><item><title>Vim tip 29: greedy quantifiers</title><link>https://learnbyexample.github.io/tips/vim-tip-29/</link><description>&lt;p&gt;Quantifiers can be applied to literal characters, dot metacharacter, groups, backreferences and character classes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; match zero or more times
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;abc*&lt;/code&gt; matches &lt;code&gt;ab&lt;/code&gt; or &lt;code&gt;abc&lt;/code&gt; or &lt;code&gt;abccc&lt;/code&gt; or &lt;code&gt;abcccccc&lt;/code&gt; but not &lt;code&gt;bc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Error.*valid&lt;/code&gt; matches &lt;code&gt;Error: invalid input&lt;/code&gt; but not &lt;code&gt;valid Error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s/a.*b/X/&lt;/code&gt; replaces &lt;code&gt;table bottle bus&lt;/code&gt; with &lt;code&gt;tXus&lt;/code&gt; since &lt;code&gt;a.*b&lt;/code&gt; matches from the first &lt;code&gt;a&lt;/code&gt; to the last &lt;code&gt;b&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\+&lt;/code&gt; match one or more times
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;abc\+&lt;/code&gt; matches &lt;code&gt;abc&lt;/code&gt; or &lt;code&gt;abccc&lt;/code&gt; but not &lt;code&gt;ab&lt;/code&gt; or &lt;code&gt;bc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\?&lt;/code&gt; match zero or one times
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\=&lt;/code&gt; can also be used, helpful if you are searching backwards with the &lt;code&gt;?&lt;/code&gt; command&lt;/li&gt;
&lt;li&gt;&lt;code&gt;abc\?&lt;/code&gt; matches &lt;code&gt;ab&lt;/code&gt; or &lt;code&gt;abc&lt;/code&gt;. This will match &lt;code&gt;abccc&lt;/code&gt; or &lt;code&gt;abcccccc&lt;/code&gt; as well, but only the &lt;code&gt;abc&lt;/code&gt; portion&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s/abc\?/X/&lt;/code&gt; replaces &lt;code&gt;abcc&lt;/code&gt; with &lt;code&gt;Xc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\{m,n}&lt;/code&gt; match &lt;code&gt;m&lt;/code&gt; to &lt;code&gt;n&lt;/code&gt; times (inclusive)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ab\{1,4}c&lt;/code&gt; matches &lt;code&gt;abc&lt;/code&gt; or &lt;code&gt;abbc&lt;/code&gt; or &lt;code&gt;xabbbcz&lt;/code&gt; but not &lt;code&gt;ac&lt;/code&gt; or &lt;code&gt;abbbbbc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\{m,}&lt;/code&gt; match at least &lt;code&gt;m&lt;/code&gt; times
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ab\{3,}c&lt;/code&gt; matches &lt;code&gt;xabbbcz&lt;/code&gt; or &lt;code&gt;abbbbbc&lt;/code&gt; but not &lt;code&gt;ac&lt;/code&gt; or &lt;code&gt;abc&lt;/code&gt; or &lt;code&gt;abbc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\{,n}&lt;/code&gt; match up to &lt;code&gt;n&lt;/code&gt; times (including &lt;code&gt;0&lt;/code&gt; times)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ab\{,2}c&lt;/code&gt; matches &lt;code&gt;abc&lt;/code&gt; or &lt;code&gt;ac&lt;/code&gt; or &lt;code&gt;abbc&lt;/code&gt; but not &lt;code&gt;xabbbcz&lt;/code&gt; or &lt;code&gt;abbbbbc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\{n}&lt;/code&gt; match exactly &lt;code&gt;n&lt;/code&gt; times
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ab\{3}c&lt;/code&gt; matches &lt;code&gt;xabbbcz&lt;/code&gt; but not &lt;code&gt;abbc&lt;/code&gt; or &lt;code&gt;abbbbbc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Greedy quantifiers will consume as &lt;em&gt;much&lt;/em&gt; as possible, provided the overall pattern is also matched. That's how the &lt;code&gt;Error.*valid&lt;/code&gt; example worked. If &lt;code&gt;.*&lt;/code&gt; had consumed everything after &lt;code&gt;Error&lt;/code&gt;, there wouldn't be any more characters to try to match &lt;code&gt;valid&lt;/code&gt;. How the regexp engine handles matching varying amount of characters depends on the implementation details (backtracking, NFA, etc).&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://vimhelp.org/pattern.txt.html#pattern-overview"&gt;:h pattern-overview&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; If you are familiar with other regular expression flavors like Perl, Python, etc, you'd be surprised by the use of &lt;code&gt;\&lt;/code&gt; in the above examples. If you use &lt;code&gt;\v&lt;/code&gt; very magic modifier, the &lt;code&gt;\&lt;/code&gt; won't be needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 19 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-29/</guid></item><item><title>Six months with my Steam Deck</title><link>https://burakku.com/blog/steam-deck-six-month-update/</link><description>&lt;p&gt;&lt;img alt="Steam Deck" src="thumbnail.png" /&gt;&lt;/p&gt;
&lt;p&gt;I've had my Steam Deck for six months now and I am surprised at just how transformative of a device it has been for me.&lt;/p&gt;
&lt;p&gt;When the Steam Deck was first announced, I was very sceptical of it. Back in August 2021, I remarked in a conversation how I only saw the use case for the Steam Deck in emulators and visual novels. And in January 2022, I commented how I wasn't interested in buying one. To be fair, my reasoning for not buying one was pretty sound: I was almost never away from home where I keep my gaming PC, and I already had a Nintendo Switch and two PlayStation Vita. But in December 2022, I changed my mind: I thought it seemed neat, especially for visual novels. And about a month later, I picked up a second-hand 256 GB model for 400€.&lt;/p&gt;
&lt;p&gt;Now six months later, I absolutely adore my little Steam Deck. It's a really cool gaming device. Unfortunately due to &lt;a href="https://twitter.com/thexpaw/status/1672256909733244928"&gt;Steam breaking the &lt;em&gt;hours per platform&lt;/em&gt; tracking that SteamDB had&lt;/a&gt;, I can no longer see how many hours I've played on Linux (Steam Deck being my only Linux gaming PC), but my current estimate is that I've spent over 120 hours playing on the Steam Deck. Around 20 hours per month is not nothing for me, especially since I'm rarely more than five kilometres away from home / my gaming PC.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; Turns out that if you register &lt;a href="https://steamcommunity.com/dev/apikey"&gt;your own Steam API key&lt;/a&gt;, you can fetch your own Linux-specific Steam playtime. My total Steam Deck (or rather Linux) playtime is currently 131 hours!&lt;/p&gt;
&lt;p&gt;It's actually extremely easy to forget that the Steam Deck is a weird, specialised gaming PC that is running on top of modified Arch Linux even if almost all of my Steam library is just Windows games. You can see that a lot of work has gone into Proton to get it work as flawlessly as it does. It's of course not perfect, but it is downright sublime all things considered.&lt;/p&gt;
&lt;p&gt;So far on my Deck, I've read through six Muv-Luv visual novels (with the seventh currently underway), played through Bastion and Jet Set Radio, tried out a bit of Forza Horizon 5, Grand Theft Auto V and San Andreas, and put many hours towards Grand Theft Auto IV, Tokyo Xanadu eX+ and Vampire Survivors. Not everything was a perfect fit on the Steam Deck, and not everything worked completely right, but I still feel extremely satisfied with what I've managed to get done with a Linux-based handheld gaming PC. Haven't managed to try emulation yet though since I've never been huge into emulation, but maybe I'll take a stab at it at some point as well. I've at least heard the emulation experience is quite good.&lt;/p&gt;
&lt;p&gt;It's also quite easy to juggle a lot of games on the Steam Deck. Even though I had the 256 GB model, I immediately spruced it up with a microSD card. Flash storage has gotten so cheap that slapping an extra 512 GB of storage to my Steam Deck only cost me 38.10€. I actually had to check my receipts and discovered that's about the same that I paid for my 16 GB Vita memory card nine years ago. And now, thanks to my 685.7 GB of usable storage, I have 43 games installed on my Steam Deck with 115.7 GB left over. That's a lot of game library for such a small device.&lt;/p&gt;
&lt;p&gt;I've also been pleasantly surprised at how manageable of a device the Steam Deck is. On paper, it seemed absolutely massive compared to my Nintendo Switch and PlayStation Vitas. But when I held it up for the first time, it didn't actually feel that massive or heavy. I was expecting it to feel like a brick but I think the weight is balanced well with its size, so it doesn't feel particularly dense. Navigation on the Steam Deck is also well-implemented, with its touchscreen and trackpads, the latter of which is particularly nice for the occasional text entry. While I haven't done any massive marathon sessions, I don't really have any issues with the ergonomics.&lt;/p&gt;
&lt;p&gt;The Steam Deck has been so excellent that I think it has managed to make PC my primary gaming platform for the first time in a decade.&lt;/p&gt;
&lt;p&gt;Back when the PlayStation 4 and the PlayStation Vita were new products, Sony touted the benefits of their ecosystem by promising that you could buy a digital copy of a game for both your PS4 and your Vita just once with Cross-Buy, and then sync your progress between the home console and the handheld console with Cross-Save. Unfortunately third-party support for these features, and the PlayStation Vita as a whole, never really took off, and Sony never had the fortitude to carry on with the experiment. Well, unless they manage to surprise with &lt;a href="https://www.theverge.com/2023/5/24/23736595/sony-project-q-playstation-handheld-official-showcase"&gt;the upcoming Project Q&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, it feels like Valve has managed to do what Sony couldn't. Since the Steam Deck is just a small PC and has direct access to every single game in my Steam Library, I can now buy a PC game just once and have it available both at home and abroad. A lot of games these days also support Steam Cloud, so I can realistically carry on where I left off on the other device – at least if the Steam Cloud support is properly implemented (&lt;a href="https://steamcommunity.com/app/789830/discussions/0/3829788562450425023/"&gt;I've seen it not be&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I think the biggest proof of my transformation is the fact that ever since January, I have not picked up a single game for any of my consoles. Contrast this to my estimate of having spent approximately $3800 on console games between January 2021 and January 2023. Ever since the Steam Deck, my interest in my gaming consoles has dropped off significantly, and my interest in PC gaming has increased dramatically. My Nintendo Switch might offer the portability of my Steam Deck and my PlayStation 5 might match my gaming PC in delivering high-fidelity experiences, but neither can offer me both. But PC gaming / Steam definitely now can.&lt;/p&gt;
&lt;p&gt;I'm in fact currently waiting for a bunch of (very expensive) gaming computer parts to arrive, hoping to have a brand-new rig ready for my summer holiday. If I'm lucky, I'll have my hands on those tomorrow. Sort of hard to argue that I'm not a PC gamer at this point and it's at least partially my Steam Deck's fault.&lt;/p&gt;
&lt;p&gt;I have to give it to Valve's business sense – releasing the Steam Deck, a good and affordable portable gaming PC, is probably a massive boon for their bottom line. I've kept track of how much money I've spent on PC games this year and the total comes to 572.88€. 451.59€ of that is directly within Steam and 121.29€ on third-party sites like &lt;a href="https://www.humblebundle.com/"&gt;Humble&lt;/a&gt; and &lt;a href="https://www.fanatical.com/"&gt;Fanatical&lt;/a&gt;. Not sure what cut Valve gets from third-party sites that deliver games as Steam activation keys, but with a 30% cut inside their own store, they've made at least 130€ off my 2023 Steam purchases alone.&lt;/p&gt;
&lt;p&gt;I'm actually pretty satisfied for having spent less than 600€ in about a half a year though. Compared to my previous console software spend, this is much cheaper. Possibly because I always opted for a physical console copy and that plastic ain't cheap. Maybe at some point my software savings will offset the huge amount of money that I'm throwing at the hardware.&lt;/p&gt;
&lt;p&gt;And since Steam is the first-party store on this device, I'm definitely avoiding all stores that are not Steam or don't give out Steam keys. You can probably get GOG and Epic Games Store working on this device but I really can't be bothered to. Not when the first-party experience is so good. So far I've picked up &lt;em&gt;Cyberpunk 2077&lt;/em&gt; for -50% on Steam after having bought it in 2021 from GOG, and despite Epic Games Store giving out free copies of &lt;em&gt;Control&lt;/em&gt; and &lt;em&gt;Death Stranding&lt;/em&gt;, I bought a 14.12€ eight-game bundle mainly for &lt;em&gt;Control&lt;/em&gt; and paid 9.99€ for a Humble Choice for &lt;em&gt;Death Stranding Director's Cut&lt;/em&gt;. I'm spending money that I didn't have to, just to have my gaming life consolidated on this one platform as much as possible. This is some truly next-level evil genius financial brilliance on Valve's part.&lt;/p&gt;
&lt;p&gt;But even though I've showered praise on Valve for the Steam Deck, there are still a couple of things about the Steam Deck that I don't like.&lt;/p&gt;
&lt;p&gt;What might be the most annoying part of using the Steam Deck portably for me is the fact that the display polariser is 90 degrees wrong. This means that it's completely impossible to use the Steam Deck while wearing sunglasses and I like to protect my eyes when I drag myself outdoors. If I'm out and about, for example sitting in the commuter train, and I want to play my Steam Deck, I'd need to find some place where to put my sunglasses for the duration. That sounds like a great way to either lose or destroy my sunglasses, which is why I opted to read a book instead during my last train ride. Apple has managed to make my iPhone work while wearing sunglasses, so I'm sure Valve could fix it too.&lt;/p&gt;
&lt;p&gt;The battery calibration also feels off on my device. At times it has sat at 99% charge for the longest time, and then started rapidly discharging afterwards. I've also had the device pop a low battery warning for 10% remaining, followed by the Steam Deck shutting itself off within 10 seconds. Afterwards it never charged the battery when I connected it to a power cord. It just stayed at 0% for over an hour and didn't start charging until I reconnected the power cable, forcibly shutting off the device in the process. Thankfully my Deck is never too far away from a power source in my usage so I've never suffered too badly, but this sort of a thing is still an annoying paper cut in the ownership experience.&lt;/p&gt;
&lt;p&gt;And whilst this isn't really a problem caused by the Steam Deck or Valve, I've also been annoyed when it has come to patching games. Some visual novels really need translation patches for a proper experience, but the Wine-based game install environment doesn't really facilitate that. Attempting to patch my copy of CHAOS;HEAD NOAH on the Steam Deck was such a pain in the neck that I think I'm just going to try copying over the patched game installation from my gaming PC to my Deck. Maybe in the future these patches will be made with considerations for the Steam Deck, but right now it's an annoyance.&lt;/p&gt;
&lt;p&gt;But still, for a 400-euro weird little first-generation gaming thingy, the Steam Deck is a hoot. I don't have any regrets about buying mine and I'd recommend it to anyone even remotely curious about getting one. During the latest Steam seasonal sale, the barrier of entry was even at just 377.10€, so the financial investment isn't too bad. Most likely you won't regret it and even if you do, you can probably load it off on the used market. Some weirdos do in fact buy used Steam Decks.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sun, 16 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/steam-deck-six-month-update/</guid></item><item><title>Bots, the second</title><link>https://rjp.is/blogging/posts/2023/07/bots-2/</link><description>In which we look at our second bot.</description><author>infrequent oscillations</author><pubDate>Wed, 12 Jul 2023 22:43:43 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/07/bots-2/</guid></item><item><title>We put a distributed database in the browser – and made a game of it</title><link>http://notes.eatonphil.com/2023-07-11-we-put-a-distributed-database-in-the-browse.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://tigerbeetle.com/blog/2023-07-11-we-put-a-distributed-database-in-the-browser/"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 11 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-07-11-we-put-a-distributed-database-in-the-browse.html</guid></item><item><title>CLI tip 30: extract only the matching portions</title><link>https://learnbyexample.github.io/tips/cli-tip-30/</link><description>&lt;p&gt;The &lt;code&gt;grep&lt;/code&gt; command provides the &lt;code&gt;-o&lt;/code&gt; option to extract only the matching portions. Here are some examples using the BRE/ERE regexp flavors:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# whole words made up of lowercase alphabets and digits only
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'coat Bin food Apple (tar12) best fig_42'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;owE &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[a-z0-9]+'
&lt;/span&gt;&lt;span&gt;coat
&lt;/span&gt;&lt;span&gt;food
&lt;/span&gt;&lt;span&gt;tar12
&lt;/span&gt;&lt;span&gt;best
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# extract characters from the start of string based on a delimiter
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple:123:banana:cherry' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;o &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'^[^:]*'
&lt;/span&gt;&lt;span&gt;apple
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# sequence of characters surrounded by double quotes
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'I like &amp;quot;mango&amp;quot; and &amp;quot;guava&amp;quot;' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;oE &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&amp;quot;[^&amp;quot;]+&amp;quot;'
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;mango&amp;quot;
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;guava&amp;quot;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# whole words that have at least one consecutive repeated character
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'effort flee facade oddball rat tool'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;owE &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\w*(\w)\1\w*'
&lt;/span&gt;&lt;span&gt;effort
&lt;/span&gt;&lt;span&gt;flee
&lt;/span&gt;&lt;span&gt;oddball
&lt;/span&gt;&lt;span&gt;tool
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here are some examples with the PCRE flavor:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# numbers &amp;gt;= 100 if there are leading zeros
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: grep -owE '0*[1-9][0-9]{2,}'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'0501 035 154 12 26 98234' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;woP &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'0*+\d{3,}'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0501
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;154
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;98234
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# extract digits only if it is preceded by - and not followed by ,
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 apple-5, fig3; x-83, y-20: f12'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;oP &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(?&amp;lt;=-)\d++(?!,)'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;20
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# extract digits that follow =
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple=42, fig=314' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;oP &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'=\K\d+'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;314
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# all digits and optional hyphen combo from the start of string
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'123-87-593 42 apple-12-345' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;oP &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\G\d+-?'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;123&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;87&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;593
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# all words except those surrounded by double quotes
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'I like2 &amp;quot;mango&amp;quot; and &amp;quot;guava&amp;quot;'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;oP &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&amp;quot;[^&amp;quot;]+&amp;quot;(*SKIP)(*F)|\w+'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;I
&lt;/span&gt;&lt;span&gt;like2
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;and
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;code&gt;ripgrep&lt;/code&gt; if you want to add some more text to the matching portions, or perhaps you need to handle multiple capture groups. Here's an example:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple=42, fig=314' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;o &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(\w+)=(\d+)' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$2:$1'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42:apple
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;314:fig
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my &lt;a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep"&gt;CLI text processing with GNU grep and ripgrep&lt;/a&gt; ebook if you are interested in learning about the &lt;code&gt;GNU grep&lt;/code&gt; and &lt;code&gt;ripgrep&lt;/code&gt; commands in more detail.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 11 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-30/</guid></item><item><title>Sending a Difficult Email</title><link>https://letterstoanewdeveloper.com/2023/07/10/sending-a-difficult-email/</link><description>Dear new developer, Sometimes you have to send a difficult or awkward email. But, before you send it, think about whether it should be an email or if an in-person conversation, phone call, or video call is best. If it is an awkward topic, it&amp;#8217;ll be an awkward conversation too, but the additional information bandwidth &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/07/10/sending-a-difficult-email/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Sending a Difficult&amp;#160;Email&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 10 Jul 2023 17:03:22 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/07/10/sending-a-difficult-email/</guid></item><item><title>Bots, part 1</title><link>https://rjp.is/blogging/posts/2023/07/bots/</link><description>In which we begin to enumerate some bots.</description><author>infrequent oscillations</author><pubDate>Mon, 10 Jul 2023 13:54:21 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/07/bots/</guid></item><item><title>Investor Coffee Story</title><link>https://jodavaho.io/posts/investor-coffee-story.html</link><description>&lt;blockquote&gt;
&lt;p&gt;Oh sweet, coffee&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One time, &lt;code&gt;$employer&lt;/code&gt; was having an all-hands event. During these events, everyone is in town from all over. I walked in the first day and saw a table of treats and gourmet coffee, and was like &amp;ldquo;oh sweet, coffee&amp;rdquo;. I walked up and was intercepted by someone who told me &amp;ldquo;Those are for the investors&amp;rdquo;. Of course the first thing out of my mouth was &amp;ldquo;I&amp;rsquo;m investing my life&amp;rdquo; like that was supposed to mean anything to him.&lt;/p&gt;
&lt;p&gt;Life is a funny little sequence of random occurrences that leads to me being escorted back to the rows of computer desks with an empty coffee cup in my hand while the VP of Engineering says &amp;ldquo;We&amp;rsquo;ll bring some over if there&amp;rsquo;s any left&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;To the best of my knowledge, &lt;code&gt;$employer&lt;/code&gt; does supply coffee to employees, and no, it&amp;rsquo;s not gourmet, but it is just fine. This is a story about me being dumb and missing the obvious signals that this was &lt;em&gt;special&lt;/em&gt; coffee for &lt;em&gt;special&lt;/em&gt; people. There it is. If I didn&amp;rsquo;t write that down I&amp;rsquo;d forget it sometday, and now it will live well past me (as long as someone pays my AWS bill). And yes, this was edited after &lt;code&gt;$employer.HR&lt;/code&gt; contacted me.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Mon, 10 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/investor-coffee-story.html</guid></item><item><title>Fixing AMD CPU Scaling on Fedora</title><link>https://smcleod.net/2023/07/fixing-amd-cpu-scaling-on-fedora/</link><description>Enabling the new AMD P-State Driver on Fedora with Kernel 6.4</description><author>smcleod.net</author><pubDate>Sun, 09 Jul 2023 09:41:37 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/07/fixing-amd-cpu-scaling-on-fedora/</guid></item><item><title>Brain &amp;amp; Background Processes</title><link>https://blog.bayindirh.io/blog/brain-background-processes/</link><description>&lt;p&gt;During the pandemic, I have found a nice application called &lt;a href="https://www.pagico.com"&gt;Pagico&lt;/a&gt;, which allowed me to dump everything in my mind, divide them into projects and actionable items. This process enabled me to plan my short term and medium term workload effortlessly, and see what I'm doing with my life.&lt;/p&gt;
&lt;p&gt;One of the things I done with Pagico was to create deadlines for almost all the tasks I added into it. Combined with the built-in Gantt chart, this method created a nice way to plan the week ahead. I was able to use this method for almost three years without any big problems.&lt;/p&gt;
&lt;p&gt;However, life being life, some tasks slipped, some went into back burner, but I never removed the dates from them, converting my tasks into a big rolling ball of mud, keeping it approximately the same size via disciplined effort.&lt;/p&gt;
&lt;p&gt;Recently, I started noticing problems about concentration, deep work and completing the same amount of tasks, causing this ball of mud to grow. I am not a workaholic, but my baseline performance slipped for this long for the first time and, this was bothering me a lot.&lt;/p&gt;
&lt;p&gt;After a week-long work travel and week-long vacation back to back, I returned to office and decided to write most important tasks into a list, on paper, returning to my old ways. I didn't open Pagico once, and used that list to finish my tasks. At the end of the week, not only I was able to finish them all, but I squeezed in what happened during that week, too. This made me remember a study I read, and gave me an idea for trying.&lt;/p&gt;
&lt;p&gt;The study has found a simple yet impactful fact: "Even if you don't see/use your open browser tabs, your mind is aware that they are there. In turn, you leak your concentration power with this awareness". Maybe, the same was true for my Pagico tasks, I concluded. They were not urgent, yet I attached dates to them, creating a sense of urgency. Because of this, my brain was trying to schedule these tasks at the background. In other words, I was making myself miserable by overloading myself for no reason.&lt;/p&gt;
&lt;p&gt;After realizing this, I removed dates from all tasks in Pagico, except a couple of hard deadlines I have about work, and I feel liberated. I am aware that these tasks are there, yet I have a much clearer vision about urgency of things at hand. I can visit the lists once a week, order them by urgency, add deadlines to things I want to tackle in that particular week, and leave the rest alone, inside Pagico. On the other hand, I'm not afraid to cancel things which are not progressing, because I don't have infinite time and selecting the battles I want to fight is an important part of resource planning. Because at the end life is a &lt;a href="https://oliveremberton.com/2014/life-is-a-game-this-is-your-strategy-guide/"&gt;resource management and strategy game&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next, I have to prune my 500+ open tabs at the office and bookmark what's important and close the rest.&lt;/p&gt;
&lt;p&gt;This small experiment and its profound effect made me remember that productivity is a journey, not a target. The ways, methods and strategies we use have to evolve after they fulfill their roles and carry us to the next level, to allow us to grow further and hopefully live a more fulfilling and enjoyable life.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Sat, 08 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/brain-background-processes/</guid></item><item><title>Up but how high?</title><link>https://rjp.is/blogging/posts/2023/07/up-and-reach/</link><description>In which we spoil the Ted Lasso S3 finale.</description><author>infrequent oscillations</author><pubDate>Thu, 06 Jul 2023 22:12:04 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/07/up-and-reach/</guid></item><item><title>2023–07–06: Sneak peek of rk2aw – what's comming up soon</title><link>https://xnux.eu/log/#089</link><author>megi's PinePhone Development Log</author><pubDate>Thu, 06 Jul 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#089</guid></item><item><title>Python tip 30: zip() function</title><link>https://learnbyexample.github.io/tips/python-tip-30/</link><description>&lt;p&gt;You can use the &lt;a href="https://docs.python.org/3/library/functions.html#zip"&gt;zip()&lt;/a&gt; builtin function to iterate over two or more iterables simultaneously. In every iteration, you'll get a &lt;code&gt;tuple&lt;/code&gt; with an item from each of the iterables. Here's an example:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;names &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Joe'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Mei'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Rose'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Ram'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;physics &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;86&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;91&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;76&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;80&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;maths &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;77&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;92&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;81&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;83&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; for &lt;/span&gt;&lt;span&gt;n, p, m &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;zip&lt;/span&gt;&lt;span&gt;(names, physics, maths):
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;f&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;{n&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;:5&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;: &lt;/span&gt;&lt;span&gt;{p}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;,&lt;/span&gt;&lt;span&gt;{m}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span&gt;Joe  : &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;86&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;77
&lt;/span&gt;&lt;span&gt;Mei  : &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;91&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;92
&lt;/span&gt;&lt;span&gt;Rose : &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;76&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;81
&lt;/span&gt;&lt;span&gt;Ram  : &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;80&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;83
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are some examples using list comprehensions and generator expressions:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;p &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;q &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;214&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;53&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[i &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+ &lt;/span&gt;&lt;span&gt;j &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;i, j &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;zip&lt;/span&gt;&lt;span&gt;(p, q)]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;217&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;58&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# inner product
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;sum&lt;/span&gt;&lt;span&gt;(i &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span&gt;j &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;i, j &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;zip&lt;/span&gt;&lt;span&gt;(p, q))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;910
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, &lt;code&gt;zip()&lt;/code&gt; will silently stop when the shortest iterable is exhausted:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;fruits &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'banana'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'guava'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;qty &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;25&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; for &lt;/span&gt;&lt;span&gt;f, q &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;zip&lt;/span&gt;&lt;span&gt;(fruits, qty):
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;f&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;{f&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;:6&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;: &lt;/span&gt;&lt;span&gt;{q}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span&gt;apple : &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100
&lt;/span&gt;&lt;span&gt;banana: &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;25
&lt;/span&gt;&lt;span&gt;fig   : &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;strict&lt;/code&gt; keyword argument was added in the Python 3.10 version. When set to &lt;code&gt;True&lt;/code&gt;, this will raise an exception if the iterables are not of the same length:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; for &lt;/span&gt;&lt;span&gt;f, q &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;zip&lt;/span&gt;&lt;span&gt;(fruits, qty, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;strict&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True&lt;/span&gt;&lt;span&gt;):
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;f&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;{f&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;:6&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;: &lt;/span&gt;&lt;span&gt;{q}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span&gt;apple : &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100
&lt;/span&gt;&lt;span&gt;banana: &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;25
&lt;/span&gt;&lt;span&gt;fig   : &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Traceback &lt;/span&gt;&lt;span&gt;(most recent call last):
&lt;/span&gt;&lt;span&gt;  File &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, line &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &amp;lt;&lt;/span&gt;&lt;span&gt;module&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="color: #a2a001;"&gt;ValueError&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;zip&lt;/span&gt;&lt;span&gt;() argument &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;is &lt;/span&gt;&lt;span&gt;shorter than argument &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://docs.python.org/3/library/itertools.html#itertools.zip_longest"&gt;itertools.zip_longest()&lt;/a&gt; and &lt;a href="https://stackoverflow.com/q/61126284/4082052"&gt;stackoverflow: zipped Python generators with 2nd one being shorter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 04 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-30/</guid></item><item><title>[Newbie Tip] What to do when you 403 during pypi twine upload</title><link>https://www.swyx.io/pypi-403</link><description>&lt;p&gt;I'm pretty new to the pypi packaging ecosystem so recently ran into some trouble pushing a python package:&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 03 Jul 2023 19:40:24 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/pypi-403</guid></item><item><title>What I'm up to - July 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-july-2023</link><description>&lt;div class="prose"&gt;
  &lt;div&gt;
&lt;strong&gt;✨ What I've been up to&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;We finished moving back to NYC last month. I made a quick stopover to Canada for &lt;a href="https://connect.tailwindcss.com/"&gt;Tailwind Connect&lt;/a&gt;, where I met some interesting people and  learned some new CSS techniques. I also visited Puerto Rico for a few days.&lt;strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;The book &lt;a href="https://www.amazon.com/Bowling-Alone-Robert-D-Putnam-audiobook/dp/B01N94FW0P/ref=sr_1_1?crid=1R6QAK9NVSWOB&amp;amp;keywords=bowling+alone&amp;amp;qid=1687785720&amp;amp;sprefix=%2Caps%2C1053&amp;amp;sr=8-1"&gt;Bowling Alone: The Collapse and Revival of American Community&lt;/a&gt; was a fantastic read. One thing I'm thinking about is &lt;strong&gt;telephones versus televisions&lt;/strong&gt; - telephones are bidirectional while TVs are one-directional. As a society, I think we under-appreciate how shift from telephones to TVs has driven a move toward more parasocial relationships.  Social media followed this trend - it started more telephone-like, promoting social relationships, but moved over time became more TV-like (epitomized by Tiktok) where people consume rather than create.&lt;br /&gt;&lt;strong&gt;&lt;br /&gt;🤔 Things to share&lt;/strong&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles&lt;/strong&gt;: &lt;a href="http://paulgraham.com/cities.html"&gt;Cities and Ambition&lt;/a&gt;. &lt;a href="https://catvalente.substack.com/p/stop-talking-to-each-other-and-start"&gt;How Parking Ruined Everything&lt;/a&gt;. &lt;a href="https://catvalente.substack.com/p/stop-talking-to-each-other-and-start"&gt;Stop Talking to Each Other and Start Buying Things: Three Decades of Survival in the Desert of Social Media&lt;/a&gt;. &lt;a href="http://paulgraham.com/greatwork.html"&gt;How to Do Great Work&lt;/a&gt; - "... the right strategy is to not plan too much".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books&lt;/strong&gt;: &lt;a href="https://www.amazon.com/gp/product/B00LFOUHFS"&gt;Watching the English&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps&lt;/strong&gt;: &lt;a href="https://apps.ankiweb.net/"&gt;Anki&lt;/a&gt;, &lt;a href="https://preply.com/en/home"&gt;Preply&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffees I'm drinking: &lt;/strong&gt;&lt;a href="https://www.heartroasters.com/products/honduras-encarnacion"&gt;Heart Honduras Encarnación Enamorado&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;
&lt;strong&gt;&lt;br /&gt;📫 What I'm up to this month&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;&lt;a href="https://booklet.community"&gt;Booklet&lt;/a&gt;, a new Contraption Co. product, is welcoming its first users this month. Stay tuned for more details.&lt;/div&gt;&lt;div&gt;&lt;strong&gt;📍 Where I'll be &lt;/strong&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;July 10-14: San Juan, PR 🇵🇷&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;
&lt;em&gt;(Let me know if we overlap!)&lt;br /&gt;&lt;br /&gt;&lt;/em&gt;&lt;strong&gt;📸 Photos&lt;/strong&gt;
&lt;/div&gt;&lt;div&gt;
&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbTliIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--8943b5860314ecea459f89498f6331c45801ca9f/IMG_8300.jpeg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Last dinner in Chicago&lt;/figcaption&gt;
&lt;/figure&gt;&lt;br /&gt;&lt;br /&gt;&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbTViIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ec8f3e2aab29ac39841f2c210360df8e1ae7e9e1/IMG_8596.jpeg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Puerto Rico&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Mon, 03 Jul 2023 18:51:08 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-july-2023</guid></item><item><title>A simple app for drawing Wardley Maps</title><link>http://akkartik.name/post/wardley</link><description>&lt;p&gt;
&lt;a href="https://git.sr.ht/~akkartik/wardley.love"&gt;wardley.love&lt;/a&gt; is a
reskin of &lt;a href="https://akkartik.name/post/snap"&gt;snap.love&lt;/a&gt; for drawing Wardley Maps.
I've been using it a lot; here's one example:

&lt;p&gt;
&lt;a href="https://akkartik.name/images/20230702-microblogging-wardley-map.png"&gt;
  &lt;img alt="A Wardley map showing current alternatives for microblogging, their dependencies, their dependencies' dependencies, and so on. Each alternative is arranged on a left-right spectrum, from custom tools to polished products to commodities." src="https://akkartik.name/images/20230702-microblogging-wardley-map.png" style="width: 80%; margin-left: 10%; border: 2px solid #aaaaff;" /&gt;
&lt;/a&gt;</description><author>Kartik Agaram</author><pubDate>Sun, 02 Jul 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">http://akkartik.name/post/wardley</guid></item><item><title>Using Whisper to Transcribe Podcasts</title><link>https://www.swyx.io/whisper-for-podcasts</link><description>&lt;h2&gt;Prerequisites&lt;/h2&gt;</description><author>swyx's site RSS Feed</author><pubDate>Sat, 01 Jul 2023 22:31:05 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/whisper-for-podcasts</guid></item><item><title>I don't think the $70 game is that expensive</title><link>https://burakku.com/blog/on-70-dollar-games/</link><description>&lt;p&gt;&lt;img alt="Shark Marin-chan!" src="thumbnail.png" /&gt;&lt;/p&gt;
&lt;p&gt;Time for some hot bourgeois takes.&lt;/p&gt;
&lt;p&gt;I recently ordered some manga from Book Depository right before they shut down all operations (RIP). In fact, I got five total volumes of &lt;a href="https://en.wikipedia.org/wiki/My_Dress-Up_Darling"&gt;My Dress-Up Darling&lt;/a&gt; in English, as the impending closure of the site prompted some good discounts: on average I paid 13.37 € per volume. On other online book retailers, each volume was closer to 17.50 €, and my previous Book Depository orders for the same series last year were also closer to 17 € than they were to 13 €.&lt;/p&gt;
&lt;p&gt;My Dress-Up Darling is great. It's funny, it's cute, it's romantic, it's well drawn. It's just all around great. Whenever I start reading a volume, I just gobble it up like a greedy pig. So much so that I decided to take out a timer and see how long it took me to finish the fifth volume.&lt;/p&gt;
&lt;p&gt;It took me just 35 minutes to finish the volume.&lt;/p&gt;
&lt;p&gt;If I use this amount of time with my favourite way of evaluating entertainment, euro per hours of enjoyment, My Dress-Up Darling is about 23 €/h even with the cheap pricing I got with Book Depository. With the more expensive prices on other sites, it'd be closer to 30 €/h. I love the manga but this still seems like a pretty awful money-to-time ratio.&lt;/p&gt;
&lt;p&gt;With this in mind, I decided to go through the list of games I've completed lately to get a comparison for the amount of entertainment I've sucked out of those games compared to the amount of money I paid for them.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Game&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Price&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Hours played&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Price/hour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Bastion&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.00 €¹&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17.7 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.00 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Ghostwire: Tokyo&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.00 €¹&lt;/td&gt;
&lt;td style="text-align: right;"&gt;39.4 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.00 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Jet Set Radio&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1.19 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.5 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.26 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Muv-Luv photonflowers*&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10.49 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10.1 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1.04 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Muv-Luv photonmelodies♮&lt;/td&gt;
&lt;td style="text-align: right;"&gt;12.49 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;29.1 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.43 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NEEDY GIRL OVERDOSE&lt;/td&gt;
&lt;td style="text-align: right;"&gt;9.30 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;11.2 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.83 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Synergia&lt;/td&gt;
&lt;td style="text-align: right;"&gt;~2.23 €²&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.7 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.60 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;THE DAY AFTER 00&lt;/td&gt;
&lt;td style="text-align: right;"&gt;8.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.5 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2.00 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;THE DAY AFTER 01&lt;/td&gt;
&lt;td style="text-align: right;"&gt;15.11 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;8.2 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1.84 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;THE DAY AFTER 02&lt;/td&gt;
&lt;td style="text-align: right;"&gt;15.11 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;6.7 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2.26 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;THE DAY AFTER 03&lt;/td&gt;
&lt;td style="text-align: right;"&gt;15.11 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;7.7 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1.96 €/h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ol style="font-size: 14px; padding: 0;"&gt;&lt;li&gt;Gift&lt;/li&gt;&lt;li&gt;Bought in a bundle&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;All of my recently finished games absolutely obliterate My Dress-Up Darling when it comes to the amount of entertainment for the price. However, they're all quite clearly old and cheap games. New games are expensive! Often going for $70 at the time as they're released!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But what if they were too?&lt;/em&gt; Well…&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Game&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Price/hour ($70)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Bastion&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$3.96/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Ghostwire: Tokyo&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$1.78/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Jet Set Radio&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$15.56/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Muv-Luv photonflowers*&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$6.93/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Muv-Luv photonmelodies♮&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$2.41/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NEEDY GIRL OVERDOSE&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$6.25/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Synergia&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$18.92/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;THE DAY AFTER 00&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$15.56/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;THE DAY AFTER 01&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$8.54/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;THE DAY AFTER 02&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$10.45/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;THE DAY AFTER 03&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$9.09/h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;At $70 per game, absolutely every single one of them becomes much worse value than what I actually paid for them. But even if we assume naive $1 = 1€ conversion, all of them are still cheaper for the amount of time I got out of them than that single volume of My Dress-Up Darling. Even Synergia, &lt;a href="../synergia-review"&gt;which I think is not worth the money or the time&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also have a bunch of unfinished games that I've dabbled with during the year. Let's see how they hold up in reality and against the $70 benchmark:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Game&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Price&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Hours played&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Price/hour&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Price/hour ($70)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;CHAOS;HEAD NOAH&lt;/td&gt;
&lt;td style="text-align: right;"&gt;6.29 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2.8 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2.25 €/h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$25.00/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Cyberpunk 2077 (Steam)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;29.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;30.8 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.97 €/h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$2.27/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Mass Effect Legendary Edition&lt;/td&gt;
&lt;td style="text-align: right;"&gt;49.79 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;31.3 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1.59 €/h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$2.24/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Mirror's Edge Catalyst&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;6.1 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.82 €/h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$11.48/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Muv-Luv Alternative Total Eclipse&lt;/td&gt;
&lt;td style="text-align: right;"&gt;23.79 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;5.3 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.49 €/h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$13.21/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Tokyo Xanadu eX+&lt;/td&gt;
&lt;td style="text-align: right;"&gt;~2.23 €¹&lt;/td&gt;
&lt;td style="text-align: right;"&gt;7.9 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.28 €/h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$8.86/h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Vampire Survivors&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14.8 h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.27 €/h&lt;/td&gt;
&lt;td style="text-align: right;"&gt;$4.73/h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ol style="font-size: 14px; padding: 0;"&gt;&lt;li&gt;Bought in a bundle&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Those again feel pretty reasonable when it comes to value. CHAOS;HEAD NOAH, a visual novel that I've only read for a couple hours so far, is the worst performer on the list and fares quite badly against the $70 benchmark. And yet, they still all fare better than my manga. And according to HowLongToBeat, I can expect 20+ hours from CHAOS;HEAD NOAH even with my speedreading, bringing the benchmark value in the $3.50/h range (0.31 €/h real value). So unless I drop any of these titles, the cost-per-hour is only going to become more and more reasonable.&lt;/p&gt;
&lt;p&gt;Granted, books do come with one big advantage: they don't require any additional purchases. It's a fully self-contained product. However, I feel like my manga collection still fares quite poorly value-wise even when accounting for the hardware. The last number I have for my Steam Deck playtime was around 110 hours. Having paid 400 € for mine, that translates to roughly 3.64 €/h. And that's with under half a year of ownership with a recluse that spends around 23 hours of their day at home. I imagine by the time that I replace the device, especially if I can still recoup some of its purchase price on the used market, that the final cost of ownership will become a rounding error. As long as I just don't break it.&lt;/p&gt;
&lt;p&gt;I also recently calculated that were I to throw my gaming PC into the lake right now, it would have had a price of 2.60 €/hour of gaming. And if I were able to sell it for half what I paid for it (which seems like a reasonable expectation looking at prices for used gaming PCs) then it'd drop to 1.30 €/h. I'm also one of those weirdos that has a completely dedicated PC for gaming, as my primary computer of choice is a Mac. I imagine most people gaming on PC do actually use their gaming PC as their primary computer anyways, so the value of the hardware over time becomes negligible.&lt;/p&gt;
&lt;p&gt;One additional piece of media that we can benchmark games against is movies. Finnkino movie tickets seem to range somewhere between 14.90 € and 22.00 €, and a movie is probably somewhere between 90 and 150 minutes in runtime. That gives us a range of 5.96 to 14.67 €/h. So had I paid $70 for my copy of NEEDY GIRL OVERDOSE, I'd still be looking at the cheaper end of the value spectrum when it comes to movie tickets. And Ghostwire: Tokyo pricing might not even cover the parking.&lt;/p&gt;
&lt;p&gt;And the thing is: it's very easy to not pay $70 for a game. Not a single game that I've listed here was bought for anywhere near $70. In my opinion, $70 is mostly just a tax that is levied against the impatient who must get the latest AAA game right at launch. Games go frequently on discount, and if you're a PC gamer, you have a pretty decent selection of storefronts to shop in.&lt;/p&gt;
&lt;p&gt;To quantify this in data, I arbitrarily chose some bigger releases from the past year and checked out what kind of savings are/were possible for them if you just waited for a bit using &lt;a href="https://gg.deals/"&gt;gg.deals&lt;/a&gt; (great website btw).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Game&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Launch price&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Current price&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Historical low&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Price drop&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Atomic Heart&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;39.59 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;39.59 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;34%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Call of Duty: Modern Warfare II&lt;/td&gt;
&lt;td style="text-align: right;"&gt;69.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;38.49 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;38.49 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;45%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Dead Island 2&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;43.79 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;35.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;27% (40%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Dead Space remake&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;41.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;35.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;30% (40%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Diablo IV&lt;/td&gt;
&lt;td style="text-align: right;"&gt;69.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;69.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;69.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Forspoken&lt;/td&gt;
&lt;td style="text-align: right;"&gt;79.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;39.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;39.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Hogwarts Legacy&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;40.65 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;40.65 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;32%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Marvel's Spider-Man Remastered&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;30.43 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;29.73 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;49% (50%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Redfall&lt;/td&gt;
&lt;td style="text-align: right;"&gt;69.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;55.74 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;52.49 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;20% (25%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Resident Evil 4 remake&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;44.39 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;38.69 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;26% (36%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Returnal&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;37.37 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;35.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;38% (40%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Star Wars Jedi: Survivor&lt;/td&gt;
&lt;td style="text-align: right;"&gt;69.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;52.49 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;52.49 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;25%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;The Callisto Protocol&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;29.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;26.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;50% (55%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;The Last of Us Part I&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;46.21 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;44.99 €&lt;/td&gt;
&lt;td style="text-align: right;"&gt;23% (25%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Looking at this data, I'd say it becomes quite clear that games do get pretty decent discounts within just a year of coming out. The only game on my list of recent major titles that wasn't discounted was &lt;em&gt;Diablo IV&lt;/em&gt; and that came out 25 days ago. Of course, that could also just a sign that the last year has had quite a lot of bad games (hello &lt;em&gt;Forspoken&lt;/em&gt; and &lt;em&gt;Redfall&lt;/em&gt;) and botched releases (looking at you, &lt;em&gt;Star Wars Jedi: Survivor&lt;/em&gt; and &lt;em&gt;The Last of Us Part I&lt;/em&gt;). But even the universally acclaimed Elden Ring (released 16 months ago) can be bought for 40% off its launch price right now. Just chip away at your backlog for some months and you can probably grab the next AAA release for a good discount.&lt;/p&gt;
&lt;p&gt;As far as I'm concerned, gaming is still an incredible value for money, even with the new $70 price tag for AAA releases. Just as long as you just play the damn games.&lt;/p&gt;
&lt;h3&gt;Collecting the dues&lt;/h3&gt;
&lt;p&gt;There is however something that I find to be incredibly bad value: collector's editions.&lt;/p&gt;
&lt;p&gt;For example, a copy of Tears of the Kingdom is currently available on a Finnish retailer's site for around 63 € but if you want the collector's edition, you need to pay 160 €. That is 97 € more for the collector's edition compared to the base game. Sure, Tears of the Kingdom might be a blockbuster that offers somewhere between 50 and 200 hours for a playthrough, but you can get that experience for just 63 €. So what does the 154% collector's edition markup give you?&lt;/p&gt;
&lt;p&gt;A 204-page hardcover artbook, a steelbook, a poster and four pins.&lt;/p&gt;
&lt;p&gt;Personally, I'm not really a fan of steelbooks, posters and pins. I think steelbooks look awkward among non-steelbooks on the shelf and they're not really even that valuable. You got a free steelbook upgrade if you pre-ordered Persona and got the launch day version. Not sure how many people like to hang posters but I'm definitely not one of them. And sure, this might be a premium steel poster, but how much can that be worth? &lt;a href="https://youtu.be/ZD4cRtz0ufc?t=558"&gt;It's not even that big.&lt;/a&gt; And do people outside of school children decorating their bags have a use for pins?&lt;/p&gt;
&lt;p&gt;The only thing that strikes me as desirable is the artbook, but even then this collector's edition seems like bad value. I actually bought the Persona 5 artbook when it came out and it's very thick at 432 pages. And how much did I pay for it back in 2017? Around 24 €. And the Zelda one is half the size for an almost 100 € upsell. It's closer to what I got with &lt;a href="https://www.amazon.com/Persona-4-Official-Design-Works/dp/1926778456"&gt;Persona 4 Official Design Works&lt;/a&gt;, which was 192 pages for 34 €, and that's probably with a reasonable markup since I bought it six years after release. (These days the English version of the Persona 4 art book seems to be fetching over 100€.)&lt;/p&gt;
&lt;p&gt;To me it seems like collector's editions are a way for game publishers to increase profit margins on games as base games have not kept up with inflation. The standard game price jumped to $60 during the PlayStation 3 era, and $60 in 2007 is worth about the same as $90 in 2023. Of course, they do need to make a couple more trinkets to stuff in those boxes for them to sell, but I've rarely seen a worthwhile collector's edition, at least one that doesn't cost an arm and a leg.&lt;/p&gt;
&lt;p&gt;Now I have to admit that I have actually bought a collector's edition for a game in the recent years. It was the Cyberpunk 2077 Collector's Edition for the PS4. It does come with quite a lot of stuff, although the value of some of them can be debated. Inside the massive box, you get the game, a steelbook, a world compendium, some postcards, a map, a collection of stickers, a metal keychain, some metal pins, a guide leaflet, embroidered patches, a 216-page hardcover artbook and a large figurine of male V on a motorcycle.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cyberpunk 2077 Collector's Edition" src="cyberpunk-ce.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I'm no expert on collector's editions but I reckon the Cyberpunk 2077 one includes a lot more stuff than your average collector's edition. It's somewhat debatable whether or not that stuff is good but it certainly has it. However, it's also quite a lot more expensive than your average collector's edition at 249 €. So why on earth did I get it? Well, because I got it for 79.95 € during a Black Friday sale. I guess the launch reception of the game really had an effect on its retail price. And since the game collector in me wanted to pick up a disc version of the atrocious 1.00 version of Cyberpunk 2077 anyways, I figured I might as well get the artbook, a cool keychain and a figurine at the same time. The base game hovered around 20 € at the time so it was really only 60 € more for those. What a bargain! Or so I tell myself at least.&lt;/p&gt;
&lt;p&gt;Still, I have limited love for these expensive collections of trinkets. I think I'm going to keep avoiding collector's editions and instead put my money towards standard versions of games – which I'll continue to mostly grab from various sales.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sat, 01 Jul 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/on-70-dollar-games/</guid></item><item><title>The Traction vs Trust Equation of Cofounder Dating</title><link>https://www.swyx.io/traction-vs-trust</link><description>&lt;p&gt;One of the most common problems in the beginning stage of a founder journey is establishing the cofounder group. I thought I would jot down some notes from my own experience + that of friends in similar situations.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 29 Jun 2023 04:20:24 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/traction-vs-trust</guid></item><item><title>CLI text processing with GNU sed book announcement</title><link>https://learnbyexample.github.io/cli-text-processing-sed-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;CLI text processing with GNU sed&lt;/strong&gt; ebook. Examples, exercises, solutions, descriptions and external links were added/updated/corrected.&lt;/p&gt;
&lt;p&gt;This book will help you learn the &lt;code&gt;GNU sed&lt;/code&gt; command step-by-step from beginner to advanced levels with &lt;strong&gt;hundreds of examples and exercises&lt;/strong&gt;. In addition to command options, &lt;strong&gt;regular expressions&lt;/strong&gt; will also be discussed in detail.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of &lt;strong&gt;CLI text processing with GNU sed&lt;/strong&gt; for FREE till 10-July-2023. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/gnu_sed"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/gnu_sed/c/new_sed_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other offers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/gnugrep_ripgrep"&gt;CLI text processing with GNU grep and ripgrep&lt;/a&gt; is FREE&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/new_sed_release"&gt;All Books Bundle&lt;/a&gt; is $12 (normal price $32) — all my 13 programming ebooks&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Command version updated to &lt;strong&gt;GNU sed 4.9&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Many more exercises added, and you can practice some of them using this &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/SedExercises"&gt;interactive TUI app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Long sections split into smaller ones&lt;/li&gt;
&lt;li&gt;In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;Book title changed to &lt;strong&gt;CLI text processing with GNU sed&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;li&gt;Images centered for EPUB format&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;On this blog, I &lt;a href="https://learnbyexample.github.io/tips/"&gt;post tips&lt;/a&gt; covering Python, command line tools and Vim. Here are video demos for these tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=THSMmCZQn1A&amp;amp;list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&amp;amp;list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="interactive-tui-app"&gt;Interactive TUI app&lt;a class="zola-anchor" href="#interactive-tui-app"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I also wrote an &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/SedExercises"&gt;interactive TUI app&lt;/a&gt; based on some of the exercises from the ebook. Reference solutions are also provided.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot from the interactive TUI app for sed exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/SedExercises/sed_exercises.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;In-place file editing&lt;/li&gt;
&lt;li&gt;Selective editing&lt;/li&gt;
&lt;li&gt;BRE/ERE Regular Expressions&lt;/li&gt;
&lt;li&gt;Flags&lt;/li&gt;
&lt;li&gt;Shell substitutions&lt;/li&gt;
&lt;li&gt;z, s and f command line options&lt;/li&gt;
&lt;li&gt;append, change, insert&lt;/li&gt;
&lt;li&gt;Adding content from file&lt;/li&gt;
&lt;li&gt;Control structures&lt;/li&gt;
&lt;li&gt;Processing lines bounded by distinct markers&lt;/li&gt;
&lt;li&gt;Gotchas and Tricks&lt;/li&gt;
&lt;li&gt;Further Reading&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/learn_gnused/"&gt;https://learnbyexample.github.io/learn_gnused/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/learn_gnused"&gt;https://github.com/learnbyexample/learn_gnused&lt;/a&gt; for markdown source, example files, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tips, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/learn_gnused/issues"&gt;https://github.com/learnbyexample/learn_gnused/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 29 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/cli-text-processing-sed-announcement/</guid></item><item><title>Premier League 2023</title><link>https://rjp.is/blogging/posts/2023/06/football-2023/</link><description>In which we analyse, loosely, the 2023 EPL.</description><author>infrequent oscillations</author><pubDate>Thu, 29 Jun 2023 00:19:43 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/06/football-2023/</guid></item><item><title>Audacity Tips</title><link>https://j11g.com/2023/06/28/audacity-tips/</link><description>This is a public note to myself for working with Audacity; which I don&amp;#8217;t do too often, and I want to make sure I don&amp;#8217;t forget it. I recently created a 5 hour music project: a DJ radio show. What I need from Audacity is: That&amp;#8217;s mostly it. Audacity is a very powerful tool, but [&amp;#8230;]</description><author>Jan van den Berg</author><pubDate>Wed, 28 Jun 2023 23:29:59 GMT</pubDate><guid isPermaLink="true">https://j11g.com/2023/06/28/audacity-tips/</guid></item><item><title>On "Software Engineering" circa 2023</title><link>https://mikewarot.blogspot.com/2023/06/on-software-engineering-circa-2023.html</link><description>I've been a programmer since the 1980s. I feel that the peak of the field was somewhere between 1995 and 2000. We had Windows 95/98, the Internet, and all programs were local applications run on a desktop, that people had gotten very productive on.&lt;br /&gt;&lt;br /&gt;The existence of Visual Basic and VBA support in the Microsoft Office Suite made it possible, and even practical, for most domain experts to build usable applications that allowed everyone to get their jobs done. If there were performance problems, or it needed to be made more reliable, professional programmers would be brought in to rebuild things in a more properly designed manner.... it was at this point that we almost shifted to being actual Software Engineers, and professionalized.&lt;br /&gt;&lt;br /&gt;Since then VB was cast into the pyre as a sacrifice to the very unnecessary migration to .Net, and the bloat that ensued as desktop programming lost a decade of productivity, people decided to just shove everything onto the web.&lt;br /&gt;&lt;br /&gt;It was only as this was starting to happen that Steve Jobs further crippled programming by introducing the iPhone, and suddenly GUI applications were expected to work on tiny screens (in either orientation) without proper input hardware like 3 button mice and keyboards, connected across a slow and unreliable network connection.&lt;br /&gt;&lt;br /&gt;Needless to say, the last 2 decades have been a total loss as far as programmer productivity goes, with one shining exception.... GIT. Git has its flaws, mostly arising when people don't realize it's a set of snapshots that fake storing deltas, and not the other way around.&lt;br /&gt;&lt;br /&gt;GIT/GitHub, et al... are fantastic. The ability to just keep multiple machines up to sync without hassle in seconds is sooooo good. I used to keep stacks of floppy disks with ZIP files of source code, all manually managed.&lt;br /&gt;&lt;br /&gt;In the future, we need to recover to the point where you can drag/drop GUI elements and have them work anywhere, like we were with VB/Delphi/Hypercard.&lt;br /&gt;&lt;br /&gt;When we get there, we'll let users build basic applications, and we can finally professionalize and apply actual engineering practices to the art of programming.&lt;br /&gt;&lt;br /&gt;Until then, &lt;b&gt;&lt;i style="background-color: #f4cccc;"&gt;please stop calling it engineering.&lt;/i&gt;&lt;/b&gt; We don't put in anywhere near the effort that &lt;a href="https://en.wikipedia.org/wiki/Margaret_Hamilton_(software_engineer)" target="_blank"&gt;Margaret Hamilton&lt;/a&gt; (the first actual Software Engineer) and crew did, in safely getting men to the moon. We're programmers, not Engineers. As Uncle Bob said, we don't profess anything. We certainly don't use engineering practices as described by The Engineer Guy.&lt;br /&gt;&lt;br /&gt;---&lt;br /&gt;&lt;br /&gt;Example: You can plug a lamp into an outlet, and in the US, it can draw up to 15 amperes, and under almost all circumstances, you can't damage the wiring in the house via a fault in the load.&lt;br /&gt;&lt;br /&gt;We have no equivalent in software. Chroot, sandboxes, etc... are far too unsafe. We have no standard way of letting the user choose resources to give to applications at run time.&lt;br /&gt;&lt;br /&gt;The worst part is, most people don't even see the deficiency. Imagine the current power grid with no fuses or circuit breakers.... the first wiring mistake would crash civilization.&lt;br /&gt;&lt;br /&gt;We can do better, we must do better.</description><author>--Mike--</author><pubDate>Tue, 27 Jun 2023 22:29:21 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2023/06/on-software-engineering-circa-2023.html</guid></item><item><title>Growth as a Software Engineer</title><link>https://healthydev.substack.com/p/growth-as-a-software-engineer</link><description>Key skills to go beyond senior</description><author>The Healthy Dev</author><pubDate>Mon, 26 Jun 2023 23:29:55 GMT</pubDate><guid isPermaLink="true">https://healthydev.substack.com/p/growth-as-a-software-engineer</guid></item><item><title>Pick up the dog poop</title><link>https://letterstoanewdeveloper.com/2023/06/26/pick-up-the-dog-poop/</link><description>Dear new developer, I was out for a run the other day. People walk dogs along my running route and I had noticed a few days before a bag of dog poop. I had run on by, assuming someone was planning to grab it on the way back from their walk. But then I saw &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/06/26/pick-up-the-dog-poop/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Pick up the dog&amp;#160;poop&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 26 Jun 2023 14:51:50 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/06/26/pick-up-the-dog-poop/</guid></item><item><title>Muv-Luv photonmelodies♮ review</title><link>https://burakku.com/blog/muv-luv-photonmelodies-review/</link><description>&lt;p&gt;&lt;img alt="Muv-Luv photonmelodies♮" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Time for the second Muv-Luv side story collection. This time with three stories: Adoration, Resurrection and Altered Fable.&lt;/p&gt;
&lt;h3&gt;Adoration&lt;/h3&gt;
&lt;p&gt;Adoration is described as "a rare glimpse into what the world of Muv-Luv Alternative is like outside of Japan", taking place in Dover, England and focusing around the Cerberus battalion. However, it is not a hardboiled European military action as you might believe from the summary. Rather, it's a lighthearted comedy starring Makabe Seijuro, a man with a hundred elder brothers and an off-brand Lelouch.&lt;/p&gt;
&lt;p&gt;That being said, Adoration is a fun read. I found myself grinning quite a bit when the serious Royal Guard cadet faces absurd situations with the German troops. Big-time Alternative fans looking for a serious character drama might not enjoy their stay, but if you enjoyed Extra, you might enjoy the adventures of Cadet Makabe. Budget Lelouch also encounters lucky pervert situations at a pace that might make Shirogane jealous, so you have some ecchi action to entertain you as well.&lt;/p&gt;
&lt;p&gt;Nonetheless, there are actually serious moments of character development as well. It might be a fairly brief read, clocking at around four hours to completion, but Makabe does in fact grow as part of the story. And the rest of the cast, while not experiencing the same kind of a character arc, are extremely likable. Even the one "asshole" of the story is still a likable dude.&lt;/p&gt;
&lt;p&gt;The writer also seems to have exercise every opportunity to cram in references to other in vogue series. I'm not calling Makabe an off-brand Lelouch for nothing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Adoration" src="adoration.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Adoration is approximately four hours long, which is fairly good for a side story.&lt;/p&gt;
&lt;h3&gt;Resurrection&lt;/h3&gt;
&lt;p&gt;From the beginning of Adoration, I figured that it'd be a fairly serious and somber story, much akin to &lt;em&gt;Rain Dancers&lt;/em&gt; in &lt;em&gt;photonflowers*&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;It's definitely not that. It's in fact probably the most moronic that Muv-Luv has ever gotten. It's exceedingly absurd and silly.&lt;/p&gt;
&lt;p&gt;That being said, Resurrection is not a bad read. As long as you are not expecting a story as serious as the beginning would suggest, you'll probably find some amusement in it. However, it is very hard to take seriously – and the story tries to have some serious parts to it, which are pretty hard to digest given how stupid everything that became before it was.&lt;/p&gt;
&lt;p&gt;Resurrection also features very good art and animation. Kozuki and the Valkyries play a pretty big part in the story, and they all get new sprites that I found vastly superior to their old ones. And animating the sprites when it came to stuff like throwing punches is definitely competing for the top places when it comes to Muv-Luv stories. I think the only other Muv-Luv story that might rival it is Total Eclipse (which I've sampled for a couple of hours in between reading photonmelodies♮).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Resurrection" src="resurrection.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Resurrection was approximately seven and a half hours long, so a good chunk longer than Adoration.&lt;/p&gt;
&lt;h3&gt;Altered Fable&lt;/h3&gt;
&lt;p&gt;Altered Fable is the meat and potatoes of the entire package, taking up over 12 hours of my time to finish. But while it might be the main dish of photonmelodies♮, it feels more like a dessert for Muv-Luv fans. And while it's the direct sequel to Muv-Luv Alternative (at least until Muv-Luv INTEGRATE comes out to muddy the waters), there's not really a central plot or discernible character development. It's more like a bunch of random events strung together. Definitely not required reading, but I wouldn't expect that from a side story collection anyways.&lt;/p&gt;
&lt;p&gt;That is not to say that there's zero plot. There is an underlying story but it really only surfaces every once in a while with a conclusion at the end. Otherwise it's just random Extraverse romps, but now with a much larger cast than you had in Extra. At least that underlying story does explain a lot of events that happen during Altered Fable that otherwise seem like really convenient coincidences to tie it into Alternative.&lt;/p&gt;
&lt;p&gt;Altered Fable also has pretty intricate routing for a side story, even though there's not really routes per se. The main story is fixed, and you can really only affect what small events play during it and which of the girls you get to spend time with. Personally, I would've loved a full on Kashiwagi route but I'll take the little scraps I get here.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Kashiwagi" src="kashiwagi.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Going for 100% completion was kind of a pain though, and took me over nine hours of additional reading/skipping, as there's quite a bit of additional content beyond a single playhrough and it's not really clear what choices unlock what. And while I couldn't find a route flowchart, &lt;a href="https://steamcommunity.com/sharedfiles/filedetails/?id=2183622330"&gt;there is thankfully a walkthrough&lt;/a&gt;. Trying to get the full gallery unlocked without it was a fool's errand.&lt;/p&gt;
&lt;p&gt;But ignoring the pain of being a completionist, Altered Fable was definitely a treat. A moment of fan service to sunset the adventures of Takeru. Just don't take it too seriously.&lt;/p&gt;
&lt;h3&gt;photonmelodies♮&lt;/h3&gt;
&lt;p&gt;While photonmelodies♮ features less stories than the previous photonflowers*, it feels like a tighter package. Many of the stories in photonflowers* were very short, and you couldn't really get into the stories in such a time. In contrast the shortest story in photonmelodies♮ is a respectable four hours, and the total package has at least 20-30 hours of content.&lt;/p&gt;
&lt;p&gt;Technically photonmelodies♮ is safe and sound. While I prefer the Ages Mark 2 engine (used in THE DAY AFTER and Total Eclipse remasters) for its text rendering and some of its options, the venerable rUGP performs fine and runs basically perfect on the Steam Deck. In fact, it doesn't even suffer from the audio crackling bug that I've experienced with the Ages Mark 2 titles.&lt;/p&gt;
&lt;p&gt;Muv-Luv photonmelodies♮ is a pretty easy recommendation to fans of Muv-Luv, and especially for those that enjoyed photonflowers*.&lt;/p&gt;</description><author>ブラック</author><pubDate>Mon, 26 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/muv-luv-photonmelodies-review/</guid></item><item><title>Vim tip 28: miscellaneous motion and reposition commands</title><link>https://learnbyexample.github.io/tips/vim-tip-28/</link><description>&lt;p&gt;Moving within the visible window:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;H&lt;/kbd&gt; move to the first non-blank character of the top (home) line of the visible window&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;M&lt;/kbd&gt; move to the first non-blank character of the middle line of the visible window&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;L&lt;/kbd&gt; move to the first non-blank character of the bottom (low) line of the visible window&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reposition the current line:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;e&lt;/kbd&gt; scroll up by a line&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;y&lt;/kbd&gt; scroll down by a line&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;zz&lt;/kbd&gt; reposition the current line to the middle of the visible window
&lt;ul&gt;
&lt;li&gt;useful to see context around lines that are nearer to the top/bottom of the visible window&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;zt&lt;/kbd&gt; reposition the current line to the top of the visible window&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;zb&lt;/kbd&gt; reposition the current line to the bottom of the visible window&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://vimhelp.org/options.txt.html#%27scrolloff%27"&gt;:h 'scrolloff'&lt;/a&gt; option if you want to always show context around the current line.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 26 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-28/</guid></item><item><title>Generating SQL with LLMs for fun and profit</title><link>https://iamnotarobot.substack.com/p/generating-sql-with-llms-for-fun</link><description>Do you trust a language model to generate and execute code on the fly? We need to talk.</description><author>I Am Not a Robot</author><pubDate>Fri, 23 Jun 2023 02:39:14 GMT</pubDate><guid isPermaLink="true">https://iamnotarobot.substack.com/p/generating-sql-with-llms-for-fun</guid></item><item><title>Leadership models V: The Hero's Journey</title><link>https://bytepawn.com/leadership-models-the-heros-journey.html</link><description>&lt;p&gt;The Hero's Journey, or Monomyth, is a narrative pattern identified by scholar Joseph Campbell that appears across a wide range of cultures and eras, and is also a useful mental model in Leadership.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Peter principle" src="https://writerswrite.co.za/wp-content/uploads/2017/02/heros_journey.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 23 Jun 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/leadership-models-the-heros-journey.html</guid></item><item><title>CLI tip 29: define fields using FPAT in GNU awk</title><link>https://learnbyexample.github.io/tips/cli-tip-29/</link><description>&lt;p&gt;In &lt;code&gt;awk&lt;/code&gt;, the &lt;code&gt;FS&lt;/code&gt; variable allows you to define the input field &lt;em&gt;separator&lt;/em&gt;. In contrast, &lt;code&gt;FPAT&lt;/code&gt; (field pattern) allows you to define what should the fields be made up of.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sample123string42with777numbers'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# one or more consecutive digits
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;FPAT&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[0-9]+' '{print $2}'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'coat Bin food tar12 best Apple fig_42'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# whole words made up of lowercase alphabets and digits only
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;FPAT&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;lt;[a-z0-9]+&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;gt;' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;OFS&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span&gt;coat,food,tar12,best
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'items: &amp;quot;apple&amp;quot; and &amp;quot;mango&amp;quot;'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# get the first double quoted item
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;FPAT&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&amp;quot;[^&amp;quot;]+&amp;quot;' '{print $1}'
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;apple&amp;quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;FPAT&lt;/code&gt; is often used for CSV input where fields can contain embedded delimiter characters. For example, a field content &lt;code&gt;&amp;quot;fox,42&amp;quot;&lt;/code&gt; when &lt;code&gt;,&lt;/code&gt; is the delimiter.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'eagle,&amp;quot;fox,42&amp;quot;,bee,frog'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# simply using , as separator isn't sufficient
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;F&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{print $2}'
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;fox
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For such simpler CSV input, &lt;code&gt;FPAT&lt;/code&gt; helps to define fields as starting and ending with double quotes or containing non-comma characters.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# * is used instead of + to allow empty fields
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;FPAT&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&amp;quot;[^&amp;quot;]*&amp;quot;|[^,]*' '{print $2}'
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;fox,42&amp;quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="warning" src="/images/warning.svg" /&gt; The above will not work for all kinds of CSV files, for example if fields contain escaped double quotes, newline characters, etc. See &lt;a href="https://stackoverflow.com/q/45420535/4082052"&gt;stackoverflow: What's the most robust way to efficiently parse CSV using awk?&lt;/a&gt; for such cases. You could also use other programming languages such as Perl, Python, Ruby, etc which come with standard CSV parsing libraries or have easy access to third party solutions. There are also specialized command line tools such as &lt;a href="https://github.com/BurntSushi/xsv"&gt;xsv&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;CLI text processing with GNU awk&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 20 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-29/</guid></item><item><title>Metaprogramming in Zig and parsing CSS</title><link>http://notes.eatonphil.com/2023-06-19-metaprogramming-in-zig-and-parsing-css.html</link><description>&lt;p&gt;I knew Zig supported some sort of reflection on types. But I had been
confused about how to use it. What's the difference between
&lt;code&gt;@typeInfo&lt;/code&gt; and &lt;code&gt;@TypeOf&lt;/code&gt;? I ignored this aspect of Zig until a
problem came up at &lt;a href="https://tigerbeetle.com"&gt;work&lt;/a&gt; where reflection
made sense.&lt;/p&gt;
&lt;p&gt;The situation was parsing and storing parsed fields in a struct. Each
field name that is parsed should match up to a struct field.&lt;/p&gt;
&lt;p&gt;This is a fairly common problem. So this post walks through how to use
Zig's metaprogramming features in a simpler but related domain:
parsing CSS into typed objects, and pretty-printing these typed CSS
objects.&lt;/p&gt;
&lt;p&gt;I live-streamed the implementation of this project yesterday on
&lt;a href="https://www.twitch.tv/eatonphil"&gt;Twitch&lt;/a&gt;. The video is &lt;a href="https://youtube.com/@eatonphil"&gt;available on
YouTube&lt;/a&gt;. And the source is &lt;a href="https://github.com/eatonphil/zig-metaprogramming-css-parser"&gt;available
on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to skip the parsing steps and just see the
metaprogramming, jump to the implementation of
&lt;a href="#&amp;lt;code&amp;gt;match_property&amp;lt;/code&amp;gt;"&gt;match_property&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="parsing-css"&gt;Parsing CSS&lt;/h3&gt;&lt;p&gt;Let's imagine a CSS that only has alphabetical selectors, property
names and values.&lt;/p&gt;
&lt;p&gt;The following would be valid:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Thinking about the structure of this stripped down CSS we've got:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CSS properties that consist of property names and values (in our case the property names are limited to &lt;code&gt;background&lt;/code&gt; and &lt;code&gt;color&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;CSS rules that have a selector and a list of rules&lt;/li&gt;
&lt;li&gt;CSS sheets that have a list of rules&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Turning that into Zig in &lt;code&gt;main.zig&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&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;&amp;quot;std&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;union&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CSSRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CSSSheet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;CSSRule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The parser is going to look for CSS rules which contain a selector and
a list of CSS rules. The entrypoint is that simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &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="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArenaAllocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;CSSSheet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSRule&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arena&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="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Parse rules until EOF.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// In case there is trailing whitespace before the EOF,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// eating whitespace here makes sure we exit the loop&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// immediately before trying to parse more rules.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eat_whitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CSSSheet&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's implement the &lt;code&gt;eat_whitespace&lt;/code&gt; helper we've referenced. It
increments a cursor into the css file while it sees whitespace.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eat_whitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ascii&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isWhitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In our stripped-down version of CSS all we have to think about is
ASCII. So the builtin &lt;code&gt;std.ascii.isWhitespace()&lt;/code&gt; function is perfect.&lt;/p&gt;
&lt;p&gt;Next, parsing CSS rules.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;parse_rule()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;parse_rule()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;A rule consists of a selector, opening curly braces, any number of
properties, and closing curly braces. We need to remember to eat
whitespace between each piece of syntax.&lt;/p&gt;
&lt;p&gt;And we'll reference a few more parsing helpers we'll talk about next
for the selector, braces, and properties.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ParseRuleResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CSSRule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &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="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArenaAllocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ParseRuleResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eat_whitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// First parse selector(s).&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selector_res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_identifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selector_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eat_whitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Then parse opening curly brace: {.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_syntax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'{'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eat_whitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arena&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="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Then parse any number of properties.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eat_whitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;attr_res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;attr_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eat_whitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Then parse closing curly brace: }.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_syntax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ParseRuleResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CSSRule&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selector_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;parse_syntax&lt;/code&gt; helper is pretty simple, it does a bounds check and
increments the cursor if the current character matches the one you
pass in.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_syntax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;syntax&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;debug_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Expected syntax: '{c}'.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoSuchSyntax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This calls attention to debugging messages on failure. When we
fail to parse a syntax, we want to give a useful error message and
point at the exact line and column of code where the error happens.&lt;/p&gt;
&lt;p&gt;So let's implement &lt;code&gt;debug_at&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;debug_at&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;debug_at&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;First, we iterate over the entire CSS source code until we find the
entire line that contains the index where the parser failed. We also
want to identify the exact line and column corresponding to that
index.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;debug_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;comptime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;anytype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line_no&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;col_no&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line_beginning&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;found_line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;found_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;col_no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;line_beginning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;line_no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;found_line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;found_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;col_no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we print it all out in a nice format for users (which will likely
just be ourselves).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Error at line {}, column {}. &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line_no&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;col_no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line_beginning&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="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col_no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col_no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;^ Near here.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok, popping our mental stack, if we look back at &lt;code&gt;parse_rule&lt;/code&gt; we still
need to implement &lt;code&gt;parse_identifier&lt;/code&gt; and &lt;code&gt;parse_property&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;parse_identifier&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;parse_identifier&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;An "identifier" for us here is just going to be an ASCII alphabetical
string (i.e. &lt;code&gt;[a-zA-Z]+&lt;/code&gt;).  We're going to &lt;em&gt;really&lt;/em&gt; simplify CSS
because we're going to use this method for parsing not just selectors
but property names and even property values.&lt;/p&gt;
&lt;p&gt;Zig again has a nice builtin &lt;code&gt;std.ascii.isAlphabetical&lt;/code&gt; we can use.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ParseIdentifierResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_identifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ParseIdentifierResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ascii&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isAlphabetic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;debug_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Expected valid identifier.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidIdentifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ParseIdentifierResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In reality, CSS properties are &lt;a href="https://www.w3schools.com/cssref/css_selectors.php"&gt;highly
complex&lt;/a&gt;. Parsing
CSS correctly isn't the main aim of this post though. :)&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;parse_property&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;parse_property&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;The final piece of CSS we need to parse is properties. These consist
of a property name, then a colon, then a property value, and finally a
semicolon. And within each piece we eat whitespace.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ParsePropertyResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ParsePropertyResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eat_whitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// First parse property name.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name_res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_identifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Could not parse property name.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eat_whitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Then parse colon: :.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_syntax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eat_whitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Then parse property value.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value_res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_identifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Could not parse property value.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Finally parse semi-colon: ;.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_syntax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;';'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;debug_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Unknown property: '{s}'.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;name_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ParsePropertyResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally we get to the first bit of metaprogramming. Once we have a
property name and value, we need to turn that into a Zig union.&lt;/p&gt;
&lt;p&gt;That's what &lt;code&gt;match_property()&lt;/code&gt; is going to be responsible for doing.&lt;/p&gt;
&lt;h3 id="&amp;lt;code&amp;gt;match_property&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;match_property&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;This function needs to take a property name and value and return a
&lt;code&gt;CSSProperty&lt;/code&gt; with the correct field (matching up to the property name
passed in) and assigned to the value passed in.&lt;/p&gt;
&lt;p&gt;If we didn't have metaprogramming or reflection, the implementation
might look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;color&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;background&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnknownProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that is not necessarily bad. In fact it may be how a lot of
production code looks over time as product needs evolve. You can keep
the internal field name unrelated to the external field name.&lt;/p&gt;
&lt;p&gt;However for the sake of learning, we'll try to implement the same
thing with Zig metaprogramming.&lt;/p&gt;
&lt;p&gt;And specifically, we can take a look at
&lt;a href="https://github.com/ziglang/zig/blob/32cb9462ffa0a9df7a080d67eaf3a5762173f742/lib/std/json/static.zig"&gt;lib/std/json/static.zig&lt;/a&gt;
to understand the reflection APIs.&lt;/p&gt;
&lt;p&gt;Specifically, if we look at line 210-226 of that file, we can see them
iterating over fields of a &lt;code&gt;Union&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;unionInfo&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;comptime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trait&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hasFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;jsonParse&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jsonParse&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unionInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@compileError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Unable to parse into untagged union '&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@typeName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;'&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;object_begin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnexpectedToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name_token&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nextAllocMax&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alloc_if_needed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_value_len&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="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name_token&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="kr"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocated_string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnexpectedToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unionInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then right after that (lines 226-243) we see them conditionally
modifying the result object:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kr"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unionInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;// Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;// (Recursing into parseInternal() might trigger more allocations.)&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;freeAllocated&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name_token&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="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;name_token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="c1"&gt;// void isn't really a json type, but we can support void payload union tags with {} as a value.&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;object_begin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnexpectedToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;object_end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnexpectedToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@unionInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="c1"&gt;// Recurse.&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@unionInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parseInternal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can see that the &lt;code&gt;.Union =&amp;gt; |unionInfo|&lt;/code&gt; condition is entered by
switching on &lt;code&gt;@typeInfo(T)&lt;/code&gt; (&lt;a href="https://github.com/ziglang/zig/blob/32cb9462ffa0a9df7a080d67eaf3a5762173f742/lib/std/json/static.zig#L149"&gt;line
149&lt;/a&gt;)
and that &lt;code&gt;T&lt;/code&gt; is a type (&lt;a href="https://github.com/ziglang/zig/blob/32cb9462ffa0a9df7a080d67eaf3a5762173f742/lib/std/json/static.zig#L144"&gt;line
144&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;We don't have a generic type though. We know we are working with a
&lt;code&gt;CSSProperty&lt;/code&gt;. And we know &lt;code&gt;CSSProperty&lt;/code&gt; is a union so we don't need
the &lt;code&gt;switch&lt;/code&gt; either.&lt;/p&gt;
&lt;p&gt;So let's apply that to our &lt;code&gt;match_property&lt;/code&gt; implementation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cssPropertyInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@typeInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cssPropertyInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@unionInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnknownProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And if we try to build that we'll get an error like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;builtin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnionField&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;must&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;comptime&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;known&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;known&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cssPropertyInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Zig's "reflection" abilities here are comptime only. So we can't use a
runtime &lt;code&gt;for&lt;/code&gt; loop, we must use a comptime &lt;code&gt;inline for&lt;/code&gt; loop.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cssPropertyInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@typeInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cssPropertyInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@unionInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnknownProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As far as I understand it, this loop is basically unrolled and the
generated code would look a lot like our hard-coded initial version.&lt;/p&gt;
&lt;p&gt;i.e. it would probably look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cssPropertyInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@typeInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;background&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@unionInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;background&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;color&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@unionInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;color&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@unionInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnknownProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Again that's just how I imagine the compiler to generate code from the
Union field reflection and &lt;code&gt;inline for&lt;/code&gt; over the fields.&lt;/p&gt;
&lt;p&gt;Try compiling that code. I get this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;main.zig:17:58: error: expected type 'void', found '[]const u8'&lt;/span&gt;
&lt;span class="go"&gt;            return @unionInit(CSSProperty, u_field.name, value);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Thinking about the generated code makes it especially clear what's
happening. We have an &lt;code&gt;unknown&lt;/code&gt; field in there that has a &lt;code&gt;void&lt;/code&gt;
type. You can't assign a string to void.&lt;/p&gt;
&lt;p&gt;We know at runtime that the condition where that happens should be
impossible because the user shouldn't enter &lt;code&gt;unknown&lt;/code&gt; as a property
name. (Though now that I write this, I see they actually could. But
let's pretend they wouldn't.)&lt;/p&gt;
&lt;p&gt;So the problem isn't a runtime failure but a comptime type-checking
failure.&lt;/p&gt;
&lt;p&gt;Thankfully we can work around this with comptime conditionals.&lt;/p&gt;
&lt;p&gt;If we wrap our current condition in an additional conditional that is
evaluated at comptime and filters out the &lt;code&gt;unknown&lt;/code&gt; pass of the
&lt;code&gt;inline for&lt;/code&gt; loop, the compiler shouldn't generate any code trying to
assign to the &lt;code&gt;unknown&lt;/code&gt; field.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cssPropertyInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@typeInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cssPropertyInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;comptime&lt;/span&gt;&lt;span class="w"&gt; &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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@unionInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnknownProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And indeed, if you try to compile it, this works. Since the
conditional is evaluated at compile time, we can imagine the code the
compiler generates is this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cssPropertyInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@typeInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;background&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@unionInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;background&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;color&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@unionInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;color&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnknownProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;unknown&lt;/code&gt; field has been skipped.&lt;/p&gt;
&lt;p&gt;In retrospect, I realize that the &lt;code&gt;unknown&lt;/code&gt; field probably isn't
even needed. We could eliminate it from the &lt;code&gt;CSSProperty&lt;/code&gt; union and
get rid of that comptime conditional. However, sometimes there are in
fact private fields you want to skip. And I wanted to show how to
deal with that case.&lt;/p&gt;
&lt;p&gt;For the last bit of metaprogramming, let's talk about displaying
the resulting &lt;code&gt;CSSSheet&lt;/code&gt; we'd get after parsing.&lt;/p&gt;
&lt;h3 id="&amp;lt;code&amp;gt;sheet.display()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;sheet.display()&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;If we didn't have metaprogramming and wanted to display the sheet,
we'd have to switch on every possible union field.&lt;/p&gt;
&lt;p&gt;Like so (I've modified the &lt;code&gt;CSSSheet&lt;/code&gt; struct definition so it includes this method):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CSSSheet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;selector: {s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unknown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;color_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;  color: {s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;color_value&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;background_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;  background: {s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;background_value&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is already a little annoying and could get unwieldy as we add
fields to the &lt;code&gt;CSSProperty&lt;/code&gt; union.&lt;/p&gt;
&lt;p&gt;Instead we can use the &lt;code&gt;inline for
(@typeInfo(CSSProperty).Union.fields) |u_field|&lt;/code&gt; method to iterate
over all fields, skip the &lt;code&gt;unknown&lt;/code&gt; field at comptime, and print out
the field name and value by matching on the current value of the
&lt;code&gt;property&lt;/code&gt; enum by using the &lt;code&gt;@tagName&lt;/code&gt; builtin.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CSSSheet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;selector: {s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="kr"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;@typeInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSSProperty&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;comptime&lt;/span&gt;&lt;span class="w"&gt; &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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&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="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;@tagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;  {s}: {s}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="nb"&gt;@tagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="nb"&gt;@field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="&amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;main&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Finally, we pull it all together with a little &lt;code&gt;main&lt;/code&gt; function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArenaAllocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&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="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_allocator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arena&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="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Let's read in a CSS file.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Skips the program name.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getEndPos&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file_size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;css_file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;css_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And try it against some tests.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;zig&lt;span class="w"&gt; &lt;/span&gt;build-exe&lt;span class="w"&gt; &lt;/span&gt;main.zig
&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;tests/basic.css
&lt;span class="go"&gt;div {&lt;/span&gt;
&lt;span class="go"&gt;    background: white;&lt;/span&gt;
&lt;span class="go"&gt;}&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;tests/basic.css
&lt;span class="go"&gt;selector: div&lt;/span&gt;
&lt;span class="go"&gt;  background: white&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nice! Let's try it against a more complex test.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;tests/multiple-blocks.css
&lt;span class="go"&gt;div {&lt;/span&gt;
&lt;span class="go"&gt;    background: black;&lt;/span&gt;
&lt;span class="go"&gt;    color: white;&lt;/span&gt;
&lt;span class="go"&gt;}&lt;/span&gt;

&lt;span class="go"&gt;a {&lt;/span&gt;
&lt;span class="go"&gt;    color: blue;&lt;/span&gt;
&lt;span class="go"&gt;}&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;tests/multiple-blocks.css
&lt;span class="go"&gt;selector: div&lt;/span&gt;
&lt;span class="go"&gt;  background: black&lt;/span&gt;
&lt;span class="go"&gt;  color: white&lt;/span&gt;

&lt;span class="go"&gt;selector: a&lt;/span&gt;
&lt;span class="go"&gt;  color: blue&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Awesome. And against a bad CSS sheet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;tests/bad-property.css
&lt;span class="go"&gt;a {&lt;/span&gt;
&lt;span class="go"&gt;    big: pink;&lt;/span&gt;
&lt;span class="go"&gt;}&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;./main&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;tests/bad-property.css
&lt;span class="go"&gt;Error at line 2, column 4. Unknown property: 'big'.&lt;/span&gt;


&lt;span class="go"&gt;    big: pink;&lt;/span&gt;
&lt;span class="go"&gt;    ^ Near here.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We've got it!&lt;/p&gt;
&lt;h3 id="addendum:-&amp;lt;code&amp;gt;@field&amp;lt;/code&amp;gt;"&gt;Addendum: &lt;code&gt;@field&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;The docs were quite clear about using &lt;code&gt;@field(object, fieldName)&lt;/code&gt; to
access the value of an &lt;code&gt;object&lt;/code&gt; of type &lt;code&gt;@TypeOf(object)&lt;/code&gt; at field
&lt;code&gt;fieldName&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And the docs do mention &lt;code&gt;@field()&lt;/code&gt; can be used as LHS but that only
really struct me when I was browsing the Zig JSON code like at &lt;a href="https://github.com/ziglang/zig/blob/master/lib/std/json/static.zig#L307"&gt;line
307&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I didn't use that in this little project but I've used it elsewhere,
so it I wanted to call this LHS behavior out.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Wrote a short post on parsing CSS as a way to motivate some basic exploration of metaprogramming in Zig.&lt;br /&gt;&lt;br /&gt;I heavily referenced Zig's builtin JSON parser when learning this. And it is referenced multiple times in the post as well.&lt;a href="https://t.co/CX6jXSLGiR"&gt;https://t.co/CX6jXSLGiR&lt;/a&gt; &lt;a href="https://t.co/jAJJZ0pONQ"&gt;pic.twitter.com/jAJJZ0pONQ&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1670868544953647129?ref_src=twsrc%5Etfw"&gt;June 19, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 19 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-06-19-metaprogramming-in-zig-and-parsing-css.html</guid></item><item><title>Real-world experiments: 5 Lessons from Google, Bing, Netflix and Alibaba</title><link>https://bytepawn.com/experimentation-lessons-google-bing-netflix-alibaba.html</link><description>&lt;p&gt;I discuss five lessons from large-scale experiments conducted by Google, Bing, Netflix and Alibaba: Kohavi's 1 out of 3 rule, Google's 41 shades of blue, Bing's unexpected big win, Alibaba's personalization experiment and Netflix' movie image personalization.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Netflix" src="/images/netflix-movie-art.png" style="width: 600px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 18 Jun 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/experimentation-lessons-google-bing-netflix-alibaba.html</guid></item><item><title>Full Time</title><link>https://www.marginalia.nu/log/83_full_time/</link><description>I&amp;rsquo;m working on Marginalia Search full time.
I left the office for the last time today, and it&amp;rsquo;s the strangest feeling. I&amp;rsquo;ve quit jobs, taken time off work, been laid off, but this is different from any of those things. This is deliberate.
There&amp;rsquo;s a note of relief. I&amp;rsquo;ve essentially been working two pretty demanding jobs; one for pay and one for passion and the joy of making a difference.</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 16 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/83_full_time/</guid></item><item><title>Python tip 29: negative lookarounds</title><link>https://learnbyexample.github.io/tips/python-tip-29/</link><description>&lt;p&gt;Lookarounds help to create custom anchors and add conditions within a regex definition. These assertions are also known as &lt;strong&gt;zero-width patterns&lt;/strong&gt; because they add restrictions similar to anchors and are not part of the matched portions. The syntax for negative lookarounds is shown below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(?!pat)&lt;/code&gt; negative lookahead assertion&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(?&amp;lt;!pat)&lt;/code&gt; negative lookbehind assertion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some examples:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# change 'cat' only if it is not followed by a digit character
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note that the end of string satisfies the given assertion
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 'catcat' has two matches as the assertion doesn't consume characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;cat(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?!\d&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hey cats! cat42 cat_5 catcat'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hey dogs! cat42 dog_5 dogdog'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# change 'cat' only if it is not preceded by _
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note how 'cat' at the start of string is matched as well
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;!&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;_)cat&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat _cat 42catcat'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog _cat 42dogdog'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# change whole word only if it is not preceded by : or -
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;![:-]&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'X'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':cart &amp;lt;apple: -rest ;tea'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':cart &amp;lt;X: -rest ;X'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lookarounds can be placed anywhere and multiple lookarounds can be combined in any order. They do not consume characters nor do they play a role in matched portions. They just let you know whether the condition you want to test is satisfied from the current location in the input string.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# extract all whole words that do not start with a/n
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;ip &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a_t row on Urn e note Dust n end a2-e|u'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?![an]&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'row'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'on'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Urn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'e'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Dust'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'end'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'e'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'u'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# since the three assertions used here are all zero-width,
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# all of the 6 possible combinations will be equivalent
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?!&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\Z&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;!&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\A&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'output=num1+35*42/num2'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'output = num1 + 35 * 42 / num2'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; and &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebooks.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 13 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-29/</guid></item><item><title>Top Traits to Look for When Hiring Exceptional Engineers</title><link>https://letterstoanewdeveloper.com/2023/06/12/top-traits-to-look-for-when-hiring-exceptional-engineers/</link><description>This is a guest post from Manish. Enjoy. Dear new developer, As leaders, we often need to hire new talent to expand our resources, replace team members, or fill vacant positions. While technical skills are important, there are also several non-technical traits that can be equally or even more valuable. Consider the following factors when &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/06/12/top-traits-to-look-for-when-hiring-exceptional-engineers/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Top Traits to Look for When Hiring Exceptional&amp;#160;Engineers&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 12 Jun 2023 18:22:10 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/06/12/top-traits-to-look-for-when-hiring-exceptional-engineers/</guid></item><item><title>AMD RX 7900 XTX review</title><link>https://burakku.com/blog/amd-rx-7900-xtx-review/</link><description>&lt;p&gt;&lt;img alt="AMD RX 7900 XTX" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The worst graphics card I've had the displeasure of owning.&lt;/p&gt;
&lt;p&gt;Now, I'm not going to lie: I'm no graphics card expert. Before the RX 7900 XTX, the three graphics card that I've owned were the Nvidia RTX 3060, the Nvidia GTX 760 and the Nvidia GeForce 9800 GT. If I don't misremember, those were bought in 2021, 2014 and 2008 respectively. Having only had experience with four different GPUs over a span of 15 years definitely doesn't give me the credentials to review a graphics card properly.&lt;/p&gt;
&lt;p&gt;However, I can still say that the AMD RX 7900 XTX is definitely the worst card I have ever owned. Why? Because it just doesn't work.&lt;/p&gt;
&lt;h3&gt;May&lt;/h3&gt;
&lt;p&gt;I bought my 7900 XTX at the beginning of May. Thankfully AMD.com uses DHL, my absolute favourite courier service in Finland, and I managed to get my grubby little hands on my GPU in mere five days after placing the order. After I got done with work, I painstakingly replaced the RTX 3060 inside my gaming PC (sandwich-style PC cases are not easy to work in), booted up and installed drivers. And then, I decided to boot up the most graphically intensive game I have: Cyberpunk 2077.&lt;/p&gt;
&lt;p&gt;Turns out that the 7900 XTX could really only muster around 40 frames per second with the ray tracing settings I had. I would have expected a little more for a 1100-euro investment. But opening up the AMD performance overlay showed a culprit: the GPU junction temperature was at 110°C.&lt;/p&gt;
&lt;p&gt;Turns out that the &lt;a href="https://www.pcgamer.com/amd-rx-7900-xtx-overheating-issue/"&gt;7900 XTX hot spot temperature problem that was in the news four months ago&lt;/a&gt; was still a thing.&lt;/p&gt;
&lt;p&gt;Well, nothing to do about it but contact AMD support. To their credit, they did immediately acknowledge that these products have a fault, although they seemed to be downplaying it to a degree ("may impact the performance but the card can still be used"). Nevertheless, I asked them to generate me an RMA so I could get my card switched for a working one. Then about a week passed before I got my shipping label because AMD support was kinda slow. Maybe they are dealing with a big influx of unhappy 7900 XTX owners?&lt;/p&gt;
&lt;p&gt;Since my assumption was that it'd only take a couple of weeks to get a card back, and since working inside the mini-ITX case gets on my nerves, I decided that I wouldn't reinstall my old graphics card back. It'll be back in a jiffy and I can just sate my gaming desires with the Steam Deck in the meanwhile.&lt;/p&gt;
&lt;p&gt;Unfortunately due to the poor pick-up schedules at my nearest DHL location, it took around a week from leaving the return package to it actually arriving at AMD's return location. At this point, it had already been two weeks since I got the initial card and that I submitted the RMA request. And then, AMD's partner kept sitting on the package for around week and a half, and so May turned to June.&lt;/p&gt;
&lt;h3&gt;June&lt;/h3&gt;
&lt;p&gt;Finally in June, I got my second DHL shipment notification and DHL then hurried over the package to my front door in less than 24 hours. Again, immediately after work I painstakingly stuffed the 7900 XTX into my computer case and launched Cyberpunk 2077 to finally see what kind of performance the GPU can dish when it's not throttling.&lt;/p&gt;
&lt;p&gt;It overheated and throttled immediately.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
    &lt;img alt="Junction temperature of 110°C" class="unscaled" src="temps.png" /&gt;
&lt;/div&gt;&lt;p&gt;Unfortunately the second card came just against the weekend, so I had no choice but to continue the already-long email chain with AMD customer support and try to get another RMA open. Although since I'd just spent the past three weeks looking at my half-finished gaming PC gathering dust, I was leaning more towards a refund than having to do the same process again.&lt;/p&gt;
&lt;p&gt;Then Monday comes and AMD replies. They couldn't ascertain that the graphics card was overheating if I didn't send them measurements from the AMD Radeon software. This felt a bit odd considering the first RMA they didn't bother even asking for evidence but I still went ahead and gathered them their precious Radeon screenshots. At least it's not difficult to document when the card hits 110°C as soon as you load a Cyberpunk 2077 save.&lt;/p&gt;
&lt;p&gt;Having proven to AMD that the card was in fact overheating using their own tools, AMD customer support had another quest for me: send system diagnostics reports of my gaming PC and prove to them that the GPU power cables (two 8-pin connectors) are connected. Again, I have a hard time understanding how this is necessary since a month ago the same customer support agent had told me that these cards have a known flaw.&lt;/p&gt;
&lt;p&gt;Unfortunately that was not enough. What was now required of me was to update my BIOS, do a clean reinstall of all AMD drivers, send details of the power supply that I was using for the card, and install the graphics card without the riser card my mini-ITX case requires. There is no clearance to install any kind of a graphics card with the motherboard in the case, so doing this would have required completely disassembling my entire PC.&lt;/p&gt;
&lt;p&gt;I'd had enough.&lt;/p&gt;
&lt;p&gt;I told AMD customer support that I was done playing their games. No BIOS update was going to fix this issue and AMD's drivers cannot be so bad that they turn the GPU into a hot plate &lt;em&gt;while&lt;/em&gt; it's throttling. But thankfully after being told by a friend and &lt;a href="https://www.youtube.com/watch?v=26Lxydc-3K8"&gt;having been proven by der8auer&lt;/a&gt;, these cards actually have a single orientation in which they won't overheat. Unfortunately, that was not an orientation that was achievable mounting inside my case.&lt;/p&gt;
&lt;p&gt;So I did the only sensible thing imaginable: I grabbed two rolls of toilet paper and very carefully perched my gaming PC on its back to get the card in a vertical orientation. And while the PC might have been &lt;em&gt;very&lt;/em&gt; unstable when it came to sitting on those bog rolls, the card did stay at a stable 80°C while playing Cyberpunk 2077. Clocks were also staying up and the fans were running a whopping 1100 RPM slower.&lt;/p&gt;
&lt;p&gt;And those stats stayed stable for over 20 minutes. Then I turned the computer back up again. Temperatures immediately skyrocketed. Not even a minute and the junction temperature was 110°C, forcing the card to throttle itself. So the cooler was most definitely busted for the second time in a row.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Temperature and fan graphs" src="graphs.png" /&gt;&lt;/p&gt;
&lt;p&gt;Thankfully, telling AMD customer support how fed up I was with their diagnosis for an issue they are already aware of and sending them the graphs of the vertical orientation test worked. After about a week of constant contact with AMD and I got my second RMA.&lt;/p&gt;
&lt;p&gt;And so, today, I dispatched the AMD RX 7900 XTX towards AMD's fulfillment center. Hopefully in a couple of weeks time, I will have my money back and I can leave this episode behind me.&lt;/p&gt;
&lt;h3&gt;Future&lt;/h3&gt;
&lt;p&gt;I think there are two lessons that I've learned from this experience.&lt;/p&gt;
&lt;p&gt;First of all, I really need to do the research before putting down this kind of money towards a product. On some level, I was aware that AMD had a overheating problem with some of its products. But that was in the news months ago. And it only affected some units (or so AMD claimed). That's surely not something that I'd encounter. Twice.&lt;/p&gt;
&lt;p&gt;And secondly, maybe just avoid buying reference graphics cards. They're clearly not that good and at least when it comes to AMD, neither is the customer support. And based on the discussions I've had with people, it seems like Nvidia support is not world-class either.&lt;/p&gt;
&lt;p&gt;But I'm still on the market for a new graphics card. The reason why I picked the RTX 3060 was because I hadn't owned a gaming PC for over seven years at the time, and I wanted to try out PC gaming again with a careful approach. But with the amount of PC gaming I've done lately, and the inherent low performance of the card, I want to upgrade. But to what?&lt;/p&gt;
&lt;p&gt;One of the reasons why I went with the 7900 XTX was because I didn't want to give my money to Nvidia and support the insane prices that they charge for their cards. The AMD RX 7900 XTX and Nvidia RTX 4080 have comparable performance but the 7900 XTX is over 200€ cheaper. But now that I've put my money where my mouth is, and fought against the Nvidia hegemony, the only things I have left to show for it are a long email thread with AMD customer support and additional scratches in my GPU bracket.&lt;/p&gt;
&lt;p&gt;So now I need to choose: do I go for a custom 7900 XTX design like the &lt;a href="https://www.sapphiretech.com/en/consumer/pulse-radeon-rx-7900-xtx-24g-gddr6"&gt;Sapphire PULSE 7900 XTX&lt;/a&gt; and pay a premium over the reference cooler design just to get a working card, or do go back to Team Green with my tail between my legs and pay hundreds of euro more, to a point where it makes sense to just go for the RTX 4090 instead? I feel like there are no good options here, but Nvidia has yet to burn me.&lt;/p&gt;</description><author>ブラック</author><pubDate>Mon, 12 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/amd-rx-7900-xtx-review/</guid></item><item><title>Searching for The Early Founder Cadence</title><link>https://www.swyx.io/early-cadence</link><description>&lt;p&gt;I've been quite inspired by David Sacks' &lt;a href="https://medium.com/craft-ventures/the-cadence-how-to-operate-a-saas-startup-436aa8099e8"&gt;The Cadence&lt;/a&gt; ever since I read it. It prescribes an operational process and ideal team structure for a 50-500 person startup - running sales, finance, product, and marketing in sync in quarterly cycles with effective communication between functions.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 12 Jun 2023 02:51:01 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/early-cadence</guid></item><item><title>Killing Community</title><link>https://www.marginalia.nu/log/82_killing_community/</link><description>This is a theory that&amp;rsquo;s previously been stated in log/39-normie-hypothesis.gmi, but I think it&amp;rsquo;s worth expanding on as it&amp;rsquo;s become very relevant with the recent Reddit shit-show actualizing just how bad that website has gotten along with social media in general.
I think the model demonstrates how the &amp;rsquo;enshittification&amp;rsquo; process is an inevitability with any social media that is run on a venture capital model.
An online community can be like a village, where you have familiar faces, collective experiences, shared values and so forth.</description><author>Weblog on marginalia.nu</author><pubDate>Sun, 11 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/82_killing_community/</guid></item><item><title>Conditional Probabilities and Simpson's Paradox</title><link>https://bytepawn.com/conditional-probabilities-and-simpsons-paradox.html</link><description>&lt;p&gt;I give examples of "unintuitive" conditional probabilities and discuss Simpson's paradox.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Simpson's paradox" src="/images/simpsons-3.jpg" style="width: 800px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 11 Jun 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/conditional-probabilities-and-simpsons-paradox.html</guid></item><item><title>Minecraft Containers</title><link>https://rjp.is/blogging/posts/2023/06/minecraft-containers/</link><description>In which we poke around in some chests.</description><author>infrequent oscillations</author><pubDate>Sat, 10 Jun 2023 09:55:27 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/06/minecraft-containers/</guid></item><item><title>2023–06–10: Orange Pi 5 Plus</title><link>https://xnux.eu/log/#088</link><author>megi's PinePhone Development Log</author><pubDate>Sat, 10 Jun 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#088</guid></item><item><title>2023–06–09: Multi-distro image supporting both Pinephone and Pinephone Pro</title><link>https://xnux.eu/log/#087</link><author>megi's PinePhone Development Log</author><pubDate>Fri, 09 Jun 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#087</guid></item><item><title>Mojo is a much better &amp;amp;quot;Objective-C without the C&amp;amp;quot; than Swift ever was</title><link>https://blog.metaobject.com/2023/06/mojo-is-much-better-without-c-than.html</link><description>One of the primary things that people don't understand about Objective-C is that it is a solution of
the &lt;a href="https://thebottomline.as.ucsb.edu/2018/10/julia-a-solution-to-the-two-language-programming-problem"&gt;two language problem&lt;/a&gt;, or more precisely a generalisation of the two language problem to
the scripted component pattern.&lt;p&gt;

The scripted component pattern itself is a (common) solution to the problem, first identified in the
70s that &lt;a href="https://dl.acm.org/doi/10.1145/390016.808431"&gt;programming-in-the-large is not the same as programming-in-the-small&lt;/a&gt;, that module implementation
languages are not necessarily suitable as module interconnection languages.&lt;p&gt;

And so we have all sorts of flexible connection languages, often interpreted (aka glue, scripting, and orchestration languages),
starting with the Unix shell, in addition to fast, compiled component languages such as C, C++ and
Rust, and a system will usually incorporate at least one of each kind.&lt;p&gt;

But then you run into the two language problem:  you have to deal with these two distinct languages, with
how they integrate, and with the boundaries of the integration often not matching up very well with the
boundaries of the problem you're trying to solve.&lt;p&gt;

Objective-C &lt;a href="https://blog.metaobject.com/2019/03/software-ics-binary-compatibility-and.html"&gt;solved&lt;/a&gt; the two language problem by just jamming the two languages into one:  Smalltalk for the
scripting/integration and C for the component language.  Interoperability is smooth and at the statement
level, thougha there is some
friction due to overlaps caused by integrating two existing languages that were not designed to be
integrated.&lt;p&gt;

&lt;a href="https://www.modular.com/mojo"&gt;Mojo&lt;/a&gt; essentially uses the Objective-C approach of jamming the two languages into one.  Except it 
doesn't repeat Objective-C's mistake of using the component language as the base (which, inexplicably,
Swift didn't just repeat, but actually doubled down on by largely deprecating objects).  The reason this 
is a mistake is that it turns out that the connection language is actually the more general one, the
component language is a specialisation of the connection language.&lt;p&gt;

With this realisation, Mojo's approach of making the connection language the base language make sense.
In addition, the fact that the component language is a specialisation also means that you don't
actually need to jam a full second language into your base, a few syntactic markers to to indicate
the specialisations are sufficient.&lt;p&gt;

This is pretty much exactly stage 2 of &lt;a href="https://blog.metaobject.com/2019/12/the-4-stages-of-objective-smalltalk.html"&gt;the 4 stages of Objective-S&lt;/a&gt;,  so I think they are using exactly the right approach for this.  Except of course for the use of Python as the base instead of Smalltalk, which is a pragmatic 
choice given what they are trying to accomplish, but means your connection language is unduly limited.&lt;p&gt;

&lt;a href="https://objective.st/"&gt;Objective-S&lt;/a&gt; has the same basic structure, but with a much more
capable connection language as the base.&lt;p&gt;</description><author>metablog</author><pubDate>Fri, 09 Jun 2023 00:54:07 GMT</pubDate><guid isPermaLink="true">https://blog.metaobject.com/2023/06/mojo-is-much-better-without-c-than.html</guid></item><item><title>Code Spelunker how it works</title><link>https://boyter.org/posts/codespelunker-details/</link><description>&lt;p&gt;I released code spelunker a few days ago &lt;a href="https://github.com/boyter/cs"&gt;https://github.com/boyter/cs&lt;/a&gt; and literally one person asked for details on how it worked. Fitting in with my habit of putting any content I produce into my blog what follows is a built out version of it.&lt;/p&gt;
&lt;p&gt;So code spelunker started when I noticed someone using Visual Studio to search files recursively in a directory and my own use of ripgrep with fzf. It&amp;rsquo;s development has been on and off over the last 4 years, roughly broken down into the following categories.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Tue, 06 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/codespelunker-details/</guid></item><item><title>What I'm up to - June 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-june-2023</link><description>&lt;div class="prose"&gt;
  &lt;h2&gt;✨ What I've been up to&lt;/h2&gt;&lt;div&gt;It's been a busy month at &lt;a href="https://www.contraption.co/"&gt;Contraption Company&lt;/a&gt;. I'm amid building a new product called &lt;a href="https://booklet.community"&gt;Booklet&lt;/a&gt;, which I plan to launch this Summer. It's a community platform designed to be a calmer alternative to Discord and Slack. I've also been helping some startups launch new generative AI products, which has been rewarding.&lt;br /&gt;&lt;br /&gt;My 2015 article &lt;a href="https://blog.staffjoy.com/how-operations-research-and-artificial-intelligence-overlap-b128a3efee2e"&gt;How Operations Research and Artificial Intelligence Overlap&lt;/a&gt; has been picking up traction as AI is in the zeitgeist.&lt;br /&gt;&lt;br /&gt;I've been trying to use my Airpods less and be bored more often. It's so easy to plug in and tune out the world. &lt;br /&gt;&lt;br /&gt;I got a shout-out in &lt;a href="https://share.transistor.fm/s/21456c85"&gt;Emma's Indiehackers podcast&lt;/a&gt;.&lt;/div&gt;&lt;h2&gt;🤔 Things to share&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles&lt;/strong&gt;: &lt;a href="https://www.nytimes.com/2018/02/16/opinion/sunday/tyranny-convenience.html"&gt;The Tyranny of Convenience&lt;/a&gt;. &lt;a href="https://www.profgalloway.com/yes/"&gt;Yes&lt;/a&gt; - "In sum, get out of the house." &lt;a href="https://ryanholiday.net/this-decision-changed-my-life-and-my-business/"&gt;Ryan Holiday's content marketing strategy&lt;/a&gt;, which I'm taking to heart. &lt;a href="https://world.hey.com/jason/we-stand-with-the-underdogs-7d487d64"&gt;We stand with the Underdogs&lt;/a&gt;. &lt;a href="https://world.hey.com/dhh/the-luxury-of-working-without-metrics-02e5dbac"&gt;The luxury of building without metrics&lt;/a&gt;. &lt;a href="https://www.strongtowns.org/journal/2020/5/14/americas-growth-ponzi-scheme-md2020"&gt;It's cheap to build suburbs but expensive to maintain them, creating a "ticking time bomb"&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books&lt;/strong&gt;: &lt;a href="https://www.amazon.com/Pathless-Path-Imagining-Story-Work-ebook/dp/B09QF1ZCT2/ref=tmm_kin_swatch_0?_encoding=UTF8&amp;amp;qid=1685051401&amp;amp;sr=8-1"&gt;The Pathless Path&lt;/a&gt; - highly recommended for capitalist creatives. &lt;a href="https://www.amazon.com/Carbon-Almanac-Its-Not-Late/dp/B09XBYL7J3/ref=sr_1_1?crid=D28L5QFDRGO2&amp;amp;keywords=carbon+almanac&amp;amp;qid=1685051422&amp;amp;sprefix=carbon+almana%2Caps%2C125&amp;amp;sr=8-1"&gt;The Carbon Almanac&lt;/a&gt; - data-focused approach to understanding carbon emissions. &lt;a href="https://www.amazon.com/gp/product/B00LFOUHFS/ref=ppx_yo_dt_b_d_asin_title_o01?ie=UTF8&amp;amp;psc=1"&gt;Watching the English: The Hidden Rules of English Behavior&lt;/a&gt; - who doesn't love an entire chapter dedicated to how to talk about the weather, or a section about the "proper" way to eat peas?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trends&lt;/strong&gt;: Fractional labor. Everybody in my network seems to be talking about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video&lt;/strong&gt;: &lt;a href="https://www.youtube.com/watch?v=eidMDdMK38s"&gt;Sonobuoy deploying&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Podcasts:&lt;/strong&gt; &lt;a href="https://overcast.fm/+zoPNGnFnc"&gt;The Infrastructure of Community&lt;/a&gt; - "Efficiency is the enemy of social life.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gadgets&lt;/strong&gt;: &lt;a href="https://flipperzero.one/"&gt;Flipper Zero&lt;/a&gt; is fun. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffees I'm drinking: &lt;/strong&gt;&lt;a href="https://www.aprilcoffeeroasters.com/products/pillcocaja-ecuador-anaerobic-pacamara-limited-1"&gt;April Pillcocaja - Ecuador - Anaerobic Pacamara&lt;/a&gt; (noteworthy!). &lt;a href="https://www.kahiwacoffee.fi/products/el-obraje"&gt;Kahiwa El Obraje&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quote: "&lt;/strong&gt;Startups mostly don't compete against each other, they compete against no one giving a shit" -&lt;a href="https://twitter.com/justinkan/status/614904706624720896?s=20"&gt;Justin Kan&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;📫 What I'm up to this month&lt;/h2&gt;&lt;div&gt;With Emma graduating, we're moving back to NYC this month, and will be living in the Lower East Side.&lt;/div&gt;&lt;h2&gt;📍 Where I'll be &lt;/h2&gt;&lt;div&gt;&lt;em&gt;(Let me know if we overlap!)&lt;/em&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;June 20-21: Cambridge, ON for &lt;a href="https://connect.tailwindcss.com/"&gt;Tailwind Connect&lt;/a&gt; 🇨🇦&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;
&lt;br /&gt;&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbzlaIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c684bd1af85a2f4fbc34f0ae7b5ea2158ef99d72/IMG_7301.jpeg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Black truffle pilsner with german chocolate cake was the best thing I ate this month.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Mon, 05 Jun 2023 18:23:23 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-june-2023</guid></item><item><title>Code Spelunker a Code Search Command Line Tool</title><link>https://boyter.org/posts/code-spelunker-a-code-search-command-line-tool/</link><description>&lt;p&gt;Code spelunker (cs) or code search is a new command line tool I have been working on and off over the last few years. It allows you to search over code or text files in the current directory either on the console, via a TUI or HTTP server, using some boolean queries or regular expressions. I just recently pushed out a stable first version release and thought I would write a quick post about it.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 05 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/code-spelunker-a-code-search-command-line-tool/</guid></item><item><title>Getting back into quantifying self for Q3 '23</title><link>https://jodavaho.io/posts/quantified-q3-23.html</link><description>&lt;p&gt;This is the first in a series of posts describing how I&amp;rsquo;m keeping data about my
self, environment, work, and goals. You can read the whole series &lt;a href="https://jodavaho.io/categories/quantified-self.html"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;As part of my journalling habit, I used to keep excellent metrics about myself.
From the year 2014 to about 2019, I recorded almost everything that I felt was
important. This included workouts, spending, and time spent in a variety of
areas, such as grad study. I logged each &amp;ldquo;good&amp;rdquo; awake hour. This allowed me to
generate reports like the following:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="The tracked time towards major goals from 2014. Guess when I met my wife? April 2014. And yes, I was trainig to be a fighter at the time, too." src="https://jodavaho.io/img/quant-self-2014.png" /&gt;&lt;figcaption&gt;
      &lt;p&gt;&lt;em&gt;The tracked time towards major goals from 2014. Guess when I met my wife? April 2014. And yes, I was trainig to be a fighter at the time, too.&lt;/em&gt;&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Quick sidebar: You can imagine two ways to get into this. First, you set some
goals, then you set up a way to track progress. Complicated goals implies
Complicated tracking / reporting. Second way: You just start tracking and make
tweaks along the way when you notice too much time or money spent on something.
I took the second track back then, and want to again, as otherwise it&amp;rsquo;s
analysis paralysis all the way down.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;d rather measure and maximize effort (delta-progress / delta-t) than attempt
to reach an arbitrarily designed goal.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Let&amp;rsquo;s break down what I want to track:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Finances&lt;/li&gt;
&lt;li&gt;Time spent with Family, improving health, or on engineering-type tasks&lt;/li&gt;
&lt;li&gt;Accomplishments vs goals&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&amp;rsquo;s just go over finances first, the rest deserve their own posts.&lt;/p&gt;
&lt;h3 id="finances-current-system"&gt;Finances, current system&lt;/h3&gt;
&lt;p&gt;For the last 1.5 years, I&amp;rsquo;ve been using &lt;a href="https://ledger-cli.org"&gt;ledger&lt;/a&gt; to run
financial reports. Long story short, it&amp;rsquo;s a pain to keep the input files
synchronized with our actual expenses. It requires manual duplication removal,
manual classification, and manual report running. It is nice at quickly
querying multiple reports or data sets, but I found that we really only needed
an answer for one question: Where did our money go this month?&lt;/p&gt;
&lt;p&gt;To get data from financial instutions, I log in, download a custom-date-range
csv file, and then run a bash script to convert the csv file to one readable by
ledger, &lt;em&gt;then&lt;/em&gt; I go transaction-by-transaction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is this transaction already in the ledger?&lt;/li&gt;
&lt;li&gt;Is the classification for this transaction correct? If not, type
&amp;ldquo;Expenses:Blah&amp;rdquo; to classify it.&lt;/li&gt;
&lt;li&gt;Is the source/ destination account sync&amp;rsquo;d? If it&amp;rsquo;s a transfer, I need to go
over to the other account&amp;rsquo;s ledger, and make sure it&amp;rsquo;s not double counted.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This is error prone. The double-entry accounting method made it great to know
that I had captured all transactions (sum=0), but that wasn&amp;rsquo;t my primary
concern. Instead it created a lot of double-counting, since account A would
credit account B with a transfer, but account B would have an incoming
transaction from A, and debit A for that amount.&lt;/li&gt;
&lt;li&gt;This is &lt;em&gt;tedious&lt;/em&gt;. By my current count, we have around 1800 transactions in
the ledger &lt;em&gt;for the last 4 months&lt;/em&gt;, 100% of which I had to input, classify,
and check &lt;em&gt;by hand&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s incomprehensible to my wife. The ASCII-column output is wonderful in a
hacker sense, but makes no sense to my wife and harms her strong sense of
supervisory role. (insert laugh track)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="finances-proposed-system"&gt;Finances, proposed system&lt;/h3&gt;
&lt;h4 id="step-1-simplify-the-report-generation"&gt;Step 1: Simplify the report generation&lt;/h4&gt;
&lt;p&gt;Let&amp;rsquo;s simplify. Ledger is used to parse incoming data and generate interactive
reports. The input format is &lt;em&gt;remarkably human readable&lt;/em&gt; (look at the example
at &lt;a href="https://ledger-cli.org"&gt;ledger-cli.org&lt;/a&gt;, which tells me the target audience
is a somewhat-non-programmer bunch. I am not that audience.&lt;/p&gt;
&lt;p&gt;Given that I&amp;rsquo;m already downloading csv files, let&amp;rsquo;s just write a jupyter
notebook, and use something like
&lt;a href="https://voila.readthedocs.io/en/stable/using.html"&gt;viola&lt;/a&gt; to render a website
that we can view.&lt;/p&gt;
&lt;h4 id="step-2-simplify-the-classification-of-transactions"&gt;Step 2: Simplify the classification of transactions&lt;/h4&gt;
&lt;p&gt;We&amp;rsquo;re habitual, most transactions are recurring (Utilities, subscriptions,
other bills), or from a few major outlets (Target, Amazon, etc). So, we can get
an 80/20 solution by automatically adding a classification column with a few
simple rules. The remaining 20% can be tracked as an &amp;ldquo;other&amp;rdquo; expense group, and
the largest or most frequent charges in that group can easily be plotted with,
say, above reporting.&lt;/p&gt;
&lt;p&gt;Later, I&amp;rsquo;d like to teach a neural network to do this form me.&lt;/p&gt;
&lt;h4 id="step-3-automate-the-retrieval-of-data-from-financial-institutions"&gt;Step 3: Automate the retrieval of data from financial institutions&lt;/h4&gt;
&lt;p&gt;This is the most complicated step. I&amp;rsquo;d like a weekly-fetched set of
transactions, either for the whole year so I can run the whole batch at once,
or to fetch the week&amp;rsquo;s transactions and append to an ongoing data store.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve looked into &lt;code&gt;ofxtools&lt;/code&gt; (&lt;a href="https://readthedocs.org/projects/ofxtools/downloads/pdf/latest/"&gt;readthedocs.org/ofxtools&lt;/a&gt;), but some of my financial
institutions are either blocking bot access, or it was too difficult for me to
configure. This is a solved problem: &lt;a href="https://plaid.com"&gt;Plaid&lt;/a&gt; will allow
developers to fetch transactions, for a modest per-user fee.&lt;/p&gt;
&lt;h4 id="step-4-preserve-ledger-output"&gt;Step 4: Preserve ledger output&lt;/h4&gt;
&lt;p&gt;This is simple, since we import the transaction data, we can just print it out
in a ledger-ready format for later use.&lt;/p&gt;
&lt;h4 id="put-together"&gt;Put together&lt;/h4&gt;
&lt;p&gt;If successful, we&amp;rsquo;ll have some cron jobs on a tiny computer somewhere in the
house:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetch year&amp;rsquo;s financial data from our handful of accounts&lt;/li&gt;
&lt;li&gt;Go through and classify most of them using hand-crafted rules, and group the
rest for manual inspection&lt;/li&gt;
&lt;li&gt;Render a report with a ton of fancy interactive graphs, and a month-to-month
and year-to-year status report.&lt;/li&gt;
&lt;li&gt;Push that to an internally-accessible website.&lt;/li&gt;
&lt;li&gt;Archive the data in an additional ledger-compatible format&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;ll update with how well we&amp;rsquo;re progressing soon.&lt;/p&gt;
&lt;p&gt;You can read the whole series &lt;a href="https://jodavaho.io/categories/quantified-self.html"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;figure&gt;&lt;img alt="&amp;lsquo;A person meticulously tracking all the details of their rich life.&amp;rsquo; Credit: Midjourney" src="https://jodavaho.io/img/tracking-details.png" /&gt;&lt;figcaption&gt;
      &lt;p&gt;&lt;em&gt;&amp;lsquo;A person meticulously tracking all the details of their rich life.&amp;rsquo;&lt;/em&gt; Credit: Midjourney&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;
&lt;h3 id="chatgpt-summary"&gt;ChatGPT Summary:&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The author discusses their intention to track various aspects of their life, including finances, family time, health improvement, and engineering tasks. They highlight their current system of using ledger-cli for financial reports but find it cumbersome. They propose a simplified system involving Jupyter notebooks, automated classification of transactions, data retrieval from financial institutions, and generating interactive reports. The goal is to create a user-friendly and efficient method for tracking and analyzing personal metrics.&lt;/p&gt;
&lt;/blockquote&gt;</description><author>jodavaho.io</author><pubDate>Mon, 05 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/quantified-q3-23.html</guid></item><item><title>Vim tip 27: regexp anchors</title><link>https://learnbyexample.github.io/tips/vim-tip-27/</link><description>&lt;p&gt;By default, regexp matches anywhere in the text. You can use line and word anchors to specify additional restrictions regarding the position of matches. These restrictions are made possible by assigning special meaning to certain characters (&lt;strong&gt;metacharacters&lt;/strong&gt;) and escape sequences.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;^&lt;/code&gt; restricts the match to the start-of-line
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;^This&lt;/code&gt; matches &lt;code&gt;This is a sample&lt;/code&gt; but not &lt;code&gt;Do This&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$&lt;/code&gt; restricts the match to the end-of-line
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;)$&lt;/code&gt; matches &lt;code&gt;apple (5)&lt;/code&gt; but not &lt;code&gt;def greeting():&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;^$&lt;/code&gt; match empty line&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\&amp;lt;pattern&lt;/code&gt; restricts the match to the start of a word
&lt;ul&gt;
&lt;li&gt;word characters include alphabets, digits and underscore&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\&amp;lt;his&lt;/code&gt; matches &lt;code&gt;his&lt;/code&gt; or &lt;code&gt;to-his&lt;/code&gt; or &lt;code&gt;history&lt;/code&gt; but not &lt;code&gt;this&lt;/code&gt; or &lt;code&gt;_hist&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pattern\&amp;gt;&lt;/code&gt; restricts the match to the end of a word
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;his\&amp;gt;&lt;/code&gt; matches &lt;code&gt;his&lt;/code&gt; or &lt;code&gt;to-his&lt;/code&gt; or &lt;code&gt;this&lt;/code&gt; but not &lt;code&gt;history&lt;/code&gt; or &lt;code&gt;_hist&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\&amp;lt;pattern\&amp;gt;&lt;/code&gt; restricts the match between start of a word and end of a word
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\&amp;lt;his\&amp;gt;&lt;/code&gt; matches &lt;code&gt;his&lt;/code&gt; or &lt;code&gt;to-his&lt;/code&gt; but not &lt;code&gt;this&lt;/code&gt; or &lt;code&gt;history&lt;/code&gt; or &lt;code&gt;_hist&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; End-of-line can be &lt;code&gt;\r&lt;/code&gt; (carriage return), &lt;code&gt;\n&lt;/code&gt; (newline) or &lt;code&gt;\r\n&lt;/code&gt; depending on your system and &lt;code&gt;fileformat&lt;/code&gt; setting.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://vimhelp.org/pattern.txt.html#pattern-atoms"&gt;:h pattern-atoms&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 05 Jun 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-27/</guid></item><item><title>June Retro</title><link>https://jodavaho.io/posts/june-retro.html</link><description>&lt;figure&gt;&lt;img src="https://jodavaho.io/img/lake-journal.png" width="100%" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;Midjourney's rendering of a painting of someone doing a retrospective journal by a lake.&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;Half way through the year, time for a brief retrospective. Each of these could
be a post on their own.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="job"&gt;Job&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s been a little over a year since I left JPL. After a brief hiatus at
Amazon, I&amp;rsquo;m settling into my new job at &lt;a href="https://outrider.ai"&gt;Outrider.ai&lt;/a&gt;.
Last year I was laid off from Amazon Robotics, AI, which I don&amp;rsquo;t think exists
any more as an organization. They cited &amp;ldquo;Marcoeconomic factors&amp;rdquo; of course. So,
the year started with me unemployed, but with some offers on the table. It came
down to JPL and Outrider, and I wasn&amp;rsquo;t very pleased with how I managed that
decision making process or juggled multiple interview tracks. I will have to
write more about this, because I feel like I burned a few bridges along the
way. However, those are two near-perfect options, and it came down to timing:
Outrider could move faster with the offer, which helped shore up my dwindling
cash reserves.&lt;/p&gt;
&lt;p&gt;Outrider has been really fun, in a chaotic, semi-supervised kind of way. I feel
like I have a lot of autonomy to work on what I feel is important, room to
screw up and fix my own screwups, and plenty of stellar colleagues to learn
from. Being a &amp;ldquo;principal&amp;rdquo; has been an exercise in restraint, mostly
self-imposed, as I tend to go off on technical tangents / crusades that aren&amp;rsquo;t
exactly what leadership wants. For example, screaming and crying about poor
meterics. Another time for that story as well&amp;hellip;&lt;/p&gt;
&lt;h2 id="hobbies"&gt;Hobbies&lt;/h2&gt;
&lt;p&gt;Most of my hobby time has been taken up with &lt;a href="https://huntshowdown.com"&gt;Hunt
Showdown&lt;/a&gt;. I play regularly with 4 others, and the
game has undergone regular content releases that I really appreciate.  I&amp;rsquo;d love
to play more &lt;a href="https://www.seaofthieves.com/"&gt;Sea of Theives&lt;/a&gt; with my nephews!&lt;/p&gt;
&lt;p&gt;On Hunt: in addition to the amazingly fun PvE action, Hunt has a
match-making-ranking (MMR) system that groups into &amp;ldquo;Stars&amp;rdquo;, 1 through 6. You
might imagine, there&amp;rsquo;s a pretty narrow bell curve, roughly corrsponding to mean
4 stars, and +/- 1 standard deviation per star from there. The MMR is based on
KDA, or (Kills + Assists)/Deaths, it is weighted by how many stars your
opponent has. It&amp;rsquo;s a fairly involved statistics / estimation problem, which I&amp;rsquo;d
love to write more about, but essentially MMR is increased by kills, decreased
by deaths, and not affected by assists. Whereas the KDA rank is improved
linearly by kills or assists, but decreased exponentially by deaths. So, my
goal of keeping 4 star rank while improving KDA means I have to spend a lot of
time assisting (playing on teams and getting assists), or killing low-ranked
players (playing solo and going for team wipes). I enjoy both those roles a
lot, so it&amp;rsquo;s a good fit.&lt;/p&gt;
&lt;p&gt;On the development side, I made good progress on the &lt;a href="https://hfopt.jodavaho.io"&gt;highfleet
cogitator&lt;/a&gt;, thanks as always to my friend &lt;a href="https://www.linkedin.com/in/alan-thomas-813334203/"&gt;Alan
Thomas&lt;/a&gt;. In my mind, it&amp;rsquo;s a
perfect portfolio project for both of us, since it meshes nicely with my
optimization and cloud integration core skills, and Alan&amp;rsquo;s deep frontend
knowledge. Because I read hackernews too much, I had thought of adding a
payment option and making a little side money from it, but I doubt that will
happen. The Highfleet community is probably a little too niche to target for
any kind of monetization, and the project is moving slowly enough that I doubt
it&amp;rsquo;d attract long term payment anyway. I really owe myself a post on the
internals and methods, but there&amp;rsquo;s a few items &lt;a href="https://jodavaho.io/tags/highfleet.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I peruse &lt;a href="https://hackaday.com"&gt;hackaday.com&lt;/a&gt; and always get a pang of sadness
that I&amp;rsquo;m not doing more with homebrew robotics. Especially since, while living
in LA, I had direct access to &lt;a href="https://supplyframe.com/designlab/"&gt;Supply Frame Design
lab&lt;/a&gt;. I have had in mind a long term goal
of building a submarine, starting with a small pvc pontoon boat. I may have to
start that this summer. I&amp;rsquo;d also love to build more potato guns. These are
simple projects with lots of opportunity for nonsensical optimizations like
autonomous navigation, underwater mapping, or even beer delivery for subs, and
auto-loaders, software controlled firing, aiming, etc for the cannons.&lt;/p&gt;
&lt;h2 id="home--personal"&gt;Home / Personal&lt;/h2&gt;
&lt;p&gt;Life as a homeowner has been challenging. Our back yard is a wasteland of
horseweed and dead soil. The hill our house is on is eroding, and needs
terracing. I suspect we&amp;rsquo;ll see a $40,000 quote to get it all sorted out though.
But man, do I love having a riding mower and walking out during the weekdays to
look for dandelions to kill. I&amp;rsquo;m really looking forward to getting a functional
garage put together, with clean storage, accessible tools, and lots of
shelving. One of the most prevalent &amp;ldquo;discussions&amp;rdquo; my wife and I have is: Just
what to do first.&lt;/p&gt;
&lt;p&gt;Side note for another post: I thought marriage would be division of
responsibilities, where we&amp;rsquo;re both CEOs of different parts of the household
responsibilities (in a flexible way), but it&amp;rsquo;s more like a committee of two for
every decision, and we both have arbitrarily different priorities. It feels
like we&amp;rsquo;re both micromanagers and &amp;ldquo;micromanagees&amp;rdquo; at the same time. I&amp;rsquo;m reading
the book
&lt;a href="https://www.amazon.com/Us-Getting-Build-Loving-Relationship/dp/0593233670/"&gt;Us&lt;/a&gt;,
which is interesting in its social commentary, taxing in its narrative-based
format, but helping me understand how to deal with this.&lt;/p&gt;
&lt;p&gt;Related to tinkering and building, I joined &lt;a href="https://tcmaker.org/"&gt;twin cities
maker&lt;/a&gt; recently. They have a stellar shop, though their
electronics workshop is a bit disorganized. Their machine shop, woodshop, and
laser cutter selections are very nice. They even have a forge! In addition to
working on those boats / guns, I think a good woodworking project will be a
playhouse for the girls, which we can keep out back.&lt;/p&gt;
&lt;p&gt;Life as a parent to two toddlers couldn&amp;rsquo;t be more amusing. It&amp;rsquo;s like living
with two goblins/hobbits. Their favorite word by number of uses is &amp;ldquo;no&amp;rdquo; by a
long shot. Followed by &amp;ldquo;more&amp;rdquo;, &amp;ldquo;cheese&amp;rdquo;, &amp;ldquo;please&amp;rdquo;, not quite in a sentance, but
definitely all used for the same goal. Ada has a love of solo play / deep
focus, while Noa is a party animal who seems to love chattering and people.&lt;/p&gt;
&lt;p&gt;Finally, no human-written retrospective is complete without a &amp;ldquo;lose weight&amp;rdquo;
goal. In 2014, I was in the best shape of my life. In LA, everyone is
looks-oriented to a fault, so when I moved there in 2015, it brought some
special treatment when I went out, etc. In the 8 years since then, I&amp;rsquo;ve tapered
off my boxing and working out. Now, as a middle-aged / old man, I&amp;rsquo;m trying to
get back into it. I bought a 20lb weight vest with the goal of accomplishing
the murph challenge in a couple years. I went boxing two weeks ago, and really
enjoyed it, but have not maganged to return. A rough plan will be 18/6 eating
schedules, occasional fasting, some heavy lifting once or twice a week, lighter
lifting and running daily, and boxing a few times a month. I&amp;rsquo;ll see what really
hooks me, and drop one or two in favor of that. Of course, I hope it&amp;rsquo;s boxing
that hooks me, but we&amp;rsquo;ll see.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This has been a rambling, stream of thought post. There&amp;rsquo;s really no conclusion
here except that time flies. If I had written this post in January, it would
have been nearly identical. That stagnation is what makes life feel short. I
look back and it&amp;rsquo;s hard to say what I&amp;rsquo;ve really done, only what I &lt;em&gt;wish&lt;/em&gt; I had
done. And the wish list gets longer as life goes on.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the real retrospective, I think. I&amp;rsquo;ve wanted and wanted, and with few
exceptions (Highfleet, Job) I&amp;rsquo;ve not really &lt;em&gt;done&lt;/em&gt; much. That needs to change.
I think there&amp;rsquo;s a mindset that if I can just find a day to work on something
that I&amp;rsquo;ll be able to make substantial progress, but over and over that&amp;rsquo;s proven
false - both because I can never find &lt;em&gt;a whole day&lt;/em&gt; and because disconnected
days are not nearly as productive as they could be. It&amp;rsquo;s probably much better
to work every couple days for an hour or so, so &lt;em&gt;that&amp;rsquo;s the goal&lt;/em&gt; for the rest
of the year.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Sat, 03 Jun 2023 16:22:56 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/june-retro.html</guid></item><item><title>Secrets 4 - Update rugpull</title><link>https://burakku.com/blog/secrets-rugpull/</link><description>&lt;p&gt;Promise updates, take the money, and then ask for more.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://secrets.app/"&gt;Secrets&lt;/a&gt; by &lt;a href="https://outercorner.com/"&gt;Outer Corner&lt;/a&gt; is a password manager for macOS and iOS. They just released the latest version of the software, Secrets 4, today and rarely do I feel as outright scammed by software as I do now.&lt;/p&gt;
&lt;p&gt;I decided to start using Secrets a couple of months ago after looking for a successor to my previous password manager. It had many good things going for it: it was a native macOS/iOS app, it didn't require a subscription, the data was hosted in iCloud, it had an integration for Windows, and so on.&lt;/p&gt;
&lt;p&gt;However, there was one large concern that I had: &lt;a href="https://outercorner.com/blog/2021/03/secrets-3.6-tying-up-loose-ends/"&gt;the last major update was in March 2021&lt;/a&gt;, two years prior.&lt;/p&gt;
&lt;p&gt;No one wants to buy a product that is unsupported, especially when it comes to software. This is especially important on macOS and iOS side, as Apple is famous for breaking old software in yearly updates. Windows users do not suffer nearly as much from this, as it's not unthinkable to be able to run a Windows 95 application in Windows 11. No wonder Windows developers have been so slow to adopt 64-bit and ARM support.&lt;/p&gt;
&lt;p&gt;So I did what any good consumer should do: I dug around some. More specifically, I dug around some on Twitter.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.com/outercorner/status/1600189882869088257" target="_blank"&gt;
    &lt;img alt="Secrets tweet on Dec 6, 2022" class="no-border" src="tweet1.png" /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.com/outercorner/status/1629030740271722496" target="_blank"&gt;
    &lt;img alt="Secrets tweet on Feb 24, 2023" class="no-border" src="tweet2.png" /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.com/outercorner/status/1636061769490628616" target="_blank"&gt;
    &lt;img alt="Secrets tweet on Mar 15, 2023" class="no-border" src="tweet3.png" /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Okay, so Secrets has not seen a big update in over two years, but the developer has made multiple tweets promising an update, and a big one at that. So I would in fact not be buying an abandoned piece of software. So I decided to pull the trigger, and bought both the macOS and iOS versions of Secrets for about 40€ in total.&lt;/p&gt;
&lt;p&gt;And then, the developer abandoned the software.&lt;/p&gt;
&lt;p&gt;On May 18th, 2023, &lt;a href="https://outercorner.com/blog/2023/05/announcing-secrets-4/"&gt;Outer Corner announced that Secrets 4, the major update being promised since last year, would be a paid upgrade&lt;/a&gt;. But to soothe old users, existing users would be promised a "considerable discount". &lt;em&gt;"Okay"&lt;/em&gt;, I figured, &lt;em&gt;"I guess I need to spend a couple of euro more for this big update."&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Today, Secrets 4 was released. The full and complete version of the universal app was offered to existing users as a launch day special for the low, low price of… 50€. That's after the considerable 50% discount. If you wanted to upgrade to the basic version of Secrets 4, you could get it for the low, low price of… 50€ again.&lt;/p&gt;
&lt;p&gt;So, having paid 40€ for Secrets just two months prior with a promise of a big update, I am now being asked for somewhere between 50€ and 100€ to get that major update that was promised and advertised multiple times.&lt;/p&gt;
&lt;p&gt;Ever since I bought Secrets, I have received a single update. The macOS version of Secrets was updated to version 3.8.5 about a month ago, which "fixed a couple of compatibility issues with macOS Ventura". The iOS version has not been updated in 10 months as of writing.&lt;/p&gt;
&lt;p&gt;The developer Paulo Andrade has been deflecting criticism of the paid upgrade by telling people that Secrets users have been enjoying free updates for seven years. Good for those users, I guess. I sure didn't! The update that was coming my way, has been turned into a paid upgrade. I just got that single-sentence update in the two months.&lt;/p&gt;
&lt;p&gt;You could argue that "update" does not mean that it's free of charge. However, I feel like "update" is an update to the existing software, and not a completely new application on the App Store and a separate purchase. And I feel like Outer Corner agrees with me, considering the &lt;a href="https://outercorner.com/blog/2023/05/secrets-4-has-shipped/"&gt;announcement post for Secrets 4&lt;/a&gt; only talks about an "upgrade", not an "update".&lt;/p&gt;
&lt;p&gt;It's not like Secrets 3 is a perfect password manager as it is. I'm currently suffering from random freezes when invoking Secrets from the Firefox extension, the image cropper in the application does absolutely nothing, and the image rendering for the thumbnails on macOS is garbage. I always just figured that since the update was coming, I'd just bear with it and hope that the update fixes everything. Had I been told that the version of Secrets would be the end of the road, I'd probably have refunded my purchase within the 14-day cooling off period. Or just not upgraded from the trial to begin with.&lt;/p&gt;
&lt;p&gt;Technically the version of Secrets that I have is &lt;em&gt;not&lt;/em&gt; abandoned. Completely abandoned, that is. The developer has that they'll support Secrets 3 for "the near future". What is the definition of "near future"? Absolutely no idea, the developer refused to elaborate. My expectations of the future after all of this are very low. I imagine I will be relying on the good graces of Apple to not kill any of the API that Secrets 3 uses in order to use it on future versions of macOS and iOS, and not Outer Corner's support.&lt;/p&gt;
&lt;p&gt;I don't like how almost every paid application these days is shifting to a subscription system. That was one of the reasons why I chose Secrets after all. However, the situation with Secrets is even worse than subscriptions – I'm currently doing like 20€ per month of active support, considering that Secrets 3 is now unceremoniously on life support. Secrets has been awful value for money. Even the Photoshop + Lightroom bundle is less than 20€/month, and Adobe software aren't known for being a bargain (at least for non-professionals).&lt;/p&gt;
&lt;p&gt;I do understand that Secrets is made by a small indie development company whose developer needs to eat. However, I find the practice of selling an already-axed version of a software without any kind of a warning to be anti-consumer. I feel like companies have two very good alternatives to the way Outer Corner did things: offer a free upgrade to anyone who bought in the last [appropriate amount of time], or make it very clear that you are buying an unsupported product with no future.&lt;/p&gt;
&lt;p&gt;If it's not clear already, I think you should &lt;strong&gt;not&lt;/strong&gt; buy Secrets. But if you do, don't expect updates.&lt;/p&gt;</description><author>ブラック</author><pubDate>Wed, 31 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/secrets-rugpull/</guid></item><item><title>CLI tip 28: substitute specific occurrence with GNU sed</title><link>https://learnbyexample.github.io/tips/cli-tip-28/</link><description>&lt;p&gt;By using the &lt;code&gt;g&lt;/code&gt; flag with the &lt;code&gt;s&lt;/code&gt; command (substitute), you can search and replace all occurrences of a pattern. Without the &lt;code&gt;g&lt;/code&gt; flag, only the first matching portion will be replaced.&lt;/p&gt;
&lt;p&gt;Did you know that you can use a number as a flag to replace only that particular occurrence of matching parts?&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# replace only the third occurrence of ':' with '---'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple:banana:cherry:fig:mango' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/:/---/3'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;apple:banana:&lt;/span&gt;&lt;span&gt;cherry&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;---&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;fig:&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace only the second occurrence of a word starting with 't'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'book table bus car banana tap camp' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\bt\w*/&amp;quot;&amp;amp;&amp;quot;/2'
&lt;/span&gt;&lt;span&gt;book table bus car banana &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;tap&amp;quot;&lt;/span&gt;&lt;span&gt; camp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To replace a specific occurrence from the end of the line, you'll have use regular expression tricks:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple:banana:cherry:fig:mango'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace the last occurrence
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/(.*):/\1---/'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;apple:banana:cherry:&lt;/span&gt;&lt;span&gt;fig&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;---&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace the last but one occurrence
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/(.*):(.*:)/\1---\2/'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;apple:banana:&lt;/span&gt;&lt;span&gt;cherry&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;---&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;fig:&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# generic formula, where {N} refers to the last but Nth occurrence
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/(.*):((.*:){2})/\1---\2/'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;apple:&lt;/span&gt;&lt;span&gt;banana&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;---&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;cherry:fig:&lt;/span&gt;&lt;span&gt;mango
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you combine a number flag with the &lt;code&gt;g&lt;/code&gt; flag, all matches from that particular occurrence will be replaced.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# replace except the first occurrence of a word starting with 'b'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'book table bus car banana tap camp' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\bb\w*/&amp;quot;&amp;amp;&amp;quot;/2g'
&lt;/span&gt;&lt;span&gt;book table &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;bus&amp;quot;&lt;/span&gt;&lt;span&gt; car &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;banana&amp;quot; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;tap&lt;/span&gt;&lt;span&gt; camp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/learn_gnused"&gt;CLI text processing with GNU sed&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 30 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-28/</guid></item><item><title>Learn how to build tools</title><link>https://letterstoanewdeveloper.com/2023/05/29/learn-how-to-build-tools/</link><description>This is a guest post from Jamie Tanna. Enjoy. Dear new developer, Our jobs and lives are full of repetition, and one of the beauties of being developers is that we can take steps to automate away some of the repetition. Learning to automate, or at least minimise, repetition optimises your work. You can get &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/05/29/learn-how-to-build-tools/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Learn how to build&amp;#160;tools&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 29 May 2023 16:57:24 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/05/29/learn-how-to-build-tools/</guid></item><item><title>Defaulting git clone to shallow (depth=1)</title><link>https://smcleod.net/2023/05/defaulting-git-clone-to-shallow-depth1/</link><description>&lt;!-- markdownlint-disable MD025 --&gt;
&lt;p&gt;Before adding this to my shell config, I would manually add &amp;ndash;depth=1 to all my git clones.&lt;/p&gt;</description><author>smcleod.net</author><pubDate>Mon, 29 May 2023 01:53:24 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/05/defaulting-git-clone-to-shallow-depth1/</guid></item><item><title>2023–05–29: Touch screen and touch menu support for Pinephone Pro in U-Boot</title><link>https://xnux.eu/log/#086</link><author>megi's PinePhone Development Log</author><pubDate>Mon, 29 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#086</guid></item><item><title>2023–05–28: Some regressions in power use in system sleep on original Pinephone</title><link>https://xnux.eu/log/#085</link><author>megi's PinePhone Development Log</author><pubDate>Sun, 28 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#085</guid></item><item><title>2023–05–27: Implementing display support in U-Boot for Pinephone Pro</title><link>https://xnux.eu/log/#084</link><author>megi's PinePhone Development Log</author><pubDate>Sat, 27 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#084</guid></item><item><title>How likely would you be to block a company from asking you to rate everything they do?</title><link>https://smcleod.net/2023/05/how-likely-would-you-be-to-block-a-company-from-asking-you-to-rate-everything-they-do/</link><description>NPS Surveys are corporate spam</description><author>smcleod.net</author><pubDate>Fri, 26 May 2023 09:41:37 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/05/how-likely-would-you-be-to-block-a-company-from-asking-you-to-rate-everything-they-do/</guid></item><item><title>Bastion review</title><link>https://burakku.com/blog/bastion-review/</link><description>&lt;p&gt;&lt;img alt="Bastion" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Time for another blast from the past, this time Bastion from 2011.&lt;/p&gt;
&lt;p&gt;I actually played through Bastion for the first time way back in 2012, when it took me about 12 hours to beat the game and do whatever side tasks I did back then. A decade older and wiser, I got through my first playthrough in under eight hours and mopped the rest of the achievements in another ten.&lt;/p&gt;
&lt;p&gt;Turns out that Bastion is still a nice game.&lt;/p&gt;
&lt;p&gt;You play as The Kid, a silent protagonist who has to smash and fight through a bunch of isometric stages that build around you as you move about the place. The core gameplay loop of Bastion revolves around combat, which is handled using melee and ranged attacks, a shield and special attacks. There is a handful of weapons to choose from, which unlock as you get through the story, and you get a choice of upgrades to further custoize your combat experience.&lt;/p&gt;
&lt;p&gt;The combat itself is not particularly challenging, although you definitely can make things harder for yourself using the Idol system. About the Author, which requires you to defeat 30 waves of enemies in the Stranger's Dream while using all of the hardenings available, definitely gives you a run for your money. Even though I managed to beat it in two attempts, the first attempt failing at the last wave, it was not without its share of panic, screaming and aching arms. But if you don't reach for the Idols, combat's more on the relaxing side of the difficulty spectrum.&lt;/p&gt;
&lt;p&gt;Oh, and if you decide to go for About the Author: Galleon Mortar and dodge roll, dodge roll, dodge roll.&lt;/p&gt;
&lt;p&gt;There is a fairly short and basic story in the game, although it's not particularly gripping. Most of the interesting stuff in the Bastion universe happened before you even came along, and you're mostly told about it from the narrator. If you were hoping for a story-heavy game, you might be disappointed, but it's still a decent, somber narrative.&lt;/p&gt;
&lt;p&gt;Bastion's art is highly stylized and gorgeous, although not perfect; due to the isometric perspective and the way the levels are designed, it's hard to tell which parts of the level you can actually access and which you can't. It's also not the easiest to avoid falling entirely off the level, which will snip some off your health bar as a punishment. Still, the overall quality of the art definitely offsets those minor annoyances.&lt;/p&gt;
&lt;p&gt;The sound design in the game is absolutely top notch. I'd like to meet the person who doesn't enjoy Logan Cunningham's voice-over narration, which there is a lot. And Bastion's soundtrack ranks in some of the best video game soundtracks that I know of. It's the kind of stuff that you'll want to listen outside the game as well. I know I definitely have.&lt;/p&gt;
&lt;p&gt;For my 2023 playthrough of Bastion, I did it entirely on my Steam Deck, as my gaming PC was actually out of commission for the entirety of it. And there is good news on that front: Bastion has a native Linux version and works absolutely flawlessly on SteamOS. Since this game is designed for a controller and is not graphically demanding, it makes for a perfect Steam Deck title.&lt;/p&gt;
&lt;p&gt;Considering for how little money you can pick up Bastion these days, and the abundance of good things about it, avoiding it would be foolish if you feel like it would appeal to you in any way.&lt;/p&gt;</description><author>ブラック</author><pubDate>Fri, 26 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/bastion-review/</guid></item><item><title>My 2023 New Mac Setup</title><link>https://www.swyx.io/new-mac-setup-2023</link><description>&lt;p&gt;I set up a new Mac for work today. Here's everything I use on a Mac for fullstack web development.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 25 May 2023 21:51:04 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/new-mac-setup-2023</guid></item><item><title>Implementing the Raft distributed consensus protocol in Go</title><link>http://notes.eatonphil.com/2023-05-25-raft.html</link><description>&lt;p&gt;As part of bringing myself up-to-speed after joining &lt;a href="https://tigerbeetle.com/"&gt;TigerBeetle&lt;/a&gt;, I
wanted some background on how distributed consensus and replicated state
machines protocols work. TigerBeetle uses &lt;a href="https://pmg.csail.mit.edu/papers/vr-revisited.pdf"&gt;Viewstamped
Replication&lt;/a&gt;. But I
wanted to understand all popular protocols and I decided to start with
&lt;a href="https://raft.github.io/"&gt;Raft&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We'll implement two key components of Raft in this post (leader
election and log replication). Around 1k lines of Go. It took me
around 7 months of sporadic studying to come to (what I hope is) an
understanding of the basics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I'm not an expert. My implementation isn't yet hooked
up to &lt;a href="https://github.com/jepsen-io/jepsen"&gt;Jepsen&lt;/a&gt;. I've run it
through a mix of
&lt;a href="https://github.com/eatonphil/goraft/tree/main#distributed-key-value-store-api"&gt;manual&lt;/a&gt; and
&lt;a href="https://github.com/eatonphil/goraft/tree/main/cmd/stress"&gt;automated
tests&lt;/a&gt; and
it seems generally correct. This is not intended to be used in
production. It's just for my education.&lt;/p&gt;
&lt;p&gt;All code for this project is &lt;a href="https://github.com/eatonphil/goraft"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's dig in!&lt;/p&gt;
&lt;h3 id="the-algorithm"&gt;The algorithm&lt;/h3&gt;&lt;p&gt;&lt;a href="https://raft.github.io/raft.pdf"&gt;The Raft paper&lt;/a&gt; itself is quite
readable. Give it a read and you'll get the basic idea.&lt;/p&gt;
&lt;p&gt;The gist is that nodes in a cluster conduct elections to pick
a leader. Users of the Raft cluster send messages to the leader. The
leader passes the message to followers and waits for a majority to
store the message. Once the message is committed (majority consensus
has been reached), the message is applied to a state machine the user
supplies. Followers learn about the latest committed message from the
leader and apply each new committed message to their local
user-supplied state machine.&lt;/p&gt;
&lt;p&gt;There's more to it including reconfiguration and snapshotting, which I
won't get into in this post. But you can get the gist of Raft by
thinking about 1) leader election and 2) replicated logs powering
replicated state machines.&lt;/p&gt;
&lt;h3 id="modeling-with-state-machines-and-key-value-stores"&gt;Modeling with state machines and key-value stores&lt;/h3&gt;&lt;p&gt;I've written before about how you can &lt;a href="https://notes.eatonphil.com/minimal-key-value-store-with-hashicorp-raft.html"&gt;build a key-value store on top
of
Raft&lt;/a&gt;. How
you can &lt;a href="https://notes.eatonphil.com/zigrocks-sql.html"&gt;build a SQL database on top of a key-value
store&lt;/a&gt;. And how you can
build a &lt;a href="https://notes.eatonphil.com/distributed-postgres.html"&gt;distributed SQL database on top of
Raft&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This post will start quite similarly to that first post except for
that we won't stop at the Raft layer.&lt;/p&gt;
&lt;h3 id="a-distributed-key-value-store"&gt;A distributed key-value store&lt;/h3&gt;&lt;p&gt;To build on top of the Raft library we'll build, we need to create a
state machine and commands that are sent to the state machine.&lt;/p&gt;
&lt;p&gt;Our state machine will have two operations: get a value from a key,
and set a key to a value.&lt;/p&gt;
&lt;p&gt;This will go in &lt;code&gt;cmd/kvapi/main.go&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bytes&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;crypto/rand&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;encoding/binary&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;log&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;math/rand&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;net/http&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;strconv&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;strings&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sync&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/eatonphil/goraft&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;statemachine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Map&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;setCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;iota&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;getCommand&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;commandKind&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;statemachine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;decodeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Key not found&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Unknown command: %x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But the Raft library we'll build needs to deal with various state
machines. So commands passed from the user into the Raft cluster must
be serialized to bytes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;encodeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the &lt;code&gt;Apply()&lt;/code&gt; function from above needs to be able to decode the
bytes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;decodeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commandKind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;keyLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;keyLen&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;valLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;keyLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;keyLen&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;keyLen&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;keyLen&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;valLen&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="http-api"&gt;HTTP API&lt;/h4&gt;&lt;p&gt;Now that we've modeled the key-value store as a state machine. Let's
build the HTTP endpoints that allow the user to operate the state
machine through the Raft cluster.&lt;/p&gt;
&lt;p&gt;First, let's implement the &lt;code&gt;set&lt;/code&gt; operation. We need to grab the key
and value the user passes in and call &lt;code&gt;Apply()&lt;/code&gt; on the Raft
cluster. Calling &lt;code&gt;Apply()&lt;/code&gt; on the Raft cluster will eventually call
the &lt;code&gt;Apply()&lt;/code&gt; function we just wrote, but not until the message sent
to the Raft cluster is actually replicated.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpServer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;raft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;goraft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Map&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example:&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;//  curl http://localhost:2020/set?key=x&amp;amp;value=1&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setCommand&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;raft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;([][]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;encodeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Could not write key-value: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To reiterate, we tell the Raft cluster we want this message
replicated. The message contains the operation type (&lt;code&gt;set&lt;/code&gt;) and the
operation details (&lt;code&gt;key&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt;). These messages are custom to
the state machine we wrote. And they will be interpreted by the state
machine we wrote, on each node in the cluster.&lt;/p&gt;
&lt;p&gt;Next we handle &lt;code&gt;get&lt;/code&gt;-ing values from the cluster. There are two ways
to do this. We already embed a local copy of the distributed key-value
map. We could just read from that map in the current process. But it
might not be up-to-date or correct. It would be fast to read
though. And convenient for debugging.&lt;/p&gt;
&lt;p&gt;But the only &lt;a href="https://github.com/etcd-io/etcd/issues/741"&gt;&lt;em&gt;correct&lt;/em&gt; way to read from a Raft
cluster&lt;/a&gt; is to pass the
read through the log replication too.&lt;/p&gt;
&lt;p&gt;So we'll support both.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Example:&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;//  curl http://localhost:2020/get?key=x&lt;/span&gt;
&lt;span class="c1"&gt;//  1&lt;/span&gt;
&lt;span class="c1"&gt;//  curl http://localhost:2020/get?key=x&amp;amp;relaxed=true # Skips consensus for the read.&lt;/span&gt;
&lt;span class="c1"&gt;//  1&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getCommand&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;relaxed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Key not found&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;goraft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ApplyResult&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;raft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;([][]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;encodeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Expected single response from Raft, got: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;results&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="nx"&gt;Error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;results&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="nx"&gt;Error&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;results&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="nx"&gt;Result&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Could not encode key-value in http response: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Could not encode key-value in http response: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="main"&gt;Main&lt;/h4&gt;&lt;p&gt;Now that we've set up our custom state machine and our HTTP API for
interacting with the Raft cluster, we'll tie it together with reading
configuration from the command-line and actually starting the Raft
node and the HTTP API.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;goraft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ClusterMember&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--node&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strconv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Atoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Expected $value to be a valid integer in `--node $value`, got: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--http&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--cluster&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clusterEntry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;goraft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ClusterMember&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;idAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;,&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;clusterEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strconv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ParseUint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idAddress&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Expected $id to be a valid integer in `--cluster $id,$ip`, got: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;idAddress&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="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;clusterEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;idAddress&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clusterEntry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Missing required parameter: --node $index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Missing required parameter: --http $address&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Missing required parameter: --cluster $node1Id,$node1Address;...;$nodeNId,$nodeNAddress&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &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="kt"&gt;byte&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[:])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cannot seed math/rand package with cryptographically secure random number generator&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[:])))&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Map&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;statemachine&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;sm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;sm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;goraft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;sm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpServer&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that's it for the easy part: a distributed key-value store on top
of a Raft cluster.&lt;/p&gt;
&lt;p&gt;Next we need to implement Raft.&lt;/p&gt;
&lt;h3 id="a-raft-server"&gt;A Raft server&lt;/h3&gt;&lt;p&gt;If we take a look at Figure 2 in the Raft paper, we get an idea for
all the state we need to model.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Raft Figure 2" src="/assets/raft-figure-2.png" /&gt;&lt;/p&gt;
&lt;p&gt;We'll dig into the details as we go. But for now let's turn that model
into a few Go types. This goes in &lt;code&gt;raft.go&lt;/code&gt; in the base directory,
not &lt;code&gt;cmd/kvapi&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;goraft&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bufio&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;context&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;encoding/binary&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;errors&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;io&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;math/rand&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;net&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;net/http&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;net/rpc&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;path&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sync&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StateMachine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ApplyResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Entry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Set by the primary so it can learn about the result of&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// applying this command to the state machine&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ApplyResult&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ClusterMember&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Index of the next log entry to send&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Highest log entry known to be replicated&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;matchIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Who was voted for in the most recent term&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;votedFor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// TCP connection&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rpcClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ServerState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;leaderState&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ServerState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;leader&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;followerState&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;follower&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;candidateState&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;candidate&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// These variables for shutting down.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mutex&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ----------- PERSISTENT STATE -----------&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// The current term&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;Entry&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// votedFor is stored in `cluster []ClusterMember` below,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// mapped by `clusterIndex` below&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ----------- READONLY STATE -----------&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Unique identifier for this Server&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// The TCP address for RPC&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// When to start elections after no append entry messages&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;electionTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// How often to send empty messages&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;heartbeatMs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// When to next send empty message&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;heartbeatTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// User-provided state machine&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;statemachine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StateMachine&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Metadata directory&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;metadataDir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Metadata store&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ----------- VOLATILE STATE -----------&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Index of highest log entry known to be committed&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;commitIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Index of highest log entry applied to state machine&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lastApplied&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Candidate, follower, or leader&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ServerState&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Servers in the cluster, including this one&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;ClusterMember&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Index of this server&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And let's build a constructor to initialize the state for all servers
in the cluster, as well as local server state.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;clusterConfig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;ClusterMember&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;statemachine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StateMachine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;metadataDir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Explicitly make a copy of the cluster because we'll be&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// modifying it in this server.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;ClusterMember&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clusterConfig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Id must not be 0.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;statemachine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;statemachine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;metadataDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;metadataDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;heartbeatMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And add a few debugging and assertion helpers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;debugmsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%s [Id: %d, Term: %d] %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RFC3339Nano&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugmsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&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="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[WARN] &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugmsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;warnf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&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="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%s. Got a = %#v, b = %#v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugmsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="persistent-state"&gt;Persistent state&lt;/h3&gt;&lt;p&gt;As Figure 2 says, &lt;code&gt;currentTerm&lt;/code&gt;, &lt;code&gt;log&lt;/code&gt;, and &lt;code&gt;votedFor&lt;/code&gt; must be
persisted to disk as they're edited.&lt;/p&gt;
&lt;p&gt;I like to initially doing the stupidest thing possible. So in the
first version of this project I used &lt;code&gt;encoding/gob&lt;/code&gt; to write these
three fields to disk every time &lt;code&gt;s.persist()&lt;/code&gt; was called.&lt;/p&gt;
&lt;p&gt;Here is what this first version looked like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Truncate&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="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Seek&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;enc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PersistentState&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;CurrentTerm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;VotedFor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;votedFor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Persisted. Term: %d. Log Len: %d. Voted For: %s.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;votedFor&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But doing so means this implementation is a function of the size of
the log. And that was horrible for throughput.&lt;/p&gt;
&lt;p&gt;I also noticed that &lt;code&gt;encoding/gob&lt;/code&gt; is pretty inefficient.&lt;/p&gt;
&lt;p&gt;For a simple struct like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;encoding/gob&lt;/code&gt; uses &lt;a href="https://play.golang.com/p/TUe9TDgaZOw"&gt;68 bytes to store that data for when B has two
entries&lt;/a&gt;. If we wrote the
encoder/decoder ourselves we could store that struct in 33 bytes (&lt;code&gt;8
(sizeof(A)) + 8 (sizeof(len(B))) + 16 (len(B) * sizeof(B)) + 1
(sizeof(C))&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;It's not that &lt;code&gt;encoding/gob&lt;/code&gt; is bad. It just likely has different
constraints than we are party to.&lt;/p&gt;
&lt;p&gt;So I decided to swap out &lt;code&gt;encoding/gob&lt;/code&gt; for simply binary encoding the
fields and also, importantly, keeping track of exactly how many
entries in the log must be written and only writing that many.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;s.persist()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;s.persist()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;Here's what that looks like.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ENTRY_HEADER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ENTRY_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;

&lt;span class="c1"&gt;// Must be called within s.mu.Lock()&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;writeLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nNewEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nNewEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;writeLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;nNewEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Seek&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 0  - 8:   Current term&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 8  - 16:  Voted for&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 16 - 24:  Log length&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 4096 - N: Log&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PutUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&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="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PutUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&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="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getVotedFor&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PutUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[:])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Wrote full page&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;writeLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nNewEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;newLogOffset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;nNewEntries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;ENTRY_SIZE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;newLogOffset&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;bw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bufio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entryBytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ENTRY_SIZE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newLogOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 0 - 8:    Entry term&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 8 - 16:   Entry command length&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 16 - ENTRY_SIZE: Entry command&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ENTRY_SIZE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ENTRY_HEADER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Command is too large (%d). Must be at most %d bytes.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ENTRY_SIZE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ENTRY_HEADER&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PutUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryBytes&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="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PutUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryBytes&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="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryBytes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryBytes&lt;/span&gt;&lt;span class="p"&gt;[:])&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Wrote full page&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ENTRY_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Persisted in %s. Term: %d. Log Len: %d (%d new). Voted For: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nNewEntries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getVotedFor&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Again the important thing is that only the entries that &lt;em&gt;need&lt;/em&gt; to be
written are written. We do that by &lt;code&gt;seek&lt;/code&gt;-ing to the offset of the
first entry that needs to be written.&lt;/p&gt;
&lt;p&gt;And we collect writes of entries in a &lt;code&gt;bufio.Writer&lt;/code&gt; so we don't waste
write syscalls. Don't forget to flush the buffered writer!&lt;/p&gt;
&lt;p&gt;And don't forget to flush all writes to disk with &lt;code&gt;fd.Sync()&lt;/code&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  &lt;code&gt;ENTRY_SIZE&lt;/code&gt; is something that I could see being configurable based
  on the workload. Some workloads truly need only 128 bytes. But a
  key-value store probably wants much more than that. This
  implementation doesn't try to handle the case of completely
  arbitrary sized keys and values.
&lt;/p&gt;&lt;p&gt;Lastly, a few helpers used in there:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Must be called within s.mu.Lock()&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getVotedFor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;votedFor&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Invalid cluster&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="&amp;lt;code&amp;gt;s.restore()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;s.restore()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;Now let's do the reverse operation, restoring from disk. This will
only be called once on startup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadataDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;md_%d.dat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_SYNC&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_CREATE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_RDWR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mo"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Seek&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 0  - 8:   Current term&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 8  - 16:  Voted for&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 16 - 24:  Log length&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 4096 - N: Log&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[:])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ensureLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Read full page&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&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="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setVotedFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&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="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lenLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lenLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Entry&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lenLog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entryBytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ENTRY_SIZE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryBytes&lt;/span&gt;&lt;span class="p"&gt;[:])&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Read full entry&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ENTRY_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 0 - 8:    Entry term&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 8 - 16:   Entry command length&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Bytes 16 - ENTRY_SIZE: Entry command&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryBytes&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="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;lenValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryBytes&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="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entryBytes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;lenValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ensureLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And a few helpers it calls:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ensureLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Always has at least one log entry.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Must be called within s.mu.Lock()&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setVotedFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;votedFor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Invalid cluster&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="the-main-loop"&gt;The main loop&lt;/h3&gt;&lt;p&gt;Now let's think about the main loop. Before starting the loop we need
to 1) restore persistent state from disk and 2) kick off an RPC
server so servers in the cluster can send and receive messages to and
from eachother.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Make sure rand is seeded&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;followerState&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rpcServer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rpcServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tcp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DefaultRPCPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rpcServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetElectionTimeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the main loop we are either in the leader state, follower state or
candidate state.&lt;/p&gt;
&lt;p&gt;All states will potentially receive RPC messages from other servers in
the cluster but that won't be modeled in this main loop.&lt;/p&gt;
&lt;p&gt;The only thing going on in the main loop is that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We send heartbeat RPCs (leader state)&lt;/li&gt;
&lt;li&gt;We try to advance the commit index (leader state only) and apply commands to the state machine (leader and follower states)&lt;/li&gt;
&lt;li&gt;We trigger a new election if we haven't received a message in some time (candidate and follower states)&lt;/li&gt;
&lt;li&gt;Or we become the leader (candidate state)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;leaderState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heartbeat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;advanceCommitIndex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;followerState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;advanceCommitIndex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;candidateState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;becomeLeader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's deal with leader election first.&lt;/p&gt;
&lt;h3 id="leader-election"&gt;Leader election&lt;/h3&gt;&lt;p&gt;Leader election happens every time nodes haven't received a message
from a valid leader in some time.&lt;/p&gt;
&lt;p&gt;I'll break this up into four major pieces:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Timing out and becoming a candidate after a random (but bounded)
period of time of not hearing a message from a valid leader:
&lt;code&gt;s.timeout()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The candidate requests votes from all other servers: &lt;code&gt;s.requestVote()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;All servers handle vote requests: &lt;code&gt;s.HandleRequestVoteRequest()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A candidate with a quorum of vote requests becomes the leader: &lt;code&gt;s.becomeLeader()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You increment &lt;code&gt;currentTerm&lt;/code&gt;, vote for yourself and send RPC vote
requests to other nodes in the server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resetElectionTimeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heartbeatMs&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heartbeatMs&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;New interval: %s.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;electionTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;hasTimedOut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;After&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;electionTimeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hasTimedOut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Timed out, starting new election.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;candidateState&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;votedFor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;votedFor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetElectionTimeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestVote&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Everything in there is implemented already except for
&lt;code&gt;s.requestVote()&lt;/code&gt;. Let's dig into that.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;s.requestvote()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;s.requestVote()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;By referring back to Figure 2 from the Raft paper we can see how to
model the request vote request and response. Let's turn that into some Go types.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RequestVoteRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Candidate requesting vote&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;CandidateId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Index of candidate's last log entry&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;LastLogIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Term of candidate's last log entry&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;LastLogTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RequestVoteResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// True means candidate received vote&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;VoteGranted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we just need to fill the &lt;code&gt;RequestVoteRequest&lt;/code&gt; struct out and send
it to each other node in the cluster in parallel. As we iterate
through nodes in the cluster, we skip ourselves (we always immediately
vote for ourselves).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;requestVote&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Requesting vote from %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;lastLogIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;lastLogTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;Term&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RequestVoteRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;CandidateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;LastLogIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lastLogIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;LastLogTerm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;lastLogTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RequestVoteResponse&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rpcCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Server.HandleRequestVoteRequest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;// Will retry later&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now remember from Figure 2 in the Raft paper that we must always check
that the RPC request and response is still valid. If the term of the
response is greater than our own term, we must immediately stop
processing and revert to follower state.&lt;/p&gt;
&lt;p&gt;Otherwise only if the response is still relevant to us at the moment
(the response term is the same as the request term) &lt;em&gt;and&lt;/em&gt; the request
has succeeded do we count the vote.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateTerm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;dropStaleResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dropStaleResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VoteGranted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Vote granted by %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;votedFor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that's it for the candidate side of requesting a vote.&lt;/p&gt;
&lt;p&gt;The implementation of &lt;code&gt;s.updateTerm()&lt;/code&gt; is simple. It just takes care
of transitioning to follower state if the term of an RPC message is
greater than the node's current term.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Must be called within a s.mu.Lock()&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;updateTerm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;transitioned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;followerState&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setVotedFor&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="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;transitioned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Transitioned to follower&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetElectionTimeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transitioned&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the implementation of &lt;code&gt;s.rpcCall()&lt;/code&gt; is a wrapper around &lt;code&gt;net/rpc&lt;/code&gt;
to lazily connect.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rpcCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rpcClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rpcClient&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rpcClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rpcClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DialHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tcp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;rpcClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rpcClient&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// TODO: where/how to reconnect if the connection must be reestablished?&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rpcClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;warnf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Error calling %s on %d: %s.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's dig into the other side of request vote, what happens when a
node receives a vote request?&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;s.handlevoterequest()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;s.HandleVoteRequest()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;First off, as discussed above, we must always check the RPC term
versus our own and revert to follower if the term is greater than our
own. (Remember that since this is an RPC request it could come to a
server in any state: leader, candidate, or follower.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HandleRequestVoteRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RequestVoteRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;RequestVoteResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateTerm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Received vote request from %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CandidateId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we can return immediately if the request term is lower than our
own (that means it's an old request).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VoteGranted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Not granting vote request from %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CandidateId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;VoteGranted = false&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VoteGranted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And finally, we check to make sure the requester's log is at least as
up-to-date as our own and that we haven't already voted for
ourselves.&lt;/p&gt;
&lt;p&gt;The first condition (up-to-date log) was not described in
the Raft paper that I could find. But the author of the paper
published a Raft TLA+ spec that does &lt;a href="https://github.com/ongardie/raft.tla/blob/master/raft.tla#L284"&gt;have it
defined&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And the second condition you might think could never happen since we
already wrote the code that said when we trigger an election we vote
for ourselves. But since each server has a random election timeout,
the one who starts the election will differ in timing sufficiently
enough to catch other servers and allow them to vote for it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lastLogTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;Term&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;logLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;logOk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LastLogTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lastLogTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LastLogTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lastLogTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LastLogIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;logLen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;grant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;logOk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getVotedFor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getVotedFor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CandidateId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Voted for %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CandidateId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setVotedFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CandidateId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VoteGranted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetElectionTimeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Not granting vote request from %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CandidateId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Lastly, we need to address how the candidate who sent out vote
requests actually becomes the leader.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;s.becomeleader()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;s.becomeLeader()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;This is a relatively simple method. If we have a quorum of votes, we
become the leader!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;becomeLeader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;quorum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;votedFor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;quorum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;quorum&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There is a bit of bookkeeping we need to do like resetting &lt;code&gt;nextIndex&lt;/code&gt;
and &lt;code&gt;matchIndex&lt;/code&gt; for each server (noted in Figure 2). And we also need
to append a blank entry for the new term.&lt;/p&gt;
&lt;p class="note"&gt;
  Despite the section quoted below in code, I still don't understand
  why this blank entry is necessary.
&lt;/p&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;quorum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Reset all cluster state&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Yes, even matchIndex is reset. Figure 2&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// from Raft shows both nextIndex and&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// matchIndex are reset after every election.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;matchIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;New leader.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;leaderState&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// From Section 8 Client Interaction:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// &amp;gt; First, a leader must have the latest information on&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// &amp;gt; which entries are committed. The Leader&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// &amp;gt; Completeness Property guarantees that a leader has&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// &amp;gt; all committed entries, but at the start of its&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// &amp;gt; term, it may not know which those are. To find out,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// &amp;gt; it needs to commit an entry from its term. Raft&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// &amp;gt; handles this by having each leader commit a blank&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// &amp;gt; no-op entry into the log at the start of its term.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Triggers s.appendEntries() in the next tick of the&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// main state loop.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heartbeatTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we're done with elections!&lt;/p&gt;
&lt;p&gt;When I was working on this for the first time, I just stopped here and
made sure I could get to a stable leader quickly. If it takes more
than 1 term to establish a leader when you run three servers in the
cluster on localhost, you've probably got a bug.&lt;/p&gt;
&lt;p&gt;In an ideal environment (which three processes on one machine most
likely is), leadership should be established quite quickly and without
many term changes. As the environment gets more adversarial
(e.g. processes crash frequently or network latency is high and
variable), leadership (and log replication) will take longer.&lt;/p&gt;
&lt;p class="note"&gt;
    But just because we have leader election working when there are no
    logs does not mean we'll have it working when we introduce log
    replication since parts of voting depend on log analysis.
    &lt;br /&gt;
    I had leader election working at one time but then it broke when I
    got log replication working until I found some more bugs in leader
    election and fixed them. Of course, there may still be bugs even
    now.
&lt;/p&gt;&lt;h3 id="log-replication"&gt;Log replication&lt;/h3&gt;&lt;p&gt;I'll break up log replication into four major pieces:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User submits a message to the leader to be replicated: &lt;code&gt;s.Apply()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The leader sends uncommitted messages (messages from
&lt;code&gt;nextIndex&lt;/code&gt;) to all followers: &lt;code&gt;s.appendEntries()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A follower receives a &lt;code&gt;AppendEntriesRequest&lt;/code&gt; and stores new
messages if appropriate, letting the leader know when it does store
the messages: &lt;code&gt;s.HandleAppendEntriesRequest()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The leader tries to update &lt;code&gt;commitIndex&lt;/code&gt; for the last uncommitted
message by seeing if it's been replicated on a quorum of servers:
&lt;code&gt;s.advanceCommitIndex()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's dig in in that order.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;s.apply()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;s.Apply()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;This is the entry point for a user of the cluster to attempt to get
messages replicated into the cluster.&lt;/p&gt;
&lt;p&gt;It must be called on the current leader of the cluster. In the future
the failure response might include the current leader. Or the user
could submit messages in parallel to all nodes in the cluster and
ignore &lt;code&gt;ErrApplyToLeader&lt;/code&gt;. In the meantime we just assume the user can
figure out which server in the cluster is the leader.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ErrApplyToLeader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Cannot apply message to follower, apply to leader.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nx"&gt;ApplyResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;leaderState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ErrApplyToLeader&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Processing %d new entry!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next we'll store the message in the leader's log along with a
Go channel that we must block on for the result of applying
the message in the state machine after the message has been committed
to the cluster.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;resultChans&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ApplyResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;resultChans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ApplyResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;resultChans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we kick off the replication process (this will not block).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Waiting to be applied!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendEntries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And then we block until we receive results from each of the channels we created.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// TODO: What happens if this takes too long?&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nx"&gt;ApplyResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WaitGroup&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resultChans&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ApplyResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The interesting thing here is that appending entries is detached from
the messages we just received. &lt;code&gt;s.appendEntries()&lt;/code&gt; will probably
include at least the messages we just appended to our log, but it
might include more too if some servers are not very up-to-date. It may
even include less than the messages we append to our log since we'll
restrict the number of entries to send at one time so we keep latency
down.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;s.appendentries()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;s.appendEntries()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;This is the meat of log replication on the leader side. We send
unreplicated messages to each other server in the cluster.&lt;/p&gt;
&lt;p&gt;By again referring back to Figure 2 from the Raft paper we can see how
to model the request vote request and response. Let's turn that into
some Go types too.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AppendEntriesRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// So follower can redirect clients&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;LeaderId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Index of log entry immediately preceding new ones&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;PrevLogIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Term of prevLogIndex entry&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;PrevLogTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Log entries to store. Empty for heartbeat.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Entries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;Entry&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Leader's commitIndex&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;LeaderCommit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AppendEntriesResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// true if follower contained entry matching prevLogIndex and&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// prevLogTerm&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For the method itself, we start optimistically sending no entries and
decrement &lt;code&gt;nextIndex&lt;/code&gt; for each server as the server fails to replicate
messages. This means that we might eventually end up sending the
entire log to one or all servers.&lt;/p&gt;
&lt;p&gt;We'll set a max number of entries to send per request so we avoid
unbounded latency as followers store entries to disk. But we still
want to send a large batch so that we amortize the cost of &lt;code&gt;fsync&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MAX_APPEND_ENTRIES_BATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appendEntries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Don't need to send message to self&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;prevLogIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;prevLogTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prevLogIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;Entry&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;len: %d, next: %d, server: %d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Keep latency down by only applying N at a time.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MAX_APPEND_ENTRIES_BATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="nx"&gt;MAX_APPEND_ENTRIES_BATCH&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;lenEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AppendEntriesRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;LeaderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;PrevLogIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prevLogIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;PrevLogTerm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;prevLogTerm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;Entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;LeaderCommit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AppendEntriesResponse&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Sending %d entries to %d for term %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rpcCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Server.HandleAppendEntriesRequest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;// Will retry next tick&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, as with every RPC request and response, we must check terms and
potentially drop the message if it's outdated.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateTerm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;dropStaleResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;leaderState&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dropStaleResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Otherwise, if the message was successful, we'll update &lt;code&gt;matchIndex&lt;/code&gt;
(the last confirmed message stored on the follower) and &lt;code&gt;nextIndex&lt;/code&gt;
(the next likely message to send to the follower).&lt;/p&gt;
&lt;p&gt;If the message was not successful, we decrement &lt;code&gt;nextIndex&lt;/code&gt;. Next time
&lt;code&gt;s.appendEntries()&lt;/code&gt; is called it will include one more previous
message for this replica.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PrevLogIndex&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;lenEntries&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;matchIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Message accepted for %d. Prev Index: %d, Next Index: %d, Match Index: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;matchIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Forced to go back to %d for: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we're done the leader side of append entries!&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;s.handleappendentriesrequest()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;s.HandleAppendEntriesRequest()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;Now for the follower side of log replication. This is, again, an RPC
handler that could be called at any moment. So we need to potentially
update the &lt;code&gt;term&lt;/code&gt; (and transition to follower).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HandleAppendEntriesRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AppendEntriesRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;AppendEntriesResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateTerm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RPCMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;"Hidden" in the "Candidates (§5.2):" section of Figure 2 is an additional rule about:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;If AppendEntries RPC received from new leader: convert to follower&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So we also need to handle that here. And if we're still not a
follower, we'll return immediately.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// From Candidates (§5.2) in Figure 2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// If AppendEntries RPC received from new leader: convert to follower&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;candidateState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;followerState&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;followerState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Non-follower cannot append entries.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, we also return early if the request term is less than our
own. This would represent an old request.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Dropping request from old leader %d: term %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LeaderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Not a valid leader.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, finally, we know we're receiving a request from a valid
leader. So we need to immediately bump the election timeout.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Valid leader so reset election.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetElectionTimeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we do the log comparison to see if we can add the entries sent
from this request. Specifically, we make sure that our log at
&lt;code&gt;req.PrevLogIndex&lt;/code&gt; exists and has the same term as &lt;code&gt;req.PrevLogTerm&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;logLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;validPreviousLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PrevLogIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* This is the induction step */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PrevLogIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;logLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PrevLogIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PrevLogTerm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validPreviousLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Not a valid log.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, we've got valid entries that we need to add to our log. This
implementation is a little more complex because we'll make use of Go
slice capacity so that &lt;code&gt;append()&lt;/code&gt; never allocates.&lt;/p&gt;
&lt;p&gt;Importantly, we must truncate the log if a new entry ever conflicts
with an existing one:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;If an existing entry conflicts with a new one (same index
but different terms), delete the existing entry and all that
follow it (§5.3)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PrevLogIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;nNewEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Entries&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;newTotal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Entries&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Second argument must actually be `i`&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// not `0` otherwise the copy after this&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// doesn't work.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Only copy until `i`, not `newTotal` since&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// we'll continue appending after this.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;newLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nx"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newTotal&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newLog&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;prevCap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// If an existing entry conflicts with a new&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// one (same index but different terms),&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// delete the existing entry and all that&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// follow it (§5.3)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Capacity remains the same while we truncated.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prevCap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Appending entry: %s. At index: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Existing log is the same as new log&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Server_assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Length is directly related to the index.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&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="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;nNewEntries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, we update the server's local &lt;code&gt;commitIndex&lt;/code&gt; to the min of
&lt;code&gt;req.LeaderCommit&lt;/code&gt; and our own log length.&lt;/p&gt;
&lt;p&gt;And finally we persist all these changes and mark the response as
successful.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LeaderCommit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LeaderCommit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&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="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nNewEntries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nNewEntries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So the combined behavior of the leader and follower when replicating
is that a follower not in sync with the leader may eventually go down
to the beginning of the log so the leader and follower have some first
N messages of the log that match.&lt;/p&gt;
&lt;h4 id="&amp;lt;code&amp;gt;s.advancecommitindex()&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;s.advanceCommitIndex()&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;Now when not just one follower but a quorum of followers all have a
matching first N messages, the leader can advance the cluster's
&lt;code&gt;commitIndex&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;advanceCommitIndex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Leader can update commitIndex on quorum.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;leaderState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;lastLogIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lastLogIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;quorum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;quorum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;isLeader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clusterIndex&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;matchIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isLeader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;quorum&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;quorum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;New commit index: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And for every state a server might be in, if there are messages
committed but not applied, we'll apply one here. And importantly,
we'll pass the result back to the message's result channel if it
exists, so that &lt;code&gt;s.Apply()&lt;/code&gt; can learn about the result.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastApplied&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastApplied&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// len(log.Command) == 0 is a noop committed by the leader.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Entry applied: %d.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastApplied&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// TODO: what if Apply() takes too long?&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statemachine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Will be nil for follower entries and for no-op entries.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Not nil for all user submitted messages.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ApplyResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastApplied&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="heartbeats"&gt;Heartbeats&lt;/h3&gt;&lt;p&gt;Heartbeats combine log replication and leader election. Heartbeats
stave off leader election (follower timeouts). And heartbeats also
bring followers up-to-date if they are behind.&lt;/p&gt;
&lt;p&gt;And it's a simple method. If it's time to heartbeat, we call
&lt;code&gt;s.appendEntries()&lt;/code&gt;. That's it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;heartbeat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;timeForHeartbeat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;After&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heartbeatTimeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;timeForHeartbeat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heartbeatTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heartbeatMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Sending heartbeat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendEntries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The reason this staves off leader election is because any number of
entries (0 or N) will come from a valid leader and will thus cause the
followers to reset their election timeout.&lt;/p&gt;
&lt;p&gt;And that's the entirety of (the basics of) Raft.&lt;/p&gt;
&lt;p&gt;There are probably bugs.&lt;/p&gt;
&lt;h3 id="running-kvapi"&gt;Running kvapi&lt;/h3&gt;&lt;p&gt;Now let's run the key-value API.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cmd/kvapi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build
&lt;span class="gp"&gt;$ &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;*.dat
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="terminal-1"&gt;Terminal 1&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;./kvapi&lt;span class="w"&gt; &lt;/span&gt;--node&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--http&lt;span class="w"&gt; &lt;/span&gt;:2020&lt;span class="w"&gt; &lt;/span&gt;--cluster&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0,:3030;1,:3031;2,:3032&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="terminal-2"&gt;Terminal 2&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;./kvapi&lt;span class="w"&gt; &lt;/span&gt;--node&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--http&lt;span class="w"&gt; &lt;/span&gt;:2021&lt;span class="w"&gt; &lt;/span&gt;--cluster&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0,:3030;1,:3031;2,:3032&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="terminal-3"&gt;Terminal 3&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;./kvapi&lt;span class="w"&gt; &lt;/span&gt;--node&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--http&lt;span class="w"&gt; &lt;/span&gt;:2022&lt;span class="w"&gt; &lt;/span&gt;--cluster&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0,:3030;1,:3031;2,:3032&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="terminal-4"&gt;Terminal 4&lt;/h4&gt;&lt;p&gt;Remember that requests will go through the leader (except for if we
turn that off in the &lt;code&gt;/get&lt;/code&gt; request). So you'll have to try sending a
message to each server until you find the leader.&lt;/p&gt;
&lt;p&gt;To set a key:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://localhost:2020/set?key&lt;span class="o"&gt;=&lt;/span&gt;y&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hello
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To get a key:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://localhost:2020/get&lt;span class="se"&gt;\?&lt;/span&gt;key&lt;span class="se"&gt;\=&lt;/span&gt;y
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that's that! Try killing a server and restarting it. A new leader
will be elected so you'll need to find the right one to send requests
to again. But all existing entries should still be there.&lt;/p&gt;
&lt;h3 id="a-test-rig"&gt;A test rig&lt;/h3&gt;&lt;p&gt;I won't cover the &lt;a href="https://github.com/eatonphil/goraft/blob/main/cmd/sim/main.go"&gt;implementation of my test
rig&lt;/a&gt; in
this post but I will describe it.&lt;/p&gt;
&lt;p&gt;It's nowhere near Jepsen but it does have a specific focus:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Can the cluster elect a leader?&lt;/li&gt;
&lt;li&gt;Can the cluster store logs correctly?&lt;/li&gt;
&lt;li&gt;Can the cluster of three nodes tolerate one node down?&lt;/li&gt;
&lt;li&gt;How fast can it store N messages?&lt;/li&gt;
&lt;li&gt;Are messages recovered correctly when the nodes shut down and start back up?&lt;/li&gt;
&lt;li&gt;If a node's logs are deleted, is the log for that node recovered after it is restarted?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This implementation passes these tests and handles around 20k-40k entries/second.&lt;/p&gt;
&lt;h3 id="considerations"&gt;Considerations&lt;/h3&gt;&lt;p&gt;This was quite a challenging project. Normally when I hack on stuff
like this I have TV (The Simpsons) on in the background. It's sort of
dumb but this was the first project where I absolutely could not focus
with that background noise.&lt;/p&gt;
&lt;p&gt;There are a tedious number of conditions and I am not sure I got them
all (right). Numerous ways for subtle bugs.&lt;/p&gt;
&lt;h4 id="race-conditions-and-deadlocks"&gt;Race conditions and deadlocks&lt;/h4&gt;&lt;p&gt;It's very easy to program in race conditions. Thankfully Go has the
&lt;code&gt;-race&lt;/code&gt; flag that detects this. This makes sure that you are locking
read and write access to shared variables when necessary.&lt;/p&gt;
&lt;p&gt;On the other side of race conditions, Go does not help you out with:
deadlocks. Once you've got locks in place for shared variables, you
need to make sure you're releasing the locks appropriately too.&lt;/p&gt;
&lt;p&gt;Thankfully someone wrote a swap-in replacement for the Go &lt;code&gt;sync&lt;/code&gt;
package called
&lt;a href="https://github.com/sasha-s/go-deadlock"&gt;go-deadlock&lt;/a&gt;. When you import
this package instead of the default &lt;code&gt;sync&lt;/code&gt; package, it will panic and
give you a stacktrace when it thinks you hit a deadlock.&lt;/p&gt;
&lt;p&gt;Sometimes it thinks you hit a deadlock because a method that needs a
lock takes too long. Sometimes that time it takes is legitimate (or
something you haven't optimized yet). But actually its default of
&lt;code&gt;30s&lt;/code&gt; is not really aggressive at all.&lt;/p&gt;
&lt;p&gt;So I normally set the deadlock timeout to &lt;code&gt;2s&lt;/code&gt; and eventually would
like to make that more like &lt;code&gt;100ms&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sync.Opts.DeadlockTimeout = 2000 * time.Millisecond
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It's mostly the &lt;code&gt;persist()&lt;/code&gt; function that causes &lt;code&gt;go-deadlock&lt;/code&gt; to
think there's a deadlock because it tries to synchronously write a
bunch of data to disk.&lt;/p&gt;
&lt;h5 id="&amp;lt;code&amp;gt;go-deadlock&amp;lt;/code&amp;gt;-is-slow"&gt;&lt;code&gt;go-deadlock&lt;/code&gt; is slow&lt;/h5&gt;&lt;p&gt;The &lt;code&gt;go-deadlock&lt;/code&gt; package is incredibly useful. But don't forget to
turn it off for benchmarks. With it on I get around 4-8k
entries/second. With it off I get around 20k-40k entries/second.&lt;/p&gt;
&lt;h4 id="unbounded-memory"&gt;Unbounded memory&lt;/h4&gt;&lt;p&gt;Another issue in this implementation is that the log keeps growing
indefinitely &lt;em&gt;and&lt;/em&gt; the entire log is duplicated in memory.&lt;/p&gt;
&lt;p&gt;There are two ways to deal with that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Implement Raft snapshotting so the log can be truncated safely.&lt;/li&gt;
&lt;li&gt;Keep only some number of entries in memory (say, 1 million) and
read from disk as needed when logs need to be verified. In ideal
operation this would never happen since ideally all servers are
always on, never miss entries, and just keep appending. But "ideal"
won't always happen.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Similarly, there is unbounded and unreused channel creation for
notifying &lt;code&gt;s.Apply()&lt;/code&gt; when the user-submitted message(s) finish.&lt;/p&gt;
&lt;h4 id="net/rpc-and-encoding/gob"&gt;net/rpc and encoding/gob&lt;/h4&gt;&lt;p&gt;In the &lt;code&gt;persist()&lt;/code&gt; section above I already mentioned how I prototyped
this using Go's builtin gob encoding. And I mentioned how inefficient
it was. It's also pretty slow and I learned that because &lt;code&gt;net/rpc&lt;/code&gt;
uses it and after everything I did &lt;code&gt;net/rpc&lt;/code&gt; started to be the
bottleneck in my benchmarks. This isn't incredibly surprising.&lt;/p&gt;
&lt;p&gt;So a future version of this code might implement its own protocol and
own encoding (like we did for disk) on top of TCP rather than use
&lt;code&gt;net/rpc&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="jepsen"&gt;Jepsen&lt;/h4&gt;&lt;p&gt;Everyone wants to know how a distributed algorithm does against
&lt;a href="https://github.com/jepsen-io/jepsen"&gt;Jepsen&lt;/a&gt;, which tests
linearizability of distributed systems in the face of network and
process faults.&lt;/p&gt;
&lt;p&gt;But the setup is not trivial so I haven't hooked it up to this project
yet. This would be a good area for future work.&lt;/p&gt;
&lt;h4 id="election-timeout-and-the-environment"&gt;Election timeout and the environment&lt;/h4&gt;&lt;p&gt;One thing I noticed as I was trying out alternatives to &lt;code&gt;net/rpc&lt;/code&gt;
(alternatives that injected latency to simulate a bad environment) is
that election timeouts should probably be tuned with latency of the
cluster in mind.&lt;/p&gt;
&lt;p&gt;If the election timeout is every &lt;code&gt;300ms&lt;/code&gt; but the latency of the
cluster is near &lt;code&gt;1s&lt;/code&gt;, you're going to have non-stop leader election.&lt;/p&gt;
&lt;p&gt;When I adjusted the election timeout to be every &lt;code&gt;2s&lt;/code&gt; when the latency
of the cluster is near &lt;code&gt;1s&lt;/code&gt;, everything was fine. Maybe this means
there's a bug in my code but I don't think so.&lt;/p&gt;
&lt;h4 id="client-request-serial-identifier"&gt;Client request serial identifier&lt;/h4&gt;&lt;p&gt;One major part of the Raft protocol I did not cover is that the client
is supposed to send a serial identifier for each message sent to the
cluster. This is to ensure that messages are not accidentally
duplicated at any level of the entire software stack.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://web.stanford.edu/~ouster/cgi-bin/papers/OngaroPhD.pdf"&gt;Diego Ongaro's
thesis&lt;/a&gt;
goes into more detail about this than the Raft paper. Search in that
PDF for "session".&lt;/p&gt;
&lt;p&gt;Again I just completely ignored the possibility of duplicate messages
in this implementation so far.&lt;/p&gt;
&lt;h3 id="references"&gt;References&lt;/h3&gt;&lt;p&gt;Finally, I could not have done this without a bunch of internet
help. This project took me about 7 months in total. The first 5 months
I was trying to figure it out mostly on my own, just looking at the
Raft paper.&lt;/p&gt;
&lt;p&gt;The biggest breakthrough came from discovering the author of Raft's
TLA+ spec for Raft. Formal methods sound scary but it was truly not
too bad! This was the first "implementation" of Raft that was in a
single file of code. And under 500 lines.&lt;/p&gt;
&lt;p&gt;Jack Vanlightly's guide to reading TLA+ helped a bunch.&lt;/p&gt;
&lt;p&gt;Finally, I had to peer at other implementations, especially to figure
out locking and avoiding deadlocks.&lt;/p&gt;
&lt;p&gt;Here's everything that helped me out.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://raft.github.io/raft.pdf"&gt;In Search of an Understandable Consensus Algorithm&lt;/a&gt;: The Raft paper.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ongardie/raft.tla/blob/master/raft.tla"&gt;raft.tla&lt;/a&gt;: Diego Ongaro's TLA+ spec for Raft.&lt;/li&gt;
&lt;li&gt;Jon Gjengset's &lt;a href="https://thesquareplanet.com/blog/students-guide-to-raft/"&gt;Students' Guide to Raft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Jack Vanlightly's &lt;a href="https://medium.com/splunk-maas/detecting-bugs-in-data-infrastructure-using-formal-methods-704fde527c58"&gt;Detecting Bugs in Data Infrastructure using Formal Methods (TLA+ Series Part 1)&lt;/a&gt;: An intro to TLA+.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And useful implementations I looked at for inspiration and clarity.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hashicorp's &lt;a href="https://github.com/hashicorp/raft"&gt;Raft implementation&lt;/a&gt; in Go: Although it's often quite complicated to learn from since it actually is intended for production.&lt;/li&gt;
&lt;li&gt;Eli Bendersky's &lt;a href="https://github.com/eliben/raft"&gt;Raft implementation&lt;/a&gt; in Go: Although I got confused following it since it used signed integers and &lt;code&gt;-1&lt;/code&gt; to represent base cases. Signed integers is a fair choice as far as I can tell, I just wanted to only use unsigned integers.&lt;/li&gt;
&lt;li&gt;Jing Yang's &lt;a href="https://github.com/ditsing/ruaft"&gt;Raft implementation&lt;/a&gt; in Rust: Although I find Rust hard to read.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And I haven't tried these but they look cool:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jepsen.io/services#training"&gt;Raft course taught by Jepsen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dabeaz.com/raft.html"&gt;Raft course taught by David Beazley&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote about implementing Raft in Go. By far the most challenging project I've worked on in spare time. About 7 months sporadically.&lt;br /&gt;&lt;br /&gt;I'm not an expert, and this is not intended to be used in production. I wanted a better background on the subject!&lt;a href="https://t.co/EhyBuQ4pD3"&gt;https://t.co/EhyBuQ4pD3&lt;/a&gt; &lt;a href="https://t.co/vGhBbV1shf"&gt;pic.twitter.com/vGhBbV1shf&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1661720451616210944?ref_src=twsrc%5Etfw"&gt;May 25, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 25 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-05-25-raft.html</guid></item><item><title>2023–05–25: DRAM frequency scaling on Pinephone Pro saves 0.5W of power!</title><link>https://xnux.eu/log/#083</link><author>megi's PinePhone Development Log</author><pubDate>Thu, 25 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#083</guid></item><item><title>Python tip 28: string concatenation and repetition</title><link>https://learnbyexample.github.io/tips/python-tip-28/</link><description>&lt;p&gt;Python provides a wide variety of features to work with strings. In this tip, you'll learn about string concatenation and repetition.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;+&lt;/code&gt; operator is one of the ways to concatenate two strings. The operands can be any expression that results in a string value and you can use any of the different ways to specify a string literal. Another option is to use f-strings. Here are some examples:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;s1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hello'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;s2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'world'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(s1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+ &lt;/span&gt;&lt;span style="color: #d07711;"&gt;' ' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+ &lt;/span&gt;&lt;span&gt;s2)
&lt;/span&gt;&lt;span&gt;hello world
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #668f14;"&gt;f&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;{s1} {s2}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hello world'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;s1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+ &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt; 1&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;2&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hello. 1&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;n2'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another way to concatenate is to simply place any kind of string literal next to each other. You can use zero or more whitespaces between the two literals. But you cannot mix an expression and a string literal. If the strings are inside parentheses, you can also use newline characters to separate the literals and optionally use comments.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hello' &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt; 1&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;2&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hello. 1&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\\&lt;/span&gt;&lt;span style="color: #d07711;"&gt;n2'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...       &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'-banana'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...       &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'-cherry'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;apple&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;banana&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;cherry
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can repeat a string by using the &lt;code&gt;*&lt;/code&gt; operator between a string and an integer. You'll get an empty string if the integer value is less than &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;style_char &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'-'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(style_char &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;50&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--------------------------------------------------
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;word &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'buffalo '
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;8 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span&gt;word)
&lt;/span&gt;&lt;span&gt;buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 24 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-28/</guid></item><item><title>Programming is a Creative Activity</title><link>https://healthydev.substack.com/p/programming-is-a-creative-activity</link><description>How to treat people who write code well</description><author>The Healthy Dev</author><pubDate>Tue, 23 May 2023 13:31:27 GMT</pubDate><guid isPermaLink="true">https://healthydev.substack.com/p/programming-is-a-creative-activity</guid></item><item><title>The path to autonomous intelligent agents: what needs to happen.</title><link>https://iamnotarobot.substack.com/p/the-path-to-autonomous-intelligent</link><description>VCs like to say that the next big thing will start out looking like a toy. We are definitely having fun with them, but when will they grow up?</description><author>I Am Not a Robot</author><pubDate>Tue, 23 May 2023 07:13:29 GMT</pubDate><guid isPermaLink="true">https://iamnotarobot.substack.com/p/the-path-to-autonomous-intelligent</guid></item><item><title>2023–05–23: Boot time power consumption tracing</title><link>https://xnux.eu/log/#082</link><author>megi's PinePhone Development Log</author><pubDate>Tue, 23 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#082</guid></item><item><title>2023–05–22: Pinephone (Pro) power measurements and optimizations</title><link>https://xnux.eu/log/#081</link><author>megi's PinePhone Development Log</author><pubDate>Mon, 22 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#081</guid></item><item><title>Underscored Numbers</title><link>https://rjp.is/blogging/posts/2023/05/underscored_numbers/</link><description>In which we test some parsers.</description><author>infrequent oscillations</author><pubDate>Sun, 21 May 2023 13:03:36 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/05/underscored_numbers/</guid></item><item><title>Rebooting the Carolina Code Conference</title><link>https://www.brightball.com/articles/rebooting-the-carolina-code-conference</link><description>The Carolina Code Conference is a welcoming and community-driven “polyglot” conference that’s set to take place in beautiful downtown Greenville, SC on Saturday August 19th, 2023 in the Greenville ONE building. This conference, which returns for the first time since 2019, invites coders of all experience levels to attend, plug into the development community, share their experiences and have a great time as well.</description><author>Brightball Articles</author><pubDate>Sun, 21 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.brightball.com/articles/rebooting-the-carolina-code-conference</guid></item><item><title>Testing GPT-4 spatial reasoning and comprehension</title><link>https://bytepawn.com/testing-gpt-4-spatial-reasoning-and-comprehension.html</link><description>&lt;p&gt;I run experiments to determine whether, or to what degree, GPT-4 has developed an comprehension of spatial relationships. I find that it it significantly better than GPT-3.&lt;br /&gt;&lt;br /&gt; &lt;img alt="GPT-3" src="/images/square-room.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 21 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/testing-gpt-4-spatial-reasoning-and-comprehension.html</guid></item><item><title>Para Lottery</title><link>https://rjp.is/blogging/posts/2023/05/para-lottery/</link><description>In which we simulate a lottery.</description><author>infrequent oscillations</author><pubDate>Sat, 20 May 2023 23:46:37 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/05/para-lottery/</guid></item><item><title>Confessions</title><link>https://www.marginalia.nu/log/81-confessions/</link><description>I use print debugging all the time
I know how to use a debugger. I use a debugger sometimes, but most of my debugging is done by print statements that are like
A B C . . 5 D . D , {true, 30} . , . , 10 E I think Clean Code makes some valid points
I don&amp;rsquo;t think it should be your bible or treated as infallable, having seen the sort of code that came before it, yeah, Uncle Bob got some things right.</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 19 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/81-confessions/</guid></item><item><title>Leadership models IV: the Pareto Principle, the Peter Principle, the Rumsfeld Matrix, Servant Leadership and Pygmalion Effect, and Goleman's Emotional Intelligence Model</title><link>https://bytepawn.com/leadership-models-pareto-principle-rumsfeld-matrix-servant-leadership.html</link><description>&lt;p&gt;In this post, I describe the Pareto Principle, the Peter Principle, the Rumsfeld Matrix, Servant Leadership and Pygmalion Effect, and finally Goleman's Emotional Intelligence Model. &lt;br /&gt;&lt;br /&gt; &lt;img alt="Peter principle" src="/images/peter-principle.jpg" style="width: 200px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 19 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/leadership-models-pareto-principle-rumsfeld-matrix-servant-leadership.html</guid></item><item><title>20 Years of Gentoo</title><link>https://blog.nawaz.org/posts/2023/May/20-years-of-gentoo/</link><description>&lt;pre class="code shell literal-block"&gt;
$&lt;span class="w"&gt; &lt;/span&gt;genlop&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;emerge.log&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;head&lt;span class="w"&gt; &lt;/span&gt;-n20&lt;span class="w"&gt;

&lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;logfile&lt;span class="w"&gt; &lt;/span&gt;emerge.log&lt;span class="w"&gt;
 &lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;merged:&lt;span class="w"&gt;

     &lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;May&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;:24:00&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2003&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;sys-apps/portage-2.0.47-r10&lt;span class="w"&gt;
     &lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;May&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;:26:59&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2003&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;sys-apps/portage-2.0.47-r10&lt;span class="w"&gt;
     &lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;May&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;:27:49&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2003&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;sys-apps/portage-2.0.47-r10&lt;span class="w"&gt;
     &lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;May&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14 …&lt;/span&gt;&lt;/pre&gt;</description><author>Beetle Space</author><pubDate>Thu, 18 May 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2023/May/20-years-of-gentoo/</guid></item><item><title>How to start a Go project in 2023</title><link>https://boyter.org/posts/how-to-start-go-project-2023/</link><description>&lt;p&gt;I previously wrote about &lt;a href="https://boyter.org/posts/how-to-start-go-project-2018/"&gt;starting a Go project in 2018&lt;/a&gt;. A lot has changed since I wrote that and I had been wanting to write an updated version. What follows should be enough for anyone new to Go to get started and ideally start them being productive.&lt;/p&gt;
&lt;h2 id="quicklinks"&gt;Quicklinks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#install--setup"&gt;Install / Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#starting-a-project"&gt;Starting a Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#learning-go"&gt;Learning Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#searching"&gt;Searching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#building--installing"&gt;Building / Installing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#linting--static-analysis--security-scanning"&gt;Linting / Static Analysis / Security Scanning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#profiling"&gt;Profiling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#unit-testing"&gt;Unit Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#integration-testing"&gt;Integration Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#community"&gt;Community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#multiple-main-entry-points"&gt;Multiple Main Entry Points&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#os-specific-code"&gt;OS Specific Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#docker"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#useful-toolspackages"&gt;Useful Tools/Packages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="install--setup"&gt;Install / Setup&lt;/h2&gt;
&lt;p&gt;The first thing to do is download and install Go. I would suggest always installing from the Go website itself &lt;a href="https://golang.org/"&gt;https://golang.org/&lt;/a&gt; and following the instructions for your OS of choice. Other than the Go 1.18 release (which included generics) I have never had any issue always installing the newest version of Go and compiling away. The backwards compatibility promise is real. So much so even if a project&amp;rsquo;s go.mod file says 1.20, if it does not use any 1.20 functionality you can probably still compile it using an earlier release.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Tue, 16 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/how-to-start-go-project-2023/</guid></item><item><title>Two books I recommend to developers</title><link>http://notes.eatonphil.com/books-developers-should-read.html</link><description>&lt;p class="note"&gt;
  Originally published on February 1, 2021. The original version
  included two books I don't think are actually so worthwhile. This
  list is down to two. I think that's a good thing actually.
&lt;/p&gt;&lt;p&gt;These are the books I recommend to developers wanting to improve their
skills as professional programmers because of high information
density, believable premises/examples, and being well edited.&lt;/p&gt;
&lt;p&gt;You don't need to read books to improve as a developer but
they are unparalleled in quickly helping you gain depth in a subject.&lt;/p&gt;
&lt;h3 id="high-performance-browser-networking"&gt;High Performance Browser Networking&lt;/h3&gt;&lt;p&gt;If you deal with networks, you would probably benefit from this book.
It is a thorough high level introduction to mobile networks, browser
network protocols, and fundamentals of networking.&lt;/p&gt;
&lt;h3 id="designing-data-intensive-applications"&gt;Designing Data-Intensive Applications&lt;/h3&gt;&lt;p&gt;If you use a database (including an in-memory array of items you
search periodically) or if you build APIs, you would probably benefit
from this book. A solid introduction to distributed computing, data
transfer, indexing, etc.&lt;/p&gt;
&lt;h3 id="that's-it!"&gt;That's it!&lt;/h3&gt;&lt;p&gt;Generic software books conspicuously not on this list for
me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clean Code&lt;/li&gt;
&lt;li&gt;JavaScript the Good Parts&lt;/li&gt;
&lt;li&gt;Design Patterns/Gang of Four&lt;/li&gt;
&lt;li&gt;Structure and Interpretation of Computer Programs&lt;/li&gt;
&lt;li&gt;A Philosophy of Software Design&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They're not all bad but give nowhere near as much return for the
investment of your time.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Four books I recommend to professional developers wanting to improve their craft, and a few I'd not&lt;a href="https://t.co/1aTrfqZ9bd"&gt;https://t.co/1aTrfqZ9bd&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@phil_eaton) &lt;a href="https://twitter.com/phil_eaton/status/1356391931274756096?ref_src=twsrc%5Etfw"&gt;February 2, 2021&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 16 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/books-developers-should-read.html</guid></item><item><title>My favorite software subreddits</title><link>http://notes.eatonphil.com/high-quality-subreddits-you-should-be-following.html</link><description>&lt;p class="note"&gt;
  Originally published on December 5, 2021.
&lt;/p&gt;&lt;p&gt;If you are an experienced software developer whose only exposure to
reddit is dank memes, &lt;a href="https://reddit.com/r/programming"&gt;proggit&lt;/a&gt; or even
language-specific subreddits like
&lt;a href="https://reddit.com/r/python"&gt;/r/python&lt;/a&gt;, you're missing out.&lt;/p&gt;
&lt;p&gt;What follows are my favorite subreddits in tech. My criteria is that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The subreddit topic is relevant to advancing as a programmer&lt;/li&gt;
&lt;li&gt;Posts generally go into good depth&lt;/li&gt;
&lt;li&gt;The comments stay on topic&lt;/li&gt;
&lt;li&gt;And the shit-posting is minimal&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This list isn't hard to guess at if you consider advanced topics
in software. But I wanted to share because I think it's worth
explicitly supporting high-quality subreddits.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/EmuDev/"&gt;/r/EmuDev&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;My favorite sub of all. Also has a &lt;a href="https://www.reddit.com/r/EmuDev/comments/9mop2q/join_the_official_remudev_chat_on_discord/"&gt;phenomenal Discord group&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/programminglanguages"&gt;/r/ProgrammingLanguages&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;Focuses a little more on PLT topics (parsing techniques, syntax, type systems) than on compiling and interpreting techniques, but still good.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/DatabaseDevelopment/"&gt;/r/DatabaseDevelopment&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;All about database internals, which ends up involving a bunch of
correctness and distributed systems stuff as well.&lt;/li&gt;
&lt;li&gt;Disclosure: I run this sub. It's at 2.7k+ members at time of publishing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/ReverseEngineering/"&gt;/r/ReverseEngineering&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;The largest subreddit on this list but still has pretty good posts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/esolangs/"&gt;/r/EsoLangs&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;One of the best/most fun intros to programming languages/compilers/interpreters is through languages like Brainfuck. This sub does a good job of keeping the fun going.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/Compilers/"&gt;/r/Compilers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/GraphicsProgramming/"&gt;/r/GraphicsProgramming&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While some language subreddits are pretty good, they are more so a
mixed bag than some of the topic-specific subreddits here. So they
don't make my list, more on principle than anything else.&lt;/p&gt;
&lt;p&gt;If there is a good one already, send me it!&lt;/p&gt;
&lt;h3 id="what-am-i-missing?"&gt;What am I missing?&lt;/h3&gt;&lt;p&gt;Am I missing other amazing subreddits? Just don't say
language-specific ones. :)&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;It's an incorrect meme IMO that tech Reddit is low-quality. You just have to find the interesting subreddits.&lt;br /&gt;&lt;br /&gt;I've updated my list for 2023.&lt;a href="https://t.co/OtM2tk8HOn"&gt;https://t.co/OtM2tk8HOn&lt;/a&gt; &lt;a href="https://t.co/ymyzChp0SO"&gt;pic.twitter.com/ymyzChp0SO&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1658567638090391555?ref_src=twsrc%5Etfw"&gt;May 16, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 16 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/high-quality-subreddits-you-should-be-following.html</guid></item><item><title>Vim tip 26: executing shell commands</title><link>https://learnbyexample.github.io/tips/vim-tip-26/</link><description>&lt;p&gt;You can execute external commands from within Vim. Here are some examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;:!ls&lt;/kbd&gt; execute the given shell command and display output
&lt;ul&gt;
&lt;li&gt;the results are displayed as part of an expanded Command-line area, doesn't change contents of the file&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:.!date&lt;/kbd&gt; replace the current line with the output of the given command
&lt;ul&gt;
&lt;li&gt;pressing &lt;kbd&gt;!!&lt;/kbd&gt; in Normal mode will also result in &lt;kbd&gt;:.!&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!&lt;/code&gt; waits for motion similar to &lt;code&gt;d&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; commands, &lt;kbd&gt;!G&lt;/kbd&gt; will give &lt;kbd&gt;:.,$!&lt;/kbd&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:%!sort&lt;/kbd&gt; sort all the lines
&lt;ul&gt;
&lt;li&gt;recall that &lt;code&gt;%&lt;/code&gt; is a shortcut for the range &lt;code&gt;1,$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;note that this executes an external command, not the built-in &lt;code&gt;:sort&lt;/code&gt; command&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:3,8!sort&lt;/kbd&gt; sort only lines &lt;code&gt;3&lt;/code&gt; to &lt;code&gt;8&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:r!date&lt;/kbd&gt; insert output of the given command below the current line&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:r report.log&lt;/kbd&gt; insert contents of the given file below the current line
&lt;ul&gt;
&lt;li&gt;Note that &lt;code&gt;!&lt;/code&gt; is not used here since there is no shell command&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:.!grep '^Help ' %&lt;/kbd&gt; replace the current line with all the lines starting with &lt;code&gt;Help&lt;/code&gt; in the current file
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%&lt;/code&gt; here refers to current file contents&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:sh&lt;/kbd&gt; open a shell session within Vim
&lt;ul&gt;
&lt;li&gt;use &lt;code&gt;exit&lt;/code&gt; command to quit the session&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://vimhelp.org/various.txt.html#%3A%21cmd"&gt;:h :!&lt;/a&gt;, &lt;a href="https://vimhelp.org/various.txt.html#%3Ash"&gt;:h :sh&lt;/a&gt; and &lt;a href="https://vimhelp.org/insert.txt.html#%3Ar"&gt;:h :r&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 16 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-26/</guid></item><item><title>Leadership models III: First Principles Thinking</title><link>https://bytepawn.com/leadership-models-first-principles-thinking.html</link><description>&lt;p&gt;I describe Elon Musk's First Principles Thinking model through several examples: SpaceX, Warren Buffet's Berkshire, Google's organization design, Python's language design, Random Forests and Convolutional Neural Networks. &lt;br /&gt;&lt;br /&gt; &lt;img alt="Elon Musk First Principles Thinking" src="/images/first-principles-thinking.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Tue, 16 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/leadership-models-first-principles-thinking.html</guid></item><item><title>Enjoy the Season You’re In</title><link>https://letterstoanewdeveloper.com/2023/05/15/enjoy-the-season-youre-in/</link><description>This is a guest post from Aaron Kardell. Enjoy. Dear new developer, I’m enjoying the season I’m in. That’s the simple phrase I find myself conveying to others lately. I have 21 years of post-college professional experience. Except for a handful of short-term consulting gigs, all of that has been working on something I founded &amp;#8230; &lt;a class="more-link" href="https://letterstoanewdeveloper.com/2023/05/15/enjoy-the-season-youre-in/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Enjoy the Season You&amp;#8217;re&amp;#160;In&lt;/span&gt; &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Letters To A New Developer</author><pubDate>Mon, 15 May 2023 16:17:00 GMT</pubDate><guid isPermaLink="true">https://letterstoanewdeveloper.com/2023/05/15/enjoy-the-season-youre-in/</guid></item><item><title>Coincidental Scores</title><link>https://rjp.is/blogging/posts/2023/05/coincidental-scores/</link><description>In which the scores coincide.</description><author>infrequent oscillations</author><pubDate>Sun, 14 May 2023 13:08:19 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/05/coincidental-scores/</guid></item><item><title>Leadership models II: Growth Mindset, Eisenhower Matrix, Tuckman Model, Cynefin Framework, SCARF Model</title><link>https://bytepawn.com/leadership-models-growth-mindset-eisenhower-matrix-tuckman-model-cynefin-framework-scarf-model.html</link><description>&lt;p&gt;I describe the following mental models useful in leadership and self-management: the Growth Mindset, Eisenhower Matrix, Tuckman Model, Cynefin Framework, SCARF Model. &lt;br /&gt;&lt;br /&gt; &lt;img alt="Trust equation" src="/images/growth-mindset.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 14 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/leadership-models-growth-mindset-eisenhower-matrix-tuckman-model-cynefin-framework-scarf-model.html</guid></item><item><title>yurizaki 1.0.0</title><link>https://burakku.com/blog/yurizaki-announcement/</link><description>&lt;p&gt;&lt;img alt="Yurizaki Mira" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;It's finally out! My first ever Rust project!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Hamuko/yurizaki"&gt;Get it on GitHub now!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;yurizaki is a file copying and sorting daemon written in Rust, made specifically for anime and fansub releases. It's specifically meant to automatically upgrade files in your library with better release groups and newer versions. You can specify multiple release groups and it'll replace lower-ranked groups with higher-ranked groups when they are added to the source directory.&lt;/p&gt;
&lt;p&gt;Everything is set up with a YAML file. For example:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/src&lt;/span&gt;
&lt;span class="nt"&gt;library&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/dst&lt;/span&gt;

&lt;span class="nt"&gt;Go to Bed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;aliases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Kimi wa Nero&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Shino&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SupersDisplease&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then if you run yurizaki and start adding files to the source directory, you can see yurizaki matching, copying and removing files to keep the highest version of the highest-ranked release group in the library:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;MATCH: &amp;quot;[SupersDisplease] Kimi wa Nero - 01 (1080p) [3838CF72].mkv&amp;quot; =&amp;gt; Go to Bed
No other release
Copied &amp;quot;[SupersDisplease] Kimi wa Nero - 01 (1080p) [3838CF72].mkv&amp;quot; to &amp;quot;/dst/Go to Bed/[SupersDisplease] Kimi wa Nero - 01 (1080p) [3838CF72].mkv&amp;quot;

MATCH: &amp;quot;[Shino] Go to Bed 01 [1080p Hi10P AC3][F4D8E418].mkv&amp;quot; =&amp;gt; Go to Bed
Inferior release found: &amp;quot;/dst/Go to Bed/[SupersDisplease] Kimi wa Nero - 01 (1080p) [3838CF72].mkv&amp;quot;
Copied &amp;quot;[Shino] Go to Bed 01 [1080p Hi10P AC3][F4D8E418].mkv&amp;quot; to &amp;quot;/dst/Go to Bed/[Shino] Go to Bed 01 [1080p Hi10P AC3][F4D8E418].mkv&amp;quot;

MATCH: &amp;quot;[SupersDisplease] Kimi wa Nero - 01v2 (1080p) [3838CF72].mkv&amp;quot; =&amp;gt; Go to Bed
Superior release found: &amp;quot;/dst/Go to Bed/[Shino] Go to Bed 01 [1080p Hi10P AC3][F4D8E418].mkv&amp;quot;

MATCH: &amp;quot;[Shino] Go to Bed 01v2 [1080p Hi10P AC3][F4D8E418].mkv&amp;quot; =&amp;gt; Go to Bed
Inferior release found: &amp;quot;/dst/Go to Bed/[Shino] Go to Bed 01 [1080p Hi10P AC3][F4D8E418].mkv&amp;quot;
Copied &amp;quot;[Shino] Go to Bed 01v2 [1080p Hi10P AC3][F4D8E418].mkv&amp;quot; to &amp;quot;/dst/Go to Bed/[Shino] Go to Bed 01v2 [1080p Hi10P AC3][F4D8E418].mkv&amp;quot;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The end result is that only one file is retained in the library:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;tree&lt;span class="w"&gt; &lt;/span&gt;/dst/
&lt;span class="go"&gt;/dst/&lt;/span&gt;
&lt;span class="go"&gt;└── Go to Bed&lt;/span&gt;
&lt;span class="go"&gt;    └── [Shino] Go to Bed 01v2 [1080p Hi10P AC3][F4D8E418].mkv&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I started writing yurizaki sometime in 2020, or possibly even 2019. Then I got it to a reasonably working state and just started using it half-unfinished. After that time, I've actually &lt;a href="https://github.com/Hamuko?tab=repositories&amp;amp;language=rust"&gt;published several Rust projects on GitHub&lt;/a&gt;, meaning that this is not actually my first Rust release. But since all of those were written after the conception of yurizaki, I'm still going to call it the first.&lt;/p&gt;
&lt;p&gt;But despite being my first attempt at Rust, and being left unfinished literally for years, it has actually worked very well in my use. I guess that might be one of the benefits of Rust – if you get it to compile, and you didn't litter your code with shortcuts, it might reasonably handle any situation you throw at it. Although my code did definitely have some shortcuts, and still does, but it has still worked fine. I don't remember if I've ever yurizaki panic on me.&lt;/p&gt;
&lt;p&gt;I do still have some things I want to add to it though, so it's definitely not completely finished. I for example want to be able to run custom commands after copying to automate other things. It'd also be beneficial to reduce the remaining &lt;code&gt;unwrap()&lt;/code&gt; uses to improve stability. I've also noticed that &lt;a href="https://github.com/erengy/anitomy"&gt;anitomy&lt;/a&gt; doesn't always play nicely with &lt;a href="https://anilist.co/anime/145665/NieRAutomata-Ver11a/"&gt;certain perverted titles&lt;/a&gt;, so regular expression overrides might also be a good idea.&lt;/p&gt;
&lt;p&gt;Oh yeah, and there definitely are no unit tests yet. I always find unit testing in Rust to be a pain in comparison to a more flexible language like Python.&lt;/p&gt;
&lt;p&gt;(Named after Yurizaki Mira from Dimension W, an absolute cutie patootie.)&lt;/p&gt;</description><author>ブラック</author><pubDate>Sat, 13 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/yurizaki-announcement/</guid></item><item><title>CI/CD with KiCad and Gitlab</title><link>https://sschueller.github.io/posts/ci-cd-with-kicad-and-gitlab/</link><description>&lt;div class="featured-image"&gt;
                &lt;img src="/posts/ci-cd-with-kicad-and-gitlab/render.png" /&gt;
            &lt;/div&gt;&lt;p&gt;(Blender Render of KiCad Exported PCB)&lt;/p&gt;</description><author>Stefan Schüller</author><pubDate>Fri, 12 May 2023 18:37:16 GMT</pubDate><guid isPermaLink="true">https://sschueller.github.io/posts/ci-cd-with-kicad-and-gitlab/</guid></item><item><title>Leadership models I: Iceberg Model, Six Thinking Hats, Trust Equation, Circle of influence, OODA Loop</title><link>https://bytepawn.com/leadership-models-iceberg-model-six-thinking-hats-trust-equation-circle-of-influence-ooda-loop.html</link><description>&lt;p&gt;I describe the following mental models useful in leadership and self-management: Iceberg Model, Six Thinking Hats, Trust Equation, Circle of Influence and OODA Loop. &lt;br /&gt;&lt;br /&gt; &lt;img alt="Trust equation" src="/images/trust-equation.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 12 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/leadership-models-iceberg-model-six-thinking-hats-trust-equation-circle-of-influence-ooda-loop.html</guid></item><item><title>(incomplete) Getting your O1 Visa as Fast as Possible (2024)</title><link>https://www.swyx.io/o1</link><description>&lt;p&gt;I have just received my O1A Visa and like &lt;a href="https://www.swyx.io/h1b1"&gt;for the H1B1&lt;/a&gt;, I figured I should write down my experience, thoughts, and tips for those who may wish to make the same journey. Note that I am not a professional at this, I'm just a guy who recently went through it so dont rely on me for precise facts, mostly a fellow traveler a little ahead of you.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 11 May 2023 22:19:29 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/o1</guid></item><item><title>My New Startup Checklist</title><link>https://www.swyx.io/my-new-startup-checklist</link><description>&lt;p&gt;some of you may know I've recently started a new company. I'm not ready to talk about -that- yet, but I did want to capture some notes on logistical stuff I have had to ramp up on as a first time founder. hopefully this helps somebody out there.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 11 May 2023 10:34:22 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/my-new-startup-checklist</guid></item><item><title>CLI text processing with GNU grep and ripgrep book announcement</title><link>https://learnbyexample.github.io/cli-text-processing-grep-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;CLI text processing with GNU grep and ripgrep&lt;/strong&gt; ebook. Examples, exercises, solutions, descriptions and external links were added/updated/corrected. The chapter on &lt;code&gt;ripgrep&lt;/code&gt; was changed significantly to focus mostly on the differences compared to &lt;code&gt;GNU grep&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This book will help you learn these commands step-by-step from beginner to advanced levels with &lt;strong&gt;hundreds of examples and exercises&lt;/strong&gt;.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of &lt;strong&gt;CLI text processing with GNU grep and ripgrep&lt;/strong&gt; for FREE till 21-May-2023. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/gnugrep_ripgrep"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/gnugrep_ripgrep/c/new_grep_release"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other offers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/cli_computing"&gt;Computing from the Command Line&lt;/a&gt; is FREE — Linux command line tools and Shell Scripting for beginner to intermediate level users&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/new_grep_release"&gt;All Books Bundle&lt;/a&gt; is $12 (normal price $32) — all my 13 programming ebooks&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Command versions updated to &lt;strong&gt;GNU grep 3.10&lt;/strong&gt; and &lt;strong&gt;ripgrep 13.0.0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Many more exercises added&lt;/li&gt;
&lt;li&gt;PCRE chapter — added section for conditional grouping, corrected description and examples for &lt;code&gt;\K&lt;/code&gt;, atomic grouping, etc&lt;/li&gt;
&lt;li&gt;ripgrep chapter — options and regex section modified to present only differences compared to &lt;code&gt;GNU grep&lt;/code&gt;, added details for more options such as &lt;code&gt;--field-match-separator&lt;/code&gt;, improved recursive search section, etc&lt;/li&gt;
&lt;li&gt;Long sections split into smaller ones&lt;/li&gt;
&lt;li&gt;In general, many of the examples, exercises, solutions, descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;Book title changed to &lt;strong&gt;CLI text processing with GNU grep and ripgrep&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;li&gt;Images centered for EPUB format&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;On this blog, I &lt;a href="https://learnbyexample.github.io/tips/"&gt;post tips&lt;/a&gt; covering Python, command line tools and Vim. Here are video demos for these tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=THSMmCZQn1A&amp;amp;list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p0KCLusMd5Q&amp;amp;list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="interactive-tui-app"&gt;Interactive TUI app&lt;a class="zola-anchor" href="#interactive-tui-app"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I also wrote an &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/GrepExercises"&gt;interactive TUI app&lt;/a&gt; based on some of the exercises from the ebook. Reference solutions are provided for both &lt;code&gt;GNU grep&lt;/code&gt; and &lt;code&gt;ripgrep&lt;/code&gt;.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot from the interactive TUI app for grep exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/GrepExercises/grep_exercises.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Frequently used options&lt;/li&gt;
&lt;li&gt;BRE/ERE Regular Expressions&lt;/li&gt;
&lt;li&gt;Context matching&lt;/li&gt;
&lt;li&gt;Recursive search&lt;/li&gt;
&lt;li&gt;Miscellaneous options&lt;/li&gt;
&lt;li&gt;Perl Compatible Regular Expressions&lt;/li&gt;
&lt;li&gt;Gotchas and Tricks&lt;/li&gt;
&lt;li&gt;ripgrep&lt;/li&gt;
&lt;li&gt;Further Reading&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/learn_gnugrep_ripgrep/"&gt;https://learnbyexample.github.io/learn_gnugrep_ripgrep/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep"&gt;https://github.com/learnbyexample/learn_gnugrep_ripgrep&lt;/a&gt; for markdown source, example files, exercise solutions, sample chapters and other details related to the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tips, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep/issues"&gt;https://github.com/learnbyexample/learn_gnugrep_ripgrep/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 11 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/cli-text-processing-grep-announcement/</guid></item><item><title>3D Printing The Hadley 114mm Newtonian Telescope</title><link>https://miscdotgeek.com/3d-printing-the-hadley-114mm-newtonian-telescope/</link><description>&lt;p&gt;Yes, we&amp;#8217;re building a 3D Printed Newtonian Telescope called Hadley. It&amp;#8217;s being printed in PETG and in the video below, I give a quick tour. My build isn&amp;#8217;t done yet, but it&amp;#8217;s darn close. In the next video I&amp;#8217;ll be discussing aligning (collimating) the optics and hopefully have some first impressions to share. More info &amp;#8230; &lt;/p&gt;
&lt;p&gt;&lt;a class="more-link btn" href="https://miscdotgeek.com/3d-printing-the-hadley-114mm-newtonian-telescope/" rel="noopener noreferrer" target="_self"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://miscdotgeek.com/3d-printing-the-hadley-114mm-newtonian-telescope/" rel="noopener noreferrer" target="_self"&gt;3D Printing The Hadley 114mm Newtonian Telescope&lt;/a&gt; appeared first on &lt;a href="https://miscdotgeek.com" rel="noopener noreferrer" target="_self"&gt;MiscDotGeek&lt;/a&gt;.&lt;/p&gt;</description><author>MiscDotGeek</author><pubDate>Wed, 10 May 2023 02:57:38 GMT</pubDate><guid isPermaLink="true">https://miscdotgeek.com/3d-printing-the-hadley-114mm-newtonian-telescope/</guid></item><item><title>CLI tip 27: reverse text line wise with tac</title><link>https://learnbyexample.github.io/tips/cli-tip-27/</link><description>&lt;p&gt;You can use &lt;code&gt;tac&lt;/code&gt; to reverse the input line wise. If you pass multiple input files, each file content will be reversed separately.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple\nbanana\ncherry\nfig and honey\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; tac
&lt;/span&gt;&lt;span&gt;fig &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;and&lt;/span&gt;&lt;span&gt; honey
&lt;/span&gt;&lt;span&gt;cherry
&lt;/span&gt;&lt;span&gt;banana
&lt;/span&gt;&lt;span&gt;apple
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use the &lt;code&gt;-s&lt;/code&gt; option to specify a different string to be used as the &lt;em&gt;line&lt;/em&gt; separator (newline is the default separator). When the custom separator occurs before the content of interest, use the &lt;code&gt;-b&lt;/code&gt; option to print those separators before the content in the output as well.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat blocks.txt
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;%=%=
&lt;/span&gt;&lt;span&gt;apple
&lt;/span&gt;&lt;span&gt;banana
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;%=%=
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;%=%=
&lt;/span&gt;&lt;span&gt;red
&lt;/span&gt;&lt;span&gt;green
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ tac &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;b &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'%=%='&lt;/span&gt;&lt;span&gt; blocks.txt
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;%=%=
&lt;/span&gt;&lt;span&gt;red
&lt;/span&gt;&lt;span&gt;green
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;%=%=
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;%=%=
&lt;/span&gt;&lt;span&gt;apple
&lt;/span&gt;&lt;span&gt;banana
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://learnbyexample.github.io/tips/cli-tip-8/"&gt;CLI tip 8: extract from start of file until matching line&lt;/a&gt; for a practical example where reversing input content helps in constructing a solution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/cli_text_processing_coreutils/cat-tac.html#tac"&gt;tac&lt;/a&gt; section from my &lt;a href="https://github.com/learnbyexample/cli_text_processing_coreutils"&gt;Command line text processing with GNU Coreutils&lt;/a&gt; ebook for more details and examples.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 09 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-27/</guid></item><item><title>Setting up Hetzner ARM instances with and for Objective-S</title><link>https://blog.metaobject.com/2023/05/setting-up-hetzner-arm-instances-with.html</link><description>The recent &lt;a href="https://www.hetzner.com/press-release/arm64-cloud/"&gt;introduction&lt;/a&gt;  
of reasonably-priced ARM64 VPS instances by Hetzner was accompanied by a big smile and
sigh of relief on my part, as I had previously made the decision to prioritize ARM with
Objective-S, for example the native-compiler is currently ARM64-only, but the simple and
low-cost VPS providers like Digital Ocean were sticking to x86 exclusively.&lt;p&gt;

Although it is possible to operate in a mixed ARM/x86 environment, the added complexity 
is not something I want as a default, which is why I also switched the hosting of the
&lt;a href="http://objective.st/"&gt;Objective-S site&lt;/a&gt; from DO to the Oracle cloud (on their
"free forever" tier), as it was the only way to host on ARM without incurring monthly 
charges upwards of $40.  With a number of alternatives spanning the spectrum, I now felt
it &lt;p&gt;

I've long had a strong hunch that there is both room and a strong need for something 
between the "we'll just hack together a few simple shell scripts" of the (very good!)
&lt;a href="https://deploymentfromscratch.com/"&gt;Deployment from Scratch&lt;/a&gt; and the
&lt;a href="https://2020.programming-conference.org/home/salon-2020"&gt;aircraft carrier&lt;/a&gt;
that is Kubernetes.&lt;p&gt;

With the external pieces finally in place, it's time to follow that hunch, and what 
better way than to control the Hetzner server API using Objective-S?&lt;p&gt;

&lt;h3&gt;Talking to the API&lt;/h3&gt;

Perusing the &lt;a href="https://docs.hetzner.cloud/#overview"&gt;documentation&lt;/a&gt;, we see
that the base URL for talking to the API is &lt;code&gt;https://api.hetzner.cloud/v1/&lt;/code&gt;.
So let's set up an API scheme handler for talking to the Hetzner API, and also set
up the authentication header and indicate that we will be using JSON:&lt;p&gt;

&lt;hr /&gt;&lt;code&gt;&lt;pre&gt;
scheme:https setHeaders: #{ 
    #Content-Type: 'application/json'
    #Authorization: "Bearer {keychain:password/hetzner-api/metaobject}",
       }.
scheme:api := ref:https://api.hetzner.cloud/v1 asScheme.
&lt;/pre&gt;&lt;/code&gt;&lt;hr /&gt;

It's not a lot of code, but there is quite a bit going on:  first, the token is
stored in the macOS keychain, accessed via &lt;code&gt;keychain:password/hetzner-api/metaobject&lt;/code&gt;.
This is interpolated into the Bearer string inside a dictionary literal.  The &lt;code&gt;api:&lt;/code&gt; scheme
is now available for talking to the Hetzner API, so for example &lt;code&gt;api:servers&lt;/code&gt; will
be sent as &lt;code&gt;https://api.hetzner.cloud/v1/servers&lt;/code&gt;. &lt;p&gt;

That setup now allows us to define a simple class that allows us to interact with the API:

&lt;hr /&gt;&lt;code&gt;&lt;pre&gt;
class HetznerCloud {
   var api.
   -schemeNames { [ 'api' ]. }
   -images {
	api:images.
   }
   -types {
	api:server_types.
   }
} 
&lt;/pre&gt;&lt;/code&gt;&lt;hr /&gt;

It currently has two user-facing methods:  &lt;code&gt;-images&lt;/code&gt;, which lists the kinds of 
images that are available and &lt;code&gt;-types&lt;/code&gt;, which lists the server types.  The method
bodies may appear to be a little short, but that really is all that's needed.  The &lt;code&gt;-schameNames&lt;/code&gt;
method makes the &lt;code&gt;api:&lt;/code&gt; scheme handler available within method bodies of this class.&lt;p&gt;

Below is an excerpt of an interactive st-shell session first asking the API for image types and then for
server types:&lt;p&gt;

&lt;hr /&gt;&lt;code&gt;&lt;pre&gt;
] cloud images
{ "images" = ( { "id" = 3;
"description" = "CentOS 7";
"created_from" = ;
"bound_to" = ;
"rapid_deploy" = true;
"deprecated" = ;
"os_flavor" = "centos";
"type" = "system";
"protection" = { "delete" = false;
} ;
"image_size" = ;
"labels" = { } ;
"deleted" = ;
"architecture" = "x86";
"created" = "2018-01-15T11:34:45+00:00";
"os_version" = "7";
"disk_size" = 5;
"status" = "available";
...
] cloud types
...
{ "memory" = 4;
"prices" = ( { "price_monthly" = { "net" = "3.2900000000";
"gross" = "3.9151000000000000";
} ;
...
} ;
} ) ;
"storage_type" = "local";
"id" = 45;
"cpu_type" = "shared";
"disk" = 40;
"deprecated" = ;
"architecture" = "arm";
"description" = "CAX11";
"name" = "cax11";
"cores" = 2;
}
...
 &lt;/pre&gt;&lt;/code&gt;&lt;hr /&gt;

The "CAX11" instance type is the entry-level ARM64 instance that we want to use.


&lt;h3&gt;Creating a server&lt;/h3&gt;

Creating a VPS is accomplished by POSTing a dictionary describing the desired
properties of the server to the &lt;code&gt;servers&lt;/code&gt; endpoint:

&lt;hr /&gt;&lt;code&gt;&lt;pre&gt;
extension HetznerCloud {
   -baseDefinition {
	#{ 
	    #location: 'fsn1',
	    #public_net: #{
                #enable_ipv4: true,
                #enable_ipv6: false,
           }
	}.
   }
   -armServerDefinition {
	#{
           #name:  'objst-2',
           #image: '103908070',
           #ssh_keys: ['marcel@naraht.local' ],
           #server_type: 'cax11',
	} , self baseDefinition.
   }
   -create {
	  ref:api:servers post: self armServerDefinition  asJSON.
   }
}
&lt;/pre&gt;&lt;/code&gt;&lt;hr /&gt;

The &lt;code&gt;-create&lt;/code&gt; sends the &lt;code&gt;post:&lt;/code&gt; message directly
to the reference of the endpoint. &lt;p&gt;

&lt;h3&gt;Interacting with servers&lt;/h3&gt;

Once we have a server, we probably want to interact with it in some way,
at the very least to be able to delete it again.  Although we could do
this using methods of the cloud API taking an extra &lt;code&gt;server_id&lt;/code&gt;
parameter, it is nicer to create a separate server abstraction that 
lets us interact with the server and encapsulates the necessary information.&lt;p&gt;

The &lt;code&gt;HetznerHost&lt;/code&gt; is initialized with a server response from which it
uses the ip address and the server id, the latter to define a &lt;code&gt;server:&lt;/code&gt;
scheme handler.  The fact that it's a subclass of &lt;code&gt;MPWRemoteHost&lt;/code&gt; will
become relevant later.&lt;p&gt;

&lt;hr /&gt;&lt;code&gt;&lt;pre&gt;
class HetznerHost : MPWRemoteHost {
   var hostDict.
   var id.
   var server.

   +withDictionary:theServer {
	self alloc initWithDictionary:theServer.
   }
   -initWithDictionary:theServer {
       self := super initWithName:(theServer at:'public_net' | at:'ipv4' | at:'ip') user:'root'.
       self setHostDict:theServer.
       self setId: theServer['id'].
       self setServer: ref:api:/servers/{this:id} asScheme.

       self.
     }
     -schemeNames { ['server']. }
     -status { this:hostDict at:'status'. }
     -delete {
         ref:server:/ delete.

     }
}
&lt;/pre&gt;&lt;/code&gt;&lt;hr /&gt;

The DELETE is handled similarly to the POST above, by sending a &lt;code&gt;delete&lt;/code&gt; message to
the root reference of the &lt;code&gt;server:&lt;/code&gt; scheme.&lt;p&gt;

We get server instances with a GET from the API's &lt;code&gt;servers&lt;/code&gt; endpoint, the same
one we POSTed to create the server.  The &lt;code&gt;collect&lt;/code&gt; HOM makes it straightforward
to map from the dictionaries returned by the APU to actual server objects:&lt;p&gt;

&lt;hr /&gt;&lt;code&gt;&lt;pre&gt;
extension HetznerCloud {
   -servers {
	HetznerHost collect withDictionary: (api:servers at:'servers') each.
   }
}
&lt;/pre&gt;&lt;/code&gt;&lt;hr /&gt;

At this point, you're probably thinking that having a class representing servers,
with its own scheme-handler to boot, is a bit of overkill if all we are going to
do is send a DELETE.  And you'd be right, so here are some of the other capabilities:

&lt;hr /&gt;&lt;code&gt;&lt;pre&gt;
extension HetznerHost {
     -actions { api:servers/{this:id}/actions value.  }
     -liveStatus { server:status. }
     -refresh {
         self setHostDict: (server:/ value at:'server').
     }
     -shutdown {
         ref:server:actions/shutdown post:#{}.
     }
     -start {
         ref:server:actions/poweron post:#{}.
     }
     -reinstall:osName {
         ref:server:actions/rebuild post: #{ #image: osName }.
     }
     -reinstall {
         self reinstall:'ubuntu-20.04'.
     }
}
&lt;/pre&gt;&lt;/code&gt;&lt;hr /&gt;

With this, we have complete lifecycle control over the server, with a surprisingly
small amount of surprisingly straightforward code, thanks to Objective-S abstractions
such as Polymorphic Identifiers, Storage Combinators and Higher Order Messaging.&lt;p&gt;

What's more, this control is available both immediately in script form, as well
as for reuse in other applications as objects.&lt;p&gt;

&lt;h3&gt;Installing Objective-S&lt;/h3&gt;

Now that we can create, start, stop and destroy virtual servers, it would be nice
to actually do something with them.  For example:  run Objective-S and Objective-S-based
web-servers.&lt;p&gt;

This is where the &lt;code&gt;MPWRemoteHost&lt;/code&gt; comes in.  This is what it says on the tin:
a representation of a remote host, very rudimentary for now.  One of the few things it
knows how to do is set up an ssh connection to that remote host to execute commands 
and transfer files via SFTP.  The latter is surfaced as a store, so you can create
files on a remote host as easily as assigning to a local variable:

&lt;hr /&gt;&lt;code&gt;&lt;pre&gt;
dest:hello.txt := 'Hello world!'.
&lt;/pre&gt;&lt;/code&gt;&lt;hr /&gt;

Copying files is similar:

&lt;hr /&gt;&lt;code&gt;&lt;pre&gt;
dest:hello.txt := file:hello.txt.
&lt;/pre&gt;&lt;/code&gt;&lt;hr /&gt;

The script copies a tar archive containing both GNUstep and the Objective-S libraries,
which it then untars into the &lt;code&gt;'/usr'&lt;/code&gt; directory of the target machine. In
addition it transfers the interactive Objective-S shell &lt;code&gt;st&lt;/code&gt;, the 
&lt;code&gt;runsite&lt;/code&gt; command that serves ".sited" bundles via HTTP, and a &lt;code&gt;.bashrc&lt;/code&gt;
that sets up some needed environment variables.

&lt;hr /&gt;&lt;code&gt;&lt;pre&gt;
extension MPWHost { 
 -installObjS {
	scheme:dest := self store.
	filenames := [ 'ObjS-GNUstep-installed.tgz', 'st', '.bashrc', 'runsite' ].
	filenames do: { :filename | 
	     dest:{filename} := file:{filename}.
	}.
	self run:'chmod a+x st runsite';
	     run:'cd /usr ; tar zxf ~/ObjS-GNUstep-installed.tgz';
	     run:'mv st /usr/local/bin';
	     run:'mv runsite /usr/local/bin'.
   }
}
host := MPWHost host:hostip user:'root'.
host installObjS.
&lt;/pre&gt;&lt;/code&gt;&lt;hr /&gt;

As this is an extension to &lt;code&gt;MPWHost&lt;/code&gt;, which is the superclass 
of the &lt;code&gt;MPWRemoteHost&lt;/code&gt; we used as the base for our &lt;code&gt;HetznerHost&lt;/code&gt;,
the server objects we use have the ability to install Objective-S on them.  Neat.&lt;p&gt;

And so do the server objects for the very similar script controlling DO droplets.&lt;p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

When I started out on this little excursion, my goal was not to demonstrate anything
about Objective-S, I only needed to be able to use these cloud systems, and my hunch
was that Objective-S would be good for the task.&lt;p&gt;

It turned out even better than my hunch had suggested:  the various features and 
characteristics of Objective-S, such as Polymorphic Identifiers, first class
references, nested scheme handlers, and Higher Order Messaging, really work together
quite seamlessly to allow interaction with both a REST API and with a remote host to
be expressed compactly and naturally.  In addition, it manages to naturally bridge
the gap between ad-hoc scripting and proper modelling, remaining hackable without
creating a mess.&lt;p&gt;

It's working...&lt;p&gt;</description><author>metablog</author><pubDate>Mon, 08 May 2023 13:19:31 GMT</pubDate><guid isPermaLink="true">https://blog.metaobject.com/2023/05/setting-up-hetzner-arm-instances-with.html</guid></item><item><title>R.I.P. Memex</title><link>https://www.marginalia.nu/log/80-rip-memex/</link><description>I killed the old memex.marginalia.nu site. Not because it wasn&amp;rsquo;t great, but because I don&amp;rsquo;t have the time to maintain the software, which was quite janky, and perhaps most of all I wasn&amp;rsquo;t really feeling it.
The new site looks superficially similar, but it&amp;rsquo;s actually just a Hugo template that emulates some of the memex&amp;rsquo; capabilities. Although some of the coolest stuff is sadly gone as a result.
Thankfully I decided to use an extremely portable markup format when building the original memex software, which meant that porting it over to hugo was literally just a matter of writing a brief python script.</description><author>Weblog on marginalia.nu</author><pubDate>Mon, 08 May 2023 11:49:28 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/80-rip-memex/</guid></item><item><title>Synergia review</title><link>https://burakku.com/blog/synergia-review/</link><description>&lt;p&gt;&lt;img alt="Synergia" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I've obtained quite a few games from various kinds of bundles, and I imagine that this was one of them since I cannot remember paying money for this one. And having played through it, I feel like that was the right choice.&lt;/p&gt;
&lt;p&gt;The story and writing aren't really that good. I couldn't really get into the story and the characters, who don't really feel like actual people. I guess the androids get a pass for that, but the people definitely don't. You are given a couple of choices during the story, but it doesn't feel like they really do anything beyond the next couple of minutes of story. Feels like they were just thrown in there to be able to say the game has "choices" (plural).&lt;/p&gt;
&lt;p&gt;The bad end feels like driving a car into a brick wall. I was shocked to see the credits roll. Completely feels like the story was just left unfinished. The good end felt less abrupt but still not exactly like a satisfying conclusion. Neither of them really left me satisfied and the epilogue didn't help things. And to top things off, there are issues with spelling and grammar. The script should be ran through a spell checker.&lt;/p&gt;
&lt;p&gt;I don't really like the art. Most of it looks like they left in the placeholder art. I'm guessing that's a style that they're purposefully aiming for but it really doesn't click with me. There are a couple of hits when it comes to the CG, but most art were misses for me. There's also not a great variety of art on display.&lt;/p&gt;
&lt;p&gt;The soundtrack is super simple but fitting. It's probably my favourite part of the game. I'm not sure if I would ever actually sit down and listen to it specifically, but it fits the general mood of the game. No voice acting but I'm not sure if I would've wanted any from this game anyways.&lt;/p&gt;
&lt;p&gt;In addition to the issues with the &lt;em&gt;visual&lt;/em&gt; and the &lt;em&gt;novel&lt;/em&gt; aspects of this &lt;em&gt;visual novel&lt;/em&gt;, it also seems to suffer from technical issues. When I read through it, there were these text transitions between scenes that flashed by my screen in like 0.2 seconds. Was this the intended effect or was it a bug? Could go either way but definitely felt like the latter. There's also a setting that I cannot figure out what it actually does.&lt;/p&gt;
&lt;p&gt;At one point there's also a completely black screen, and you're just kinda supposed to figure out on your own that you need to advance manually to continue the story. And even when you are shown the dialogue UI, it's really not good. The font is also very small to a degree where it feels awful ergonomically. I do have to wonder what kind of visual novels the developers are used to reading. I at least don't remember another VN with such a poor user interface.&lt;/p&gt;
&lt;p&gt;There's also no cross-saves, so I wouldn't try to split the read between your PC and Steam Deck. Not that you'd want to read through it on your Steam Deck anyways, since the text is tiny and there's no ready controller layout. You'd either have to make your own or stick to trackpad controls. For what it's worth though, it seems to run "fine" (the same way it does on Windows) on the Steam Deck.&lt;/p&gt;
&lt;p&gt;There's really just not much to like here. While my monetary investment towards this product is minimal, I am still disappointed at what return I got for my time investment. Honestly speaking, I would not recommend this visual novel to anyone.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sun, 07 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/synergia-review/</guid></item><item><title>GPT News Poet: silly AI poems based on today's news</title><link>https://bytepawn.com/gpt-news-poet-silly-ai-poems-based-on-todays-news.html</link><description>&lt;p&gt;I show how I used GNews and the OpenAI API to build &lt;a href="(https://gptnewspoet.bytepawn.com)"&gt;GPT News Poet&lt;/a&gt;, a fun toy site showing silly AI poems based on today's news. &lt;br /&gt;&lt;br /&gt; &lt;img alt="ROC curve" src="/images/gpt-news-poet-example-short.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 07 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/gpt-news-poet-silly-ai-poems-based-on-todays-news.html</guid></item><item><title>An off-ramp from the digital IKEA maze</title><link>https://www.marginalia.nu/log/79-ikea-offramp/</link><description>There is an episode of Star Trek where a character is for plot reasons trapped in a shrinking parallel universe. As time passes, people she knows one by one just vanish and she is the only one who seems to notice. Eventually it gets to an absurd point. She asks if it really makes sense if a ship made for a thousand people would have a crew of a few people, and everyone just sort of like shrugs and looks at her like she&amp;rsquo;s crazy.</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 04 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/79-ikea-offramp/</guid></item><item><title>Python tip 27: enumerate() function</title><link>https://learnbyexample.github.io/tips/python-tip-27/</link><description>&lt;p&gt;When you use a &lt;code&gt;for&lt;/code&gt; loop, you get one element per each iteration. If you need the &lt;em&gt;index&lt;/em&gt; of the elements as well, use the &lt;a href="https://docs.python.org/3/library/functions.html#enumerate"&gt;enumerate()&lt;/a&gt; built-in function. You'll get a &lt;code&gt;tuple&lt;/code&gt; value per each iteration, containing index (starting with &lt;code&gt;0&lt;/code&gt; by default) and the value at that index.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3.14&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1000&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; for &lt;/span&gt;&lt;span&gt;t &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;enumerate&lt;/span&gt;&lt;span&gt;(nums):
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(t)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3.14&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1000&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;names &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Jo'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Joe'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Jon'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[(n1, n2) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;i, n1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;enumerate&lt;/span&gt;&lt;span&gt;(names) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;n2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;names[i&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;:]]
&lt;/span&gt;&lt;span&gt;[(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Jo'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Joe'&lt;/span&gt;&lt;span&gt;), (&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Jo'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Jon'&lt;/span&gt;&lt;span&gt;), (&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Joe'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Jon'&lt;/span&gt;&lt;span&gt;)]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By setting the &lt;code&gt;start&lt;/code&gt; argument, you can change the initial value of the index.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;items &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'car'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'table'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'book'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; for &lt;/span&gt;&lt;span&gt;idx, val &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;enumerate&lt;/span&gt;&lt;span&gt;(items, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;start&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;):
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;f&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;{idx}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;: &lt;/span&gt;&lt;span&gt;{val}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;: car
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;: table
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;: book
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 03 May 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-27/</guid></item><item><title>Anagrammatic Bus Stops</title><link>https://rjp.is/blogging/posts/2022/09/anagrammatic-bus-stops/</link><description>In which we analyse some bus stops.</description><author>infrequent oscillations</author><pubDate>Tue, 02 May 2023 13:11:27 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2022/09/anagrammatic-bus-stops/</guid></item><item><title>What I'm up to - May 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-may-2023</link><description>&lt;div class="prose"&gt;
  &lt;h2&gt;✨ What I've been up to&lt;/h2&gt;&lt;div&gt;April was a blur - I was in the Bay Area, NYC, and Cleveland. I'm looking forward to spending the next three weeks without travel. &lt;br /&gt;&lt;br /&gt;I am currently immersed in the development of &lt;a href="https://booklet.community"&gt;Booklet&lt;/a&gt;, a new product from &lt;a href="https://contraption.co"&gt;Contraption Co.&lt;/a&gt; designed for online online communities. I started building Booklet to address the shortcomings of using real-time chat apps for communities. Booklet strives to be calmer - it combines a forum and member directory, and generates a customized email newsletter for the &lt;a href="https://en.wikipedia.org/wiki/1%25_rule"&gt;99%&lt;/a&gt; of community members who consume content rather than create it. If you're intrigued and want early access, register at &lt;a href="https://booklet.community"&gt;booklet.community&lt;/a&gt;.&lt;/div&gt;&lt;h2&gt;🤔 Things to share&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles&lt;/strong&gt;: &lt;a href="https://www.mrmoneymustache.com/2023/04/07/car-free-cities/"&gt;Less Cars, More Money&lt;/a&gt; - ". . . &lt;em&gt;the&lt;/em&gt; &lt;em&gt;only reason we think we need cars to get around, is because we have wasted most of our space on accommodating cars, which spread everything out so far (and made everything so loud and dangerous) that nobody feels like walking or biking!"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books&lt;/strong&gt;: &lt;a href="https://www.amazon.com/Let-My-People-Go-Surfing-audiobook/dp/B01KB9LY6I/ref=sr_1_2?crid=PILVR688LMYV&amp;amp;keywords=patagonia+founder&amp;amp;qid=1682977469&amp;amp;sprefix=pagatagonia+found%2Caps%2C142&amp;amp;sr=8-2"&gt;Let My People Go Surfing&lt;/a&gt;. &lt;a href="https://railsandhotwirecodex.com/"&gt;The Rails and Hotwire Codex&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffee I'm drinking: &lt;/strong&gt;&lt;a href="https://www.prologcoffee.com/collections/buy-our-coffee/products/santa-teresa-natural-geisha?variant=46414055178568"&gt;Santa Teresa Natural Geisha from Prolog&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;📫 What I'm up to this month&lt;/h2&gt;&lt;div&gt;Working and relaxing, with a short trip to Colorado. I hope warm weather returns soon! &lt;/div&gt;&lt;h2&gt;📍 Where I'll be &lt;/h2&gt;&lt;div&gt;&lt;em&gt;(Let me know if we overlap!)&lt;/em&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;May 24-28: 🏔️ Denver&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;
&lt;br /&gt;&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbFJXIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--e8b3c2f4fe7d7fcdb59ce063685003546a5d9246/483FDEF0-7923-447D-AA04-97457CC1FCD4_1_105_c.jpeg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Photo of the month: Baking canelés&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Tue, 02 May 2023 01:36:45 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-may-2023</guid></item><item><title>February to April Gears emulator update</title><link>https://anisse.astier.eu/gears-update-2023-03.html</link><description>&lt;p&gt;&lt;a href="talks-emulation.html"&gt;Previously&lt;/a&gt;, I &lt;a href="gears-update-2023-01.html"&gt;had a bug&lt;/a&gt; in my &lt;a href="https://github.com/anisse/gears"&gt;gears emulator&lt;/a&gt;, which &lt;a href="gears-update-2023-02.html"&gt;I then fixed&lt;/a&gt;. But Sonic 2 still wasn't working, so let's see what went wrong there.&lt;/p&gt;
&lt;h1&gt;Missing interrupt behaviour&lt;/h1&gt;
&lt;p&gt;The Sonic 2 ROM wasn't starting at all, leaving an uninteresting black screen, which led me to suspect some kind of …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Tue, 02 May 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/gears-update-2023-03.html</guid></item><item><title>Escaping the Time Trap: Why Estimating Effort, Not Time, Leads to Greater Success</title><link>https://smcleod.net/2023/04/escaping-the-time-trap-why-estimating-effort-not-time-leads-to-greater-success/</link><description>Effort is non-linear and multi-dimensional. Time (with relation to delivery) is linear and one-dimensional</description><author>smcleod.net</author><pubDate>Fri, 28 Apr 2023 09:41:37 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/04/escaping-the-time-trap-why-estimating-effort-not-time-leads-to-greater-success/</guid></item><item><title>Chirping, chirping, chirping</title><link>https://blog.nawaz.org/posts/2023/Apr/chirping-chirping-chirping/</link><description>&lt;p&gt;For the last few days I&amp;#8217;ve been playing with &lt;a class="reference external" href="https://chirper.ai"&gt;&lt;span class="caps"&gt;AI&lt;/span&gt;
Chirpers&lt;/a&gt; - a Twitter-like social network where
only &lt;span class="caps"&gt;AI&lt;/span&gt; bots are allowed and they chat with one&amp;nbsp;another.&lt;/p&gt;
&lt;p&gt;What&amp;nbsp;fun!&lt;/p&gt;
&lt;p&gt;How it works: You register an account, and create up to 5 AIs. All you
need to do is provide …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Wed, 26 Apr 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2023/Apr/chirping-chirping-chirping/</guid></item><item><title>On tools</title><link>https://blog.bayindirh.io/blog/on-tools/</link><description>&lt;p&gt;Learning and being informed about new technologies is a habit of mine. I regularly visit webpages and follow RSS feeds to see what is happening and what are the new tools being developed.&lt;/p&gt;
&lt;p&gt;While I learn and be aware about these new tools, I rarely switch what I use with shinier and newer incarnations of the ones with the same role or functionality. Moreover, I defend using the same tools for a long time despite their shortcomings. More importantly, I defend using stable workflows (or stacks if you prefer the term) for a long time, despite the disagreements with my peers.&lt;/p&gt;
&lt;h2 id="solving-problems-once"&gt;Solving problems once&lt;/h2&gt;
&lt;p&gt;The biggest reason I'm using same tools and workflows for a long time is solving every problem once. When starting to build a workflow, selection of tools and making them work together takes a great deal of time, which is gone unnoticed due to excitement of starting a new project.&lt;/p&gt;
&lt;p&gt;After setting the workflow for the first time, there will be inevitable problems and paper cuts which hamper both the development and mental flow. Interruption of this flow makes the developer lose a lot of time, since human brain works best when it's warmed up and focused on a deep subject for a while.&lt;/p&gt;
&lt;p&gt;When these problems are solved, and documented if possible, the workflow starts to settle down. This settling is similar to annealing. It's a slow and delicate process, but when it's complete; the result is robust, high quality and pretty indestructible under normal usage conditions.&lt;/p&gt;
&lt;p&gt;After this workflow and tools around it is settled, it can be lifted and applied to new projects with ease. The time required is minuscule when compared to doing everything all over again, every time, and this saved time allows for nifty things, namely iteration and mastery.&lt;/p&gt;
&lt;h2 id="reproducibility"&gt;Reproducibility&lt;/h2&gt;
&lt;p&gt;Another benefit of having a well defined and well polished workflow is reproducibility. This affects not only the developer, but potential contributors of the project. When one has a reproducible workflow built around well-defined tools, it can be reproduced with much less effort.&lt;/p&gt;
&lt;p&gt;This is crucial for making collaboration on a project easy and effortless. When somebody new to a project can setup the workflow in an hour and start hacking away, the contributor is more likely to be happy and motivated. This initial momentum will greatly improve both developer and user ergonomics, because if a user who knows how to program starts to use the tool, and setup its development environment in a coffee break, they can both use and improve the tool at the same time. As a result, the chance of getting patches from the same user will go up.&lt;/p&gt;
&lt;h2 id="mastery"&gt;Mastery&lt;/h2&gt;
&lt;p&gt;As I noted in a &lt;a href="https://blog.bayindirh.io/blog/tools-craftsmanship-and-mastery/"&gt;previous post&lt;/a&gt;, I love the concept of craftsmanship and mastery. The results created by this dedication and discipline is always high quality, even exceptional.&lt;/p&gt;
&lt;p&gt;I personally love to create tools for myself, and want these tools be in the same league with the more established tools, regardless of the category. Writing high quality code and robust tools which are production ready and complete in every sense is a great source of pride and satisfaction for me. &lt;/p&gt;
&lt;p&gt;On the other hand, trying to reach that level of quality is a great learning experience. Because, finishing the second 90% of the work needs discipline, dedication and elaborate work.&lt;/p&gt;
&lt;p&gt;Reaching that level of finesse is possible when one knows the tools at hand down to its smallest details and edge cases. Similarly, to be able to focus on the product to maximize its quality, one needs to trust the tools to the point they become invisible or extension of oneself. This again requires using the tool for an extensive time and being familiar with it. As a result, a stable and well worn workflow with established tools is essential in this process.&lt;/p&gt;
&lt;h2 id="licenses"&gt;Licenses&lt;/h2&gt;
&lt;p&gt;While tangential, the licenses of the tools used in the workflow has a great impact on the workflow itself, because it affects the portability of the workflow and the possibilities to share it greatly.&lt;/p&gt;
&lt;p&gt;I personally prefer free software over open source, because when combined with a free software project and proper documentation, all moats are removed between the project and possible collaborators.&lt;/p&gt;
&lt;p&gt;Using workflows consisting free software tools as much as possible guarantees that the workflow will be stable in the long term, because the probability of tools going proprietary or being developed as closed source forks is way lower.&lt;/p&gt;
&lt;p&gt;Lastly, the four freedoms about software is essential and non-negotiable from my perspective. As a result, everything I put out and the tooling required to build and modify is free software, and I have no plans or intentions to change that to more permissive models.&lt;/p&gt;
&lt;p&gt;Building workflows is fun and exciting, but building them over and over limits the time which can be used for creating even more exciting things.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Tue, 25 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/on-tools/</guid></item><item><title>Vim tip 25: substitute flags</title><link>https://learnbyexample.github.io/tips/vim-tip-25/</link><description>&lt;p&gt;Here are some of the flags you can use with the substitute command:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;g&lt;/code&gt; replace all occurrences within a matching line
&lt;ul&gt;
&lt;li&gt;by default, only the first matching portion will be replaced&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;c&lt;/code&gt; ask for confirmation before each replacement&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i&lt;/code&gt; ignore case for &lt;em&gt;searchpattern&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;I&lt;/code&gt; don't ignore case for &lt;em&gt;searchpattern&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These flags are applicable for the substitute command but not &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;?&lt;/code&gt; searches. Flags can also be combined, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;s/cat/Dog/gi&lt;/code&gt; replace every occurrence of &lt;code&gt;cat&lt;/code&gt; with &lt;code&gt;Dog&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Case is ignored, so &lt;code&gt;Cat&lt;/code&gt;, &lt;code&gt;cAt&lt;/code&gt;, &lt;code&gt;CAT&lt;/code&gt;, etc are all valid matches&lt;/li&gt;
&lt;li&gt;Note that &lt;code&gt;i&lt;/code&gt; doesn't affect the case of the replacement string&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://vimhelp.org/change.txt.html#%3As_flags"&gt;:h s_flags&lt;/a&gt; for a complete list of flags and more details about them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 25 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-25/</guid></item><item><title>Suzume no Tojimari</title><link>https://burakku.com/blog/suzume-no-tojimari/</link><description>&lt;p&gt;&lt;img alt="Suzume no Tojimari" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Just got out of the movie theatre after watching &lt;em&gt;Suzume no Tojimari&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I've never really self-identified as a Shinkai Makoto fan, but if I keep going to the movie theatre to see his movies and love all of them, I guess I just might be one. Definitely feels like that keeps coming true ever since I watched &lt;em&gt;Kimi no Na wa&lt;/em&gt; in a theatre back in 2017.&lt;/p&gt;
&lt;p&gt;(I have seen Shinkai movies before &lt;em&gt;Kimi no Na wa&lt;/em&gt; but not in theatres.)&lt;/p&gt;
&lt;p&gt;If you've seen the two Shinkai movies before &lt;em&gt;Suzume no Tojimari&lt;/em&gt;, you probably know what to expect: beautiful scenery, fluid animation, tons of details, the supernatural, comedy, drama, love and RADWIMPS. Stick to what you know, right?&lt;/p&gt;
&lt;p&gt;The story was captivating as ever. Shinkai also writes great characters that you'll want to root for. That goes for the main characters and all of the side characters. Suzume was of course as adorable as Hina and Mitsuha before her. Tears were definitely also shed during. You can definitely see how the 2011 earthquakes shaped this story, and the suffering brought by it. You even see a diary entry for March 11th during a scene.&lt;/p&gt;
&lt;p&gt;I think Suzume might be the first time Shinkai has crossed the two-hour threshold in runtime. It didn't really feel that long, and the narrative was quite packed. Wouldn't have minded at all if it was even longer, I wasn't checking my watch.&lt;/p&gt;
&lt;p&gt;I feel like this time around though, they definitely toned down the RADWIMPS. No insert songs this time, which feels like a bit of a shame since &lt;a href="https://www.youtube.com/watch?v=EQ94zflNqn4"&gt;愛にできることはまだあるかい&lt;/a&gt; playing during &lt;em&gt;Tenki no Ko&lt;/em&gt; still gives me shivers. Not that the audio side of Suzume was anything to scoff at. The soundtrack fits the storytelling like a glove and the sound effects kept me immersed. I'm actually pretty glad I saw it in a proper theatre, you just can't get those rich bass sounds in a home setup.&lt;/p&gt;
&lt;p&gt;If I had to rank Shinkai's latest three movies, I'm not quite sure what I'd say. When I saw &lt;em&gt;Tenki no Ko&lt;/em&gt;, I felt like I enjoyed it more than &lt;em&gt;Kimi no Na wa&lt;/em&gt;, even though the popular opinion seemed to be the opposite. I in fact enjoyed it so much that I went to see it in the movies twice. Even dragged someone to go watch it with me. But I don't know how to rank &lt;em&gt;Tenki no Ko&lt;/em&gt; and &lt;em&gt;Suzume&lt;/em&gt; relative to each other. I feel like I love them both, but in different ways. Not sure if I'd go rewatch it in a threatre though – maybe if I got to drag someone with me again.&lt;/p&gt;
&lt;p&gt;I don't think Shinkai has pulled a Miyazaki (announced some kind of a retirement that may or may not last) yet, so I guess I'll wait for the next beautiful supernatural adventure from him.&lt;/p&gt;</description><author>ブラック</author><pubDate>Mon, 24 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/suzume-no-tojimari/</guid></item><item><title>Thoughts on AI and AI-veganism</title><link>https://www.marginalia.nu/log/78-on-ai-veganism/</link><description>I&amp;rsquo;ve come to think LLMs/GPTs/whatever are a threat to conventional search engines because the modern web is an unbelievably annoying dumpster fire.
They don&amp;rsquo;t really provide better or faster answers, what they provide is an experience that is not a complete pain in the ass.
This frog has been simmering for a long while now and we&amp;rsquo;re so used to it that seeing literally anything else seems revolutionary.
You visit a website and need to dismiss a cookie policy notification, a request to show popups, a request to know your location, an invitation to subscribe to a newsletter, a sales rep wants to have a chat with you, then you get random layout shifts for several minutes as all the ad auctions finish, and then just as you&amp;rsquo;re ready to read the content the website crashes and reloads and the circus starts over again.</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 21 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/78-on-ai-veganism/</guid></item><item><title>CLI tip 26: removing duplicate lines with GNU awk</title><link>https://learnbyexample.github.io/tips/cli-tip-26/</link><description>&lt;p&gt;&lt;code&gt;awk '!a[$0]++'&lt;/code&gt; is one of the most famous Awk one-liners. It eliminates line based duplicates while retaining input order. The following example shows it in action along with an illustration of how the logic works.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat purchases.txt
&lt;/span&gt;&lt;span&gt;coffee
&lt;/span&gt;&lt;span&gt;tea
&lt;/span&gt;&lt;span&gt;washing powder
&lt;/span&gt;&lt;span&gt;coffee
&lt;/span&gt;&lt;span&gt;toothpaste
&lt;/span&gt;&lt;span&gt;tea
&lt;/span&gt;&lt;span&gt;soap
&lt;/span&gt;&lt;span&gt;tea
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{print +a[$0] &amp;quot;\t&amp;quot; $0; a[$0]++}'&lt;/span&gt;&lt;span&gt; purchases.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;       coffee
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;       tea
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;       washing powder
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;       coffee
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;       toothpaste
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;       tea
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;       soap
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;       tea
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# only those entries with zero in the first column will be retained
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'!a[$0]++'&lt;/span&gt;&lt;span&gt; purchases.txt
&lt;/span&gt;&lt;span&gt;coffee
&lt;/span&gt;&lt;span&gt;tea
&lt;/span&gt;&lt;span&gt;washing powder
&lt;/span&gt;&lt;span&gt;toothpaste
&lt;/span&gt;&lt;span&gt;soap
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Removing field based duplicates is simple for single field comparison. Just change &lt;code&gt;$0&lt;/code&gt; to the required field number after setting the appropriate field separator.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat duplicates.txt
&lt;/span&gt;&lt;span&gt;brown,toy,bread,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span&gt;dark red,ruby,rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;111
&lt;/span&gt;&lt;span&gt;blue,ruby,water,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;span&gt;dark red,sky,rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;555
&lt;/span&gt;&lt;span&gt;yellow,toy,flower,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;span&gt;white,sky,bread,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;111
&lt;/span&gt;&lt;span&gt;light red,purse,rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# based on the last field
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;F&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'!seen[$NF]++'&lt;/span&gt;&lt;span&gt; duplicates.txt
&lt;/span&gt;&lt;span&gt;brown,toy,bread,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span&gt;dark red,ruby,rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;111
&lt;/span&gt;&lt;span&gt;blue,ruby,water,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;span&gt;dark red,sky,rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;555
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For multiple fields comparison, separate the fields with &lt;code&gt;,&lt;/code&gt; so that &lt;code&gt;SUBSEP&lt;/code&gt; is used to combine the field values to generate the key. &lt;code&gt;SUBSEP&lt;/code&gt; has a default value of &lt;code&gt;\034&lt;/code&gt; which is a non-printing character and not usually used in text files.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# based on the first and third fields
&lt;/span&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;F&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'!seen[$1,$3]++'&lt;/span&gt;&lt;span&gt; duplicates.txt
&lt;/span&gt;&lt;span&gt;brown,toy,bread,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;42
&lt;/span&gt;&lt;span&gt;dark red,ruby,rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;111
&lt;/span&gt;&lt;span&gt;blue,ruby,water,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;span&gt;yellow,toy,flower,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;span&gt;white,sky,bread,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;111
&lt;/span&gt;&lt;span&gt;light red,purse,rose,&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;333
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; &lt;a href="https://github.com/koraa/huniq"&gt;huniq&lt;/a&gt; is a faster alternative for removing line based duplicates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;CLI text processing with GNU awk&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 20 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-26/</guid></item><item><title>Tools I use: Evernote</title><link>https://blog.bayindirh.io/blog/tools-i-use-evernote/</link><description>&lt;p&gt;In recent years, a lot of note taking applications (or personal knowledge base tools if you prefer the term) have entered the space. Prominent examples are &lt;a href="https://roamresearch.com"&gt;Roam Research&lt;/a&gt;, &lt;a href="https://obsidian.md"&gt;Obsidian&lt;/a&gt;, &lt;a href="https://www.notion.so"&gt;Notion&lt;/a&gt; and countless alternatives and reimplementations of those. On the other hand, there are older ones, and there's one literal elephant in the room, and it's called &lt;a href="https://www.evernote.com"&gt;Evernote&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Evernote is one of the oldest applications in this space. Started as a suite of desktop and mobile applications with a web-based client for accessing your notes, it supports rich formatting and predates popularity of Markdown. As a result, what it provides is more of an old fashioned, WYSIWYG editor, with tables, file attachments, and other quality of life improvements.&lt;/p&gt;
&lt;p&gt;Evernote was not in its current form when it started. It took more of an "everything and the kitchen sink" type of approach, more targeted to road warriors of 2000s. The resulting desktop application was something between an office suite and note taking tool. This situation created a big problem: There was no feature parity between platforms and this hurt Evernote big time.&lt;/p&gt;
&lt;p&gt;At this stage, while I was ready to abandon Evernote and cancel my subscription, something happened: Company's CEO has changed, and they announced a new direction. I decided to wait longer and give them another chance.&lt;/p&gt;
&lt;p&gt;In hindsight, waiting was the correct thing to do, because new Evernote is much better than the first iteration. Yes, it has an Electron based desktop application (and I don't like Electron), yet it's not sluggish and taxing your system in unreasonable amounts.&lt;/p&gt;
&lt;p&gt;The new Evernote lacks many of the features of the first iteration, and many of its competitors, however this simplicity and well-roundedness is what makes Evernote enticing. As a result, I'm managing all my knowledge base, collaboration and public tech notes with Evernote.&lt;/p&gt;
&lt;p&gt;What it makes Evernote great is the combination of simplicity and feature parity between platforms it runs on, including Web, which allows me to work on any platform. Combined with the new synchronization engine they developed over the years, everything is pretty seamless. I have never lost any data with them, and that's pretty important for me.&lt;/p&gt;
&lt;p&gt;The simplicity they provide is &lt;em&gt;reduced&lt;/em&gt; set of fonts and headers, a selection of color choices to emphasize things, and text alignment options. The resulting feature set can be called "Markdown+". While a superset of pure Markdown, it can degrade to it gracefully, and they can be converted to each other with relative ease.&lt;/p&gt;
&lt;p&gt;Given my love for simplicity and practicality, Evernote's reduced set of options allows me to think less and create more. On top of that, this reduced set of formatting options allows me to create consistent documents with less effort, which is a win in my book.&lt;/p&gt;
&lt;p&gt;Another thing I like with Evernote is one-click sharing of notes, which I use for writing technical documents and sharing them (which you can see &lt;a href="https://bayindirh.lists.sh/Useful%20technical%20documents"&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Like other tools, Evernote supports collaboration over shared notebooks, and the process is pretty smooth since the new Evernote has started its life. I have used it throughout my Ph.D. with my professor for seven years, and it worked. This is enough of a testament for the experience.&lt;/p&gt;
&lt;p&gt;Finding what you have written in the past has gotten much easier with Evernote in recent years. Its search engine is revamped and improved greatly. While not all features are available to all tiers, the highest tier membership (which is nowhere expensive here since Evernote does local pricing) allows location based searches. Consider you went somewhere and took notes there. You can search the notes you have taken at that location, which is much more effective than it sounds. For example, I used frequent a Starbucks branch where I met with my professor for my studies. I can list the notes I have taken there and filter further down to find what I was looking for, much faster. That's both impressive and comforting, because that elephant not only can remember what you have told, but can recall these things with accuracy and precision.&lt;/p&gt;
&lt;p&gt;Another feature added to Evernote in its recent iterations is task management and calendar integration. It's a set of powerful features centered around tasks and task delegations (even to people outside Evernote), but since I manage my daily tasks elsewhere, I don't use these features much, hence I can't comment on them. &lt;/p&gt;
&lt;p&gt;Lastly, while Evernote is a closed source ecosystem, it has great exporting capabilities. It has a file format called &lt;code&gt;.enex&lt;/code&gt;, which is an XML based export file. XML is disliked in tech circles (which is a story for another time), but Evernote both documents the format openly, and provides its schema for interoperability. You can read more about it &lt;a href="https://evernote.com/blog/how-evernotes-xml-export-format-works/"&gt;here&lt;/a&gt;, and see the schema &lt;a href="http://xml.evernote.com/pub/evernote-export3.dtd"&gt;here&lt;/a&gt;. The files you attach to a particular note are even encoded BASE64 and embedded inside the XML, hence you can get your notes as-is and with everything you added.&lt;/p&gt;
&lt;p&gt;As a result, the old elephant Evernote is useful, even today. It allows me to create nice looking documents, share them with the world or people I want to collaborate and allows me to get my notes as-is for plethora of purposes via an open and well documented file format. Paired with clients on many platforms with the same features, the result is a winning formula and happy users.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Tue, 18 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/tools-i-use-evernote/</guid></item><item><title>2023–04–16: Pinephone DRM driver issues</title><link>https://xnux.eu/log/#080</link><author>megi's PinePhone Development Log</author><pubDate>Sun, 16 Apr 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#080</guid></item><item><title>Digital clutter</title><link>https://paulstamatiou.com/digital-clutter</link><description>&lt;p&gt;I started taking photography more seriously about a decade ago. I shot photos in RAW with increasingly higher megapixel cameras, spent a lot of time post-processing photos—see my 30K word post about &lt;a href="https://paulstamatiou.com/building-a-windows-10-lightroom-photo-editing-pc/"&gt;building a custom Adobe Lightroom PC&lt;/a&gt;—and designed and built custom photo stories to share them.&lt;/p&gt;
&lt;p&gt;While I would go through the process of culling photos that I published online, I didn’t do that for what I backed up. I kept &lt;em&gt;everything&lt;/em&gt;. I always thought, "what if I want to go back to this and edit it differently one day?"&lt;/p&gt;
&lt;p&gt;I kept every RAW photo I shot, every video, every GoPro clip... I even kept every phone photo going back to my first iPhone. And why not, storage is cheap and easy, right?&lt;/p&gt;
&lt;p&gt;The terabytes kept adding up. I added close to a terabyte with &lt;a href="https://photos.paulstamatiou.com/new-zealand/"&gt;New Zealand&lt;/a&gt;. Hundreds of gigs with &lt;a href="https://photos.paulstamatiou.com/africa/"&gt;Africa&lt;/a&gt;. When you hit a few terabytes the obvious solution is to move to a NAS, or Network Attached Storage. I wrote about this extensively in 2015 with &lt;a href="https://paulstamatiou.com/storage-for-photographers-part-2/"&gt;Storage for Photographers (Part 2)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Years back these were pretty dumb devices that would just appear as a networked drive for your computer to manage. Today, they’re full-blown personal servers, capable of running self-hosted apps and services. That makes it easy to have them encrypt your data, and automatically back your files up to cloud services, which is probably what you want if you’re paranoid about backups and try to follow the 3-2-1 rule.&lt;/p&gt;
&lt;img alt="NAS setup" src="https://turbo.paulstamatiou.com/uploads/2017/08/copyright-paulstamatiou_com-DSC05186-2000.jpg" /&gt;
&lt;small&gt;My NAS setup some years back.. it became far messier over the years after I moved to NYC and had less space for networking gear.&lt;/small&gt;
&lt;p&gt;Over the years I paid thousands of dollars just to keep those RAW photos safe. At one point I think I had copies on 3 or 4 separate cloud services.. you know, just in case. I later upgraded to another NAS that was physically a bit smaller.&lt;/p&gt;
&lt;p&gt;In hindsight, I couldn&amp;#x27;t care less about 90% of all that data, if not more. The fact that I had terabytes of available storage made it all too easy to not be picky with what I was backing up and just archive everything.&lt;/p&gt;
&lt;p&gt;When it was time to move to a different apartment, I took that as the perfect excuse to simplify. I no longer wanted a NAS in my life. A little whirring box, always making random hard drive noises, always taking up space, always making heat. Though I’m sure my perspective would be a bit different if I had enough space to hide the NAS in a perfect server closet completely out of sight and mind.&lt;/p&gt;
&lt;p&gt;I needed to get rid of tons of RAW photos for which I no longer cared. I knew I wouldn’t be able to get rid of everything and I still wanted maybe 2 TB of local storage, separate from my computer. My first inclination was to just look for a tiny NAS that I could load up with 2.5” SATA SSDs, or something even better like M.2 NVMe SSDs. I didn’t find anything I liked—most small NAS devices would still require some massive barrel-jack power adapter.&lt;/p&gt;
&lt;h3&gt;Picking an M.2 SSD enclosure&lt;/h3&gt;
&lt;p&gt;I ended up deciding to get a small, simple external Thunderbolt SSD enclosure. In particular, I wanted an M.2 NVMe SSD enclosure. M.2 SSDs are much smaller and faster than other modern SSDs using SATA. I wanted an small, bus-powered enclosure that was Thunderbolt 3 or 4, and had some basic heatsink or thermal pad for the SSD.&lt;/p&gt;
&lt;p&gt;There were a few options I looked at. The &lt;a href="https://www.amazon.com/dp/B07ZWPH7G3?_encoding=UTF8&amp;amp;psc=1&amp;amp;linkCode=ll1&amp;amp;tag=paulstamatiou-20&amp;amp;linkId=ec9bd7e1c319cc02b39d8045c1d94095&amp;amp;language=en_US&amp;amp;ref_=as_li_ss_tl"&gt;Sabrent Rocket Nano&lt;/a&gt; looked perfect at first glance due to its tiny size but they don&amp;#x27;t sell it as an enclosure, and it&amp;#x27;s only USB-C, not Thunderbolt, so I ruled that out. Then I looked at a &lt;a href="https://www.amazon.com/Thunderbolt-Certified-Tool-Free-Enclosure-EC-T3NS/dp/B08FT59SB6?ie=UTF8&amp;amp;linkCode=ll1&amp;amp;tag=paulstamatiou-20&amp;amp;linkId=e9fc4a2515dc46de1493ee73d89912ad&amp;amp;language=en_US&amp;amp;ref_=as_li_ss_tl"&gt;Thunderbolt 3 enclosure from Sabrent&lt;/a&gt;. Unfortunately, some reviews pointed out issues with performance and reliability that had me concerned. There was also a new &lt;a href="https://satechi.net/products/usb4-nvme-ssd-pro-enclosure?variant=40188470067288"&gt;Thunderbolt 4 enclosure from Satechi&lt;/a&gt;, but it wasn&amp;#x27;t shipping (and still seems to be out of stock). That aside, it looked like a great option, but it was far too large for some reason.&lt;/p&gt;
&lt;p&gt;After a lot of looking I eventually landed on the &lt;a href="https://www.amazon.com/OWC-Envoy-Express-Thunderbolt-Enclosure/dp/B08LYQPRPJ?&amp;amp;linkCode=ll1&amp;amp;tag=paulstamatiou-20&amp;amp;linkId=6eab0ee6644082eab4ef5f7e6ba7ea89&amp;amp;language=en_US&amp;amp;ref_=as_li_ss_tl"&gt;OWC Envoy Express&lt;/a&gt;. I was hoping for a more aesthetically pleasing option but OWC has been a reputable brand I&amp;#x27;ve been familiar with so it felt like the safe option. I don&amp;#x27;t like how the Thunderbolt cable is part of the case design (you can detach it but you have to open the case), but it wasn&amp;#x27;t a dealbreaker for me. At the time I purchased this, it was a bit cheaper at $79.&lt;/p&gt;
&lt;img alt="OWC Envoy Express Thunderbolt 3 SSD enclosure" src="https://turbo.paulstamatiou.com/uploads/2023/04/copyright-paulstamatiou_com-owc-envoy-express-ssd.jpg" /&gt;
&lt;small&gt;The Envoy Express enclosure alongside a cheaper USB-C enclosure I got for another spare SSD I had.&lt;/small&gt;
&lt;p&gt;The Envoy Express enclosure is all metal and seems very well-made so I&amp;#x27;m quite pleased with it.&lt;/p&gt;
&lt;p&gt;As for what SSD to use with the enclosure, I decided to use one I already had: an older 2 TB Samsung 970 Evo. At the time I was also parting out and selling &lt;a href="https://paulstamatiou.com/gear/desktop-pc/"&gt;my gaming PC&lt;/a&gt; (didn&amp;#x27;t use it enough after I got an Xbox Series X and it was taking up space) and I decided to repurpose the 2 NVMe SSDs I took from it. If you want to do the same, you can find affordable 1 and 2 TB options from Western Digital, Samsung, and SK Hynix among others. It&amp;#x27;s when you start looking for 4TB SSDs that the prices go up considerably.&lt;/p&gt;
&lt;img alt="My old desktop PC" src="https://turbo.paulstamatiou.com/uploads/2019/05/copyright-paulstamatiou_com-DSC2824-2000.jpg" /&gt;
&lt;small&gt;My old desktop PC where I salvaged my M.2 SSDs from&lt;/small&gt;
&lt;p&gt;Installing the SSD in the OWC enclosure took less than a minute. There&amp;#x27;s two screws to open the case, then one more screw to fasten the SSD. The case has a built-in thermal pad that touches the top of the SSD, though I wish it had some sort of cooling for the bottom as well.&lt;/p&gt;
&lt;img alt="Samsung 970 EVO SSD inside OWC Envoy Express Thunderbolt 3 enclosure" src="https://turbo.paulstamatiou.com/uploads/2023/04/copyright-paulstamatiou_com-owc-envoy-express-ssd-2.jpg" /&gt;
&lt;small&gt;Breathing new life into my old 970 EVO M.2 SSD by putting it in the OWC Envoy Express enclosure.&lt;/small&gt;
&lt;h3&gt;Setup and disk encryption&lt;/h3&gt;
&lt;p&gt;The next thing I did was open up Disk Utility on macOS, format the SSD, then set up encryption. While there&amp;#x27;s lots of ways to encrypt your data—like VeraCrypt which touts plausible deniability with hidden volumes—I opted for the &lt;a href="https://support.apple.com/guide/disk-utility/encrypt-protect-a-storage-device-password-dskutl35612/mac"&gt;encryption functionality built into macOS&lt;/a&gt; for simplicity.&lt;/p&gt;
&lt;p&gt;With the SSD ready to go, the hard part was determining what photos and data I wanted to from my NAS I wanted to keep and transfer to this SSD. This was made particularly hard as my NAS was very slow. I had gigabit ethernet but that doesn’t matter, these spinning disks are barely faster than 100MB/s for sequential read even with a RAID setup. So my long process over a week or so was transferring a folder or two to my laptop overnight, as much as I could with the available space I had on my MacBook Pro&amp;#x27;s internal SSD.&lt;/p&gt;
&lt;p&gt;Once on my laptop I had to go through and decide what to delete and what to keep. There were a lot of easy ways I was able to save space by cutting things like GoPro videos of which I had hundreds of gigs from trips. I didn&amp;#x27;t care for that footage much and the important clips I had already used in other videos elsewhere.&lt;/p&gt;
&lt;p&gt;But there&amp;#x27;s no getting around it, this was an arduous process. For some older trips I only kept the post-processed original size JPGs and deleted the RAWs. Then there were tons of just random photos I deleted. It took me a few days but eventually I was able to shave a few terabytes; enough to fit the important stuff on this 2 TB SSD. (I had a bit of overflow which I tossed on the other USB-C external enclosure I had which I won&amp;#x27;t use often so the speed isn&amp;#x27;t important).&lt;/p&gt;
&lt;h3&gt;Cloud encryption and backup&lt;/h3&gt;
&lt;p&gt;With the photos and data trimmed down and ready to transfer to the SSD, I needed to figure out how I wanted to have this SSD backed up and encrypted on cloud services. For that I turned to &lt;a href="https://www.arqbackup.com/"&gt;Arq&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Arq is a backup power tool for advanced users. It&amp;#x27;s a standalone Mac app that connects to just about any cloud service including AWS, Backblaze, OneDrive, Wasabi, Google Cloud, as well as all S3-compatible storage providers. The best part is that you bring your own credentials for those cloud services, and you have fine-grained control over how things are backed up, stored, encrypted, and much more.&lt;/p&gt;
&lt;img alt="Arq backup app for macOS" src="https://turbo.paulstamatiou.com/uploads/2023/04/copyright-paulstamatiou_com-arq-macos-ssd-backup.jpg" /&gt;
&lt;p&gt;I decided to separate my photos and other more important data into different folders with different backup strategies. Photos took up most of the 2 TB and I wanted them backed up but I didn&amp;#x27;t expect to access them frequently so I didn&amp;#x27;t want to use the Amazon "S3 Standard" storage class. I decided to use S3 Glacier Instant Retrieval for my photos.&lt;/p&gt;
&lt;p&gt;I&amp;#x27;m no stranger to Glacier, Amazon&amp;#x27;s slower and cheaper data archival storage tier, and have been using some form of it for a decade, but I never had a great experience. It always felt unpredictable and slow; yes, I realize that&amp;#x27;s the basically the trade-off you sign up for. As of 2021, Amazon added a new type of Glacier storage called Instant Retrieval. So you get the benefit of more affordable data archival storage along with quicker retrieval times.&lt;/p&gt;
&lt;p&gt;There&amp;#x27;s quite a few S3 storage classes to select from: Standard, Standard Infrequent Access, One Zone Infrequent Access, Glacier Instant Retrieval, Glacier Flexible Retrieval, Glacier Deep Archive. It&amp;#x27;s important to familiarize yourself with the &lt;a href="https://aws.amazon.com/s3/storage-classes/"&gt;functionality of the S3 storage class&lt;/a&gt; you plan on using. And that&amp;#x27;s just with AWS, you can to use any other cloud provider you like with Arq.&lt;/p&gt;
&lt;p&gt;To give you a rough idea of the prices associated with some of these S3 storage classes, &lt;strong&gt;here&amp;#x27;s what it would cost to store 2 TB per month&lt;/strong&gt;, with no other actions or transfers:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;S3 Storage Class&lt;/th&gt;&lt;th&gt;Monthly price to store 2 TB&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;S3 Standard&lt;/td&gt;&lt;td&gt;$47.10 USD&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;S3 Standard Infrequent Access&lt;/td&gt;&lt;td&gt;$25.60 USD&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;S3 One Zone Infrequent Access&lt;/td&gt;&lt;td&gt;$20.48 USD&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;S3 Glacier Instant Retrieval&lt;/td&gt;&lt;td&gt;$8.19 USD&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;S3 Glacier Flexible Retrieval&lt;/td&gt;&lt;td&gt;$7.41 USD&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;S3 Glacier Deep Archive&lt;/td&gt;&lt;td&gt;$2.05 USD&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;And then, for my other data, which was more important and not nearly as sizable, I opted for S3 Standard Infrequent Access. Here&amp;#x27;s how that looks in Arq:&lt;/p&gt;
&lt;img alt="Arq showing different S3 storage classes for different folders" src="https://turbo.paulstamatiou.com/uploads/2023/04/copyright-paulstamatiou_com-arq-macos-ssd-backup-settings-3.jpg" /&gt;
&lt;small&gt;Arq showing different S3 storage classes for different folders&lt;/small&gt;
&lt;p&gt;Arq also lets you specify encryption for your data—separate from the macOS encryption I set up that&amp;#x27;s required to mount the drive—so I did that as well. It&amp;#x27;s important to note that you will need the Arq client to be able to access the encrypted files again; they&amp;#x27;re unreadable if you go directly to the storage bucket on S3.&lt;/p&gt;
&lt;p&gt;That&amp;#x27;s it! I&amp;#x27;m very happy with the new setup. I&amp;#x27;m relieved to no longer have a NAS taking up space and making noise in my house. The external SSD is tiny and I still have control over where my data is backed up online. I can easily add more cloud storage destinations later on as I see fit.&lt;/p&gt;
&lt;h3&gt;Cloud control&lt;/h3&gt;
&lt;p&gt;However, after all this work slimming down my archives, the job is not done. My NAS was just the first target. I still have a stockpile of data and memories across other services like Google. Take Google Photos for example. Absolutely amazing service. I love the heck out of the product. And while Apple&amp;#x27;s Photos functionality is great, I&amp;#x27;ve always loved all that Google Photos had to offer from amazing search to strong sharing functionality.&lt;/p&gt;
&lt;p&gt;I just wish Google Photos wasn&amp;#x27;t owned by Google. I&amp;#x27;m trying to reduce my reliance on consumer-oriented Big Tech products like this in favor of services where I have more control. Google has been heralded for their data portability tool Google Takeout, which includes Google Photos data, but in my experience using it was all but pleasant.&lt;/p&gt;
&lt;p&gt;First, I had to wait days to let the archives prepare, which I understand what needs to happen with this much data so that&amp;#x27;s fine. But then, I had to download more than a dozen separate zipped files amounting to over 800 GB. I don&amp;#x27;t even think I had that much space on my laptop, let alone extra space to be able to unzip them all. And once unzipped, I don&amp;#x27;t recall the files having any sort of helpful organization to make it feasible to trim down moments you don&amp;#x27;t need. I gave up on that.&lt;/p&gt;
&lt;p&gt;Cloud storage services make it all too easy to eagerly backup anything and everything without being intentional. Now we&amp;#x27;ve all inadvertently become data hoarders.&lt;/p&gt;
&lt;p&gt;Just because we have easy access to near-infinite and increasingly more affordable storage doesn&amp;#x27;t mean we &lt;em&gt;should&lt;/em&gt; be using all that space. The next generation of cloud-based services need to design for curation and control. It doesn&amp;#x27;t feel like an immediate concern, but it will only continue to increase.&lt;/p&gt;
&lt;p&gt;These days getting your Google account hacked isn&amp;#x27;t just about your email. It may be about your entire digital life. This is more than just decluttering, it&amp;#x27;s about control, &lt;a href="https://paulstamatiou.com/getting-started-with-security-keys/"&gt;security&lt;/a&gt; and privacy too.&lt;/p&gt;</description><author>Paul Stamatiou</author><pubDate>Sat, 15 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://paulstamatiou.com/digital-clutter</guid></item><item><title>Can ChatGPT write the C++20 async message queue codes from the previous posts?</title><link>https://bytepawn.com/can-chatgpt-write-the-cpp-async-message-queue-codes-from-the-previous-posts.html</link><description>&lt;p&gt;I try to get ChatGPT to write the codes in the previous posts. It's able to write the basic message queue skeleton, but it cannot implement more complicated features such as delivery semantics with caching; also, the code crashes. C++ programmers do not (yet) need to fear for their jobs.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/cpp-hands.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 15 Apr 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/can-chatgpt-write-the-cpp-async-message-queue-codes-from-the-previous-posts.html</guid></item><item><title>2023–04–15: Pinephone Backlight Testing</title><link>https://xnux.eu/log/#079</link><author>megi's PinePhone Development Log</author><pubDate>Sat, 15 Apr 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#079</guid></item><item><title>Ranking #1 on HN in Mid April</title><link>https://www.swyx.io/ranking-#1-on-hn-in-mid-april</link><description>&lt;p&gt;I last wrote about &lt;a href="https://www.swyx.io/lspace-2022"&gt;Ranking #1 on HN in December&lt;/a&gt;, and wanted to offer an update from my mild hit today. I am now taking &lt;a href="https://latent.space/"&gt;Latent Space&lt;/a&gt; (the new name enabled by the previous owner of that domain selling it to me in &lt;a href="https://www.swyx.io/substack-change-domain"&gt;my first P2P domain purchase&lt;/a&gt;) a lot more seriously with the support of my partner and friend Alessio.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Fri, 14 Apr 2023 08:17:23 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/ranking-#1-on-hn-in-mid-april</guid></item><item><title>Blog</title><link>https://burakku.com/blog/hello-world/</link><description>&lt;p&gt;Everything old is new again.&lt;/p&gt;
&lt;p&gt;Seeing as Twitter has been (&lt;a href="https://twitter.com/Mailia/status/1514584742208970759"&gt;unsurprisingly&lt;/a&gt;) in a constant backslide since last autumn, I've decided that I need to start diversifying where I output all of my thoughts and opinions. And considering what a craptastic thing Mastodon is, I'd rather not partake in it either.&lt;/p&gt;
&lt;p&gt;Therefore, I have decided that I should have a web log. Just like in the good old days.&lt;/p&gt;
&lt;p&gt;I have probably attempted to have a blog in the past too, without much success. I was much more of a fan of microblogging, which is probably why I've been on Twitter for the past 15 years. But since there's a massive vacuum in the microblogging space, I've decided to pivot into (macro?)blogging.&lt;/p&gt;
&lt;p&gt;Quite honestly, I do not have a long-term vision for this blog beyond having a space to store whatever I feel like I need to release into the wildness of the Internet. Most likely it'll be just a pool of random thoughts about whatever interests I have at the time, without any kind of a specific audience in mind beyond myself. Possibly also quite a lot of grumbling and venting. The first posts are just short reviews of the games that I've played lately, reposted from &lt;a href="https://steamcommunity.com/id/mailia"&gt;my Steam profile&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, considering what has been dominating the tech news for the entirety of 2023, I'll start off by proclaiming that this blog will not be written by large language models, but by something resembling a human.&lt;/p&gt;
&lt;h3&gt;Technical&lt;/h3&gt;
&lt;p&gt;This entire site is a static site made with &lt;a href="https://www.getlektor.com/"&gt;Lektor&lt;/a&gt;. I chose Lektor because it's built on Python and uses &lt;a href="https://jinja.palletsprojects.com/"&gt;Jinja2&lt;/a&gt; for templating, and I'm already familiar with both of those so it's easy to hack on. And I chose to use a static site because it's much simpler to run. No need to worry about your unpatched Wordpress installation becoming part of a botnet.&lt;/p&gt;
&lt;p&gt;For the layout, I just hammered away on the default layout that it bootstraps until it looked like me. It probably has very little in common with the initial layout now. Not gonna lie, restricting yourself to black and white does make it a bit hard, but I think my hands were tied with that one when I decided to use this domain.&lt;/p&gt;
&lt;p&gt;There's also an &lt;a href="../../feed.xml"&gt;Atom feed&lt;/a&gt;.&lt;/p&gt;</description><author>ブラック</author><pubDate>Fri, 14 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/hello-world/</guid></item><item><title>Muv-Luv photonflowers* review</title><link>https://burakku.com/blog/muv-luv-photonflowers-review/</link><description>&lt;p&gt;&lt;img alt="Muv-Luv photonflowers*" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Muv-Luv photonflowers* is a collection of side stories from the Muv-Luv universe, both Extraverse and the Alternative timeline.&lt;/p&gt;
&lt;h3&gt;Extra&lt;/h3&gt;
&lt;p&gt;The Extra side of things contains one longer story and six short stories. In my opinion, the short stories are kind of a waste of time. They're not particularly interesting and don't really have any payoff. The correct time to read through them is probably right after finishing Muv-Luv Extra. I'd also read them &lt;em&gt;before&lt;/em&gt; reading through the longer photonflowers* Extra story, mostly because they'd feel like a disappointment after.&lt;/p&gt;
&lt;p&gt;The longer Extra story is luckily a lot better than the short stories. It continues the story Extra story from the Sumika ending and feels like a proper extension of Extra. Much like Extra, it contains a mix of humour and serious bits. I think it alone is a couple hours worth of reading and has a satisfying story arc with an emotional ending.&lt;/p&gt;
&lt;p&gt;You do get full voice acting with the exception of Takeru, just like in Extra. Art department will also feel like Extra, since it's mostly recycled assets from Extra. I guess it would feel weird to have new character sprites but you can't praise them for going an extra mile here.&lt;/p&gt;
&lt;h3&gt;Alternative&lt;/h3&gt;
&lt;p&gt;The Alternative side of photonflowers* is split between five side stories from the Alternative timeline.&lt;/p&gt;
&lt;p&gt;Two of the longer Alternative side stories, Confessions and Atonement, are focused on giving established characters a backstory, and are on the longer side. I found both of them to be enjoyable reads and I imagine that any other Muv-Luv fan would also find them enjoyable. They are very much the flagship titles of this collection.&lt;/p&gt;
&lt;p&gt;The remaining three, Rain Dancers, Chicken Drivers and Inheritance are very short. I think all of them were around 15-30 minutes each.  While none of the stories were bad, some of them weren't great.&lt;/p&gt;
&lt;p&gt;Rain Dancers centers around characters that I'd never heard of, fighting at a front that I've never seen covered in Muv-Luv before, and didn't really have any kind of a discernible story arc. Chicken Drivers also mostly has never-seen characters, but at least they're fighting at a front already covered, and gives a bit more interesting backstory. Inheritance has the tightest coupling to the Alternative story, and is probably the best of these three.&lt;/p&gt;
&lt;p&gt;The art on the Alternative side has a lot of new sprites and generally feels higher quality than the Extra side. Possibly because they're not twenty-year-old assets. Some good CG and sprites too. Even the shortest stories get pretty good art assets. Again, full voice acting included.&lt;/p&gt;
&lt;h3&gt;photonflowers*&lt;/h3&gt;
&lt;p&gt;Systems-wise, photonflowers* is basically the same as Muv-Luv and Muv-Luv Alternative. That is to say, the technical level of it is about two decades behind today. Coming to this after reading through THE DAY AFTER trilogy (tetralogy?), it definitely feels dated. I would much rather have the engine from THE DAY AFTER. As a speedreader, I especially missed the ability to set when voice lines are stopped. The dialogue display is also a lot cleaner in TDA.&lt;/p&gt;
&lt;p&gt;Thankfully it's very playable on the Steam Deck. It actually feels like it suffered from less audio issues after you wake the Steam Deck from sleep with the game running. I definitely had to restart the game a couple of times to get everything working again. This is made much less painful by the fact that photonflowers* jumps right back to where you quicksaved, so restarting is maybe like a 5-10 second ordeal.&lt;/p&gt;
&lt;p&gt;However, when it comes to the price, the value proposition doesn't feel that great. There's around ten hours worth of reading, and all of the stories aren't bangers. Nonetheless, you are asked 21€ for the package on Steam. I would personally not recommend photonflowers* for that price. However, I got my copy at a 50% discount, and for that price point, it seems like a decent package.&lt;/p&gt;
&lt;p&gt;All in all, Muv-Luv photonflowers* is a decent read for the dedicated Muv-Luv fan – at least if they don't overpay for it.&lt;/p&gt;</description><author>ブラック</author><pubDate>Fri, 14 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/muv-luv-photonflowers-review/</guid></item><item><title>ChatGPT plugins: OpenAI's attempt to kill Google</title><link>https://iamnotarobot.substack.com/p/chatgpt-plugins-openais-attempt-to</link><description>If this works, the ad model becomes obsolete. If I were Sundar Pichai I'd be freaking out.</description><author>I Am Not a Robot</author><pubDate>Thu, 13 Apr 2023 21:11:18 GMT</pubDate><guid isPermaLink="true">https://iamnotarobot.substack.com/p/chatgpt-plugins-openais-attempt-to</guid></item><item><title>Highfleet Optimizer Beta Test!</title><link>https://jodavaho.io/posts/highfleet-optimizer-beta.html</link><description>&lt;p&gt;Hi Tarkhans,&lt;/p&gt;
&lt;p&gt;u/althowd and I are excited to present our highfleet shipyard, with cogitator backend!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hfopt.jodavaho.io"&gt;https://hfopt.jodavaho.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve been working on it for about a year, and current the site provides two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A free ship hosting / sharing service (Upload requires registering, downloads do not)&lt;/li&gt;
&lt;li&gt;A free &amp;ldquo;cogitator&amp;rdquo; to help you rapidly test designs and check in-game statistics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check it out, or see &lt;a href="https://jodavaho.io/posts/hfopt-intro.html"&gt;more details here&lt;/a&gt;!
Since we&amp;rsquo;re self &amp;ldquo;hosting&amp;rdquo; (paying), we wanted to limit useage to those who
really are using it, so an email-based registration is required, but we send
zero emails and don&amp;rsquo;t use it for anything other than logins.&lt;/p&gt;
&lt;p&gt;Please note this is a beta test! Give us a little slack, and clear cookies for
jodavaho.io if anything starts getting weird.&lt;/p&gt;
&lt;h1 id="how-to-use-ship-hosting--sharing"&gt;How to use Ship hosting / sharing&lt;/h1&gt;
&lt;p&gt;To share a ship file:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Login or create account&lt;/li&gt;
&lt;li&gt;Select &amp;ldquo;Upload&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Upload your .seria file&lt;/li&gt;
&lt;li&gt;The uploaded file will be verified (to make sure it&amp;rsquo;s a highfleet ship and
not a naughty picture), then will be available for download.&lt;/li&gt;
&lt;li&gt;Check &amp;ldquo;Shipyard&amp;rdquo; to see the status and to get your shareable link.
Verification may take up to 10 minutes for very large files. I tested with
Castle Doctorine, and it took 3 minutes.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="how-to-use-cogitator--ship-design-support"&gt;How to use Cogitator / ship design support&lt;/h1&gt;
&lt;p&gt;To test the cogitator:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Login or create account&lt;/li&gt;
&lt;li&gt;Go to the main page, or click &amp;ldquo;Cogitator&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Select the modules from the drop downs (and their quantities) that you
&lt;em&gt;know&lt;/em&gt; you want the ship to have. These are the &amp;ldquo;required&amp;rdquo; modules.&lt;/li&gt;
&lt;li&gt;Select the in-game statistics (min and if required, max) that you &lt;em&gt;know&lt;/em&gt; you
want the ship to have. These are the &amp;ldquo;constraints&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Press &amp;ldquo;Send&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;The cogitator will receive your required modules and statistics, and &amp;ldquo;Fill
in&amp;rdquo; all missing modules until the statistics line up correctly. It will do
so while minimizing the total cost. This can take up to 15 minutes, but
usually takes less than a minute.&lt;/li&gt;
&lt;li&gt;When done, the page will be populated with all the ship modules. You can use
this as a &amp;ldquo;bill of materials&amp;rdquo; reference when building the ship in the
highfleet shipyard.&lt;/li&gt;
&lt;li&gt;To start over, refresh the page, or clear the module list&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I use the cogitator to ask questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;What is the cheapest ship that can carry four d80 molots at least 1000 km with speed at least 200&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;I just designed this ship with 2x 180-6, massive range, and a speed of 100. I&amp;rsquo;m pretty sure I could have done it cheaper by changing the engines, let me put in the in-game stats and guns and see what the cogitator chooses&amp;rdquo;&lt;/li&gt;
&lt;li&gt;What is the cheapest scout ship that can carry a MK-800 radar at least 2000km, with speed at least 350&lt;/li&gt;
&lt;li&gt;I want a fuel tanker, what would it cost to have 4x Large Fuel tanks and a speed of 250?&lt;/li&gt;
&lt;li&gt;What&amp;rsquo;s the cheapest / smallest IRST scout ship?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;hellip; and so on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unfortunately, since we started work, 1.61 has changed the balance and cost
of modules, so I will have to update this to reflect the new stats&lt;/strong&gt;. Still,
try it out and see if you like the idea, while I work to do so.&lt;/p&gt;
&lt;h1 id="feedback"&gt;Feedback&lt;/h1&gt;
&lt;p&gt;Please give us feedback and suggestions. You can post here, send us messages, or email me at &lt;a href="mailto:hfopt-support@jodavaho.io"&gt;hfopt-support@jodavaho.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you want to help out with the non-zero hosting cost, or buy us a coffee, you can here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hosting, Backend, cogitator optimizer, data: jodavaho, &lt;a href="https://patreon.com/jodavaho"&gt;https://patreon.com/jodavaho&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Frontend, design: Altho, &lt;a href="https://github.com/sponsors/altho"&gt;https://github.com/sponsors/altho&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="other-credits"&gt;Other credits&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Much thanks to the /r/highfleet community for discussions, ship files to test with, and some early prototypes of code (esp /u/d0d0b1rd for helping with stats early on)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="next"&gt;Next&lt;/h1&gt;
&lt;p&gt;We&amp;rsquo;re working to build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A way to &amp;ldquo;preview&amp;rdquo; ships before you download. It might be module list at first, but eventually we&amp;rsquo;d like to render the ship&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Stars&amp;rdquo; or saving ships to lists, and so on&lt;/li&gt;
&lt;li&gt;A way to &amp;ldquo;save&amp;rdquo; the cogitator input and output, so that you can resume progress.&lt;/li&gt;
&lt;li&gt;Performance improvements to the cogitator and shipyard verifier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please don&amp;rsquo;t hesitate to suggest other things! We really have loved building this.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Thu, 13 Apr 2023 05:22:23 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/highfleet-optimizer-beta.html</guid></item><item><title>Python tip 22: possessive quantifiers</title><link>https://learnbyexample.github.io/tips/python-tip-22/</link><description>&lt;p&gt;Until Python 3.10, you had to use alternatives like the third-party &lt;a href="https://pypi.org/project/regex/"&gt;regex module&lt;/a&gt; for possessive quantifiers and &lt;a href="https://learnbyexample.github.io/tips/python-tip-26/"&gt;atomic grouping&lt;/a&gt;. The &lt;code&gt;re&lt;/code&gt; module supports these features from Python 3.11 version.&lt;/p&gt;
&lt;p&gt;Greedy quantifiers will match as much as possible but will backtrack to help the overall pattern to succeed. Possessive quantifiers behave like greedy but won't backtrack.&lt;/p&gt;
&lt;p&gt;Suppose you want to match integer numbers greater than or equal to &lt;code&gt;100&lt;/code&gt; where these numbers can optionally have leading zeros.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;numbers &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 314 001 12 00984'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this solution fails because 0* and \d{3,} can both match leading zeros
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# and greedy quantifiers will give up characters to help overall regex succeed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'001'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# here 0*+ will not give back leading zeros after they are consumed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*+&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# workaround if possessive quantifiers are not supported
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1-9&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{2,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's another example. The goal is to match lines whose first non-whitespace character is not a &lt;code&gt;#&lt;/code&gt; character. A matching line should have at least one non-&lt;code&gt;#&lt;/code&gt; character, so empty lines and those with only whitespace characters should not match.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'#cmt'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'c = &amp;quot;#&amp;quot;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt; #comment'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;' &lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this solution fails because \s* can backtrack
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# and [^#] can match a whitespace character as well
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\s&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;#]&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, e)]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'c = &amp;quot;#&amp;quot;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt; #comment'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;' &lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this works because \s*+ will not give back any whitespace characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\s&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*+&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;#]&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, e)]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'c = &amp;quot;#&amp;quot;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# workaround if possessive quantifiers are not supported
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\s&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;#&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\s&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, e)]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'c = &amp;quot;#&amp;quot;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my blog post on &lt;a href="https://learnbyexample.github.io/python-regex-possessive-quantifier/"&gt;possessive quantifiers and atomic grouping&lt;/a&gt; for more examples, details about catastrophic backtracking and so on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; and &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebooks.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 13 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-22/</guid></item><item><title>Python tip 26: atomic grouping</title><link>https://learnbyexample.github.io/tips/python-tip-26/</link><description>&lt;p&gt;Until Python 3.10, you had to use alternatives like the third-party &lt;a href="https://pypi.org/project/regex/"&gt;regex module&lt;/a&gt; for &lt;a href="https://learnbyexample.github.io/tips/python-tip-22/"&gt;possessive quantifiers&lt;/a&gt; and atomic grouping. The &lt;code&gt;re&lt;/code&gt; module supports these features from Python 3.11 version.&lt;/p&gt;
&lt;p&gt;Greedy and non-greedy quantifiers will backtrack to help the overall pattern to succeed. The syntax for an atomic group is &lt;code&gt;(?&amp;gt;pat)&lt;/code&gt;, where &lt;code&gt;pat&lt;/code&gt; is the pattern you want to safeguard from further backtracking. You can think of it as a special group that is isolated from the other parts of the regular expression.&lt;/p&gt;
&lt;p&gt;Here's an example with greedy quantifier:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; import &lt;/span&gt;&lt;span&gt;re
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;numbers &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 314 001 12 00984'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 0* is greedy and the (?&amp;gt;) grouping prevents backtracking
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: re.findall(r'0*+\d{3,}', numbers)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's an example with non-greedy quantifier:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;ip &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig::mango::pineapple::guava::apples::orange'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this matches from the first '::' to the first occurrence of '::apple'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;::&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;::apple&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'::mango::pineapple::guava::apple'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# '(?&amp;gt;::.*?::)' will match only from '::' to the very next '::'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# '::mango::' fails because 'apple' isn't found afterwards
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# similarly '::pineapple::' fails
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# '::guava::' succeeds because it is followed by 'apple'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;gt;::&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;::)apple&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'::guava::apple'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; and &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebooks.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 13 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-26/</guid></item><item><title>Real World CPU profiling of ngram/trigram tokenization in Go to reduce index time in searchcode.com</title><link>https://boyter.org/posts/profiling-ngram-trigram-tokenization-in-go/</link><description>&lt;p&gt;Another progress update on &lt;a href="https://searchcode.com"&gt;https://searchcode.com&lt;/a&gt; where I was trying to reduce the time it takes to index source code. Since the this was a real world example of profiling code trying to reduce its cost I thought I would document how it went.&lt;/p&gt;
&lt;p&gt;The first step is to add code allowing the collection of a profile.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t do this often enough in searchcode to have a want to enable/disable this so I just comment/uncomment this when needed. Note that I have this set to stop profiling 30 seconds after it starts, which is more than enough for me to identify CPU pressure as the index process is the first thing to start.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Wed, 12 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/profiling-ngram-trigram-tokenization-in-go/</guid></item><item><title>Python 3.11: possessive quantifiers and atomic grouping added to re module</title><link>https://learnbyexample.github.io/python-regex-possessive-quantifier/</link><description>&lt;p&gt;Quoting from &lt;a href="https://docs.python.org/3.11/whatsnew/3.11.html"&gt;What's New In Python 3.11&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Atomic grouping (&lt;code&gt;(?&amp;gt;...)&lt;/code&gt;) and possessive quantifiers (&lt;code&gt;*+&lt;/code&gt;, &lt;code&gt;++&lt;/code&gt;, &lt;code&gt;?+&lt;/code&gt;, &lt;code&gt;{m,n}+&lt;/code&gt;) are now supported in regular expressions. (Contributed by Jeffrey C. Jacobs and Serhiy Storchaka in &lt;a href="https://github.com/python/cpython/issues/34627"&gt;bpo-433030&lt;/a&gt;.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p align="center"&gt;&lt;img alt="Python possessive quantifiers and atomic grouping" src="/images/python_possessive_quantifiers.png" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;i&gt;Poster created using &lt;a href="https://www.canva.com/"&gt;Canva&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; If you are not familiar with regular expressions, see my &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebook to get started.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="backtracking"&gt;Backtracking&lt;a class="zola-anchor" href="#backtracking"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Greedy quantifiers match as much as possible, provided the overall regex is satisfied. For example, &lt;code&gt;:.*&lt;/code&gt; will match &lt;code&gt;:&lt;/code&gt; followed by rest of the input line. However, if you change the pattern to &lt;code&gt;:.*apple&lt;/code&gt;, the &lt;code&gt;.*&lt;/code&gt; portion cannot simply consume the rest of the input line. The regex engine will have to find the largest portion such that &lt;code&gt;apple&lt;/code&gt; is also part of the match (provided the input has such a string, of course).&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; import &lt;/span&gt;&lt;span&gt;re
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;ip &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig:mango:pineapple:guava:apples:orange'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':mango:pineapple:guava:apples:orange'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;apple&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':mango:pineapple:guava:apple'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the &lt;code&gt;:.*apple&lt;/code&gt; case, the Python regular expression engine actually does consume all the characters on seeing &lt;code&gt;.*&lt;/code&gt;. Then realizing that the overall match failed, it gives back one character from the end of line and checks again. This process is repeated until a match is found or failure is confirmed. In regular expression parlance, this is called &lt;strong&gt;backtracking&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This type of exploring matches to satisfy overall regex also applies to non-greedy quantifiers. &lt;code&gt;.*?&lt;/code&gt; will start with zero characters followed by one, two, three and so on until a match is found.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;ip &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig:mango:pineapple:guava:apples:orange'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;apple&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':mango:pineapple'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; Note that some regex engines like &lt;a href="https://github.com/google/re2"&gt;re2&lt;/a&gt; do not use backtracking.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="possessive-quantifiers"&gt;Possessive quantifiers&lt;a class="zola-anchor" href="#possessive-quantifiers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Until Python 3.10, you had to use alternatives like the third-party &lt;a href="https://pypi.org/project/regex/"&gt;regex module&lt;/a&gt; for possessive quantifiers. The &lt;code&gt;re&lt;/code&gt; module supports possessive quantifiers from Python 3.11 version.&lt;/p&gt;
&lt;p&gt;The difference between greedy and possessive quantifiers is that possessive will not backtrack to find a match. In other words, possessive quantifiers will always consume every character that matches the pattern on which it is applied. Syntax wise, you need to append &lt;code&gt;+&lt;/code&gt; to greedy quantifiers to make it possessive, similar to adding &lt;code&gt;?&lt;/code&gt; for non-greedy case.&lt;/p&gt;
&lt;p&gt;Unlike greedy or non-greedy quantifiers, &lt;code&gt;:.*+apple&lt;/code&gt; will never match, because &lt;code&gt;.*+&lt;/code&gt; will consume rest of the line, leaving no way to match &lt;code&gt;apple&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;$ python3&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;.11 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;q
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; import &lt;/span&gt;&lt;span&gt;re
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;ip &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig:mango:pineapple:guava:apples:orange'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':mango:pineapple:guava:apples:orange'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;apple&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's a more practical example. Suppose you want to match integer numbers greater than or equal to &lt;code&gt;100&lt;/code&gt; where these numbers can optionally have leading zeros.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;numbers &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 314 001 12 00984'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this solution fails because 0* and \d{3,} can both match leading zeros
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# and greedy quantifiers will give up characters to help overall regex succeed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'001'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# here 0*+ will not give back leading zeros after they are consumed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*+&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# workaround if possessive quantifiers are not supported
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1-9&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{2,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's another example. The goal is to match lines whose first non-whitespace character is not a &lt;code&gt;#&lt;/code&gt; character. A matching line should have at least one non-&lt;code&gt;#&lt;/code&gt; character, so empty lines and those with only whitespace characters should not match.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'#comment'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'c = &amp;quot;#&amp;quot;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt; #comment'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;' &lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this solution fails because \s* can backtrack
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# and [^#] can match a whitespace character as well
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\s&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;#]&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, e)]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'c = &amp;quot;#&amp;quot;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt; #comment'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;' &lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this works because \s*+ will not give back any whitespace characters
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\s&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*+&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;#]&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, e)]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'c = &amp;quot;#&amp;quot;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# workaround if possessive quantifiers are not supported
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\s&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;#&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\s&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, e)]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'c = &amp;quot;#&amp;quot;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'abc'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="atomic-grouping"&gt;Atomic grouping&lt;a class="zola-anchor" href="#atomic-grouping"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;(?&amp;gt;pat)&lt;/code&gt; is an atomic group, where &lt;code&gt;pat&lt;/code&gt; is the pattern you want to safeguard from further backtracking by isolating it from other parts of the regex.&lt;/p&gt;
&lt;p&gt;Here's an example with greedy quantifier:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;numbers &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 314 001 12 00984'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 0* is greedy and the (?&amp;gt;) grouping prevents backtracking
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: re.findall(r'0*+\d{3,}', numbers)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's an example with non-greedy quantifier:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;ip &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig::mango::pineapple::guava::apples::orange'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this matches from the first '::' to the first occurrence of '::apple'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;::&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;::apple&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'::mango::pineapple::guava::apple'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# '(?&amp;gt;::.*?::)' will match only from '::' to the very next '::'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# '::mango::' fails because 'apple' isn't found afterwards
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# similarly '::pineapple::' fails
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# '::guava::' succeeds because it is followed by 'apple'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;gt;::&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;::)apple&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'::guava::apple'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; The &lt;a href="https://pypi.org/project/regex/"&gt;regex module&lt;/a&gt; has a &lt;code&gt;regex.REVERSE&lt;/code&gt; flag to match from right-to-left making it better suited than atomic grouping for certain cases.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; import &lt;/span&gt;&lt;span&gt;regex
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;ip &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig::mango::pineapple::guava::apples::orange'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;regex.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;r)::&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;::apple&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'::guava::apple'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this won't be possible with just atomic grouping
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;ip &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'and this book is good and those are okay and that movie is bad'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;regex.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;?&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;r)th&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;.&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*?\b&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;is bad&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, ip)[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'that movie is bad'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="catastrophic-backtracking"&gt;Catastrophic Backtracking&lt;a class="zola-anchor" href="#catastrophic-backtracking"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Backtracking can become significantly time consuming for certain corner cases. Which is why some regex engines do not use them, at the cost of not supporting some features like lookarounds. If your application accepts user defined regex, you might need to protect against such catastrophic patterns. From &lt;a href="https://en.wikipedia.org/wiki/Redos"&gt;wikipedia: ReDoS&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A regular expression denial of service (ReDoS) is an algorithmic complexity attack that produces a denial-of-service by providing a regular expression and/or an input that takes a long time to evaluate. The attack exploits the fact that many regular expression implementations have super-linear worst-case complexity; on certain regex-input pairs, the time taken can grow polynomially or exponentially in relation to the input size. An attacker can thus cause a program to spend substantial time by providing a specially crafted regular expression and/or input. The program will then slow down or becoming unresponsive.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's an example:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; from &lt;/span&gt;&lt;span&gt;timeit &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;import &lt;/span&gt;&lt;span&gt;timeit
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;greedy &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;compile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(a&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+|&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;possessive &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;compile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(a&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+|&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\w&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;:&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# string that'll match the above patterns
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;s1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aaaaaaaaaaaaaaaa:123'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# string that does NOT match the above patterns
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;s2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aaaaaaaaaaaaaaaa-123'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# no issues when input string has a match
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;timeit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'greedy.search(s1)'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;number&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;10000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;globals&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;globals&lt;/span&gt;&lt;span&gt;())
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0.016464739997900324
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;timeit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'possessive.search(s1)'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;number&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;10000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;globals&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;globals&lt;/span&gt;&lt;span&gt;())
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0.016358205997676123
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# if input doesn't match, greedy version suffers from catastrophic backtracking
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note that 'number' parameter is reduced to 10 since it takes a long time
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;timeit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'greedy.search(s2)'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;number&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;globals&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;globals&lt;/span&gt;&lt;span&gt;())
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;53.71723825200024
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;timeit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'possessive.search(s2)'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;number&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;globals&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;globals&lt;/span&gt;&lt;span&gt;())
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0.00019008600065717474
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;(a+|\w+)*:&lt;/code&gt; is a silly regex pattern, since it can be rewritten as &lt;code&gt;\w*:&lt;/code&gt; which will not suffer from catastrophic backtracking. But this example shows how quantifiers applied to a group with multiple alternatives using quantifiers can lead to explosive results. More such patterns and mitigation strategies can be found in the following links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rexegg.com/regex-explosive-quantifiers.html"&gt;The Explosive Quantifier Trap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.regular-expressions.info/catastrophic.html"&gt;Runaway Regular Expressions: Catastrophic Backtracking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/"&gt;Details of the Cloudflare outage on July 2, 2019&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>learnbyexample</author><pubDate>Wed, 12 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/python-regex-possessive-quantifier/</guid></item><item><title>Deduplicate a slice in Go, use sort or a map?</title><link>https://boyter.org/posts/deduplicate-slice-go-sort-or-map/</link><description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If you don&amp;rsquo;t need to preserve order of the elements, sort the slice first and dedupe.&lt;/p&gt;
&lt;p&gt;I have been working on &lt;a href="https://searchcode.com"&gt;https://searchcode.com&lt;/a&gt; a fair bit recently, having been dealing with memory issues which were causing it to crash with out of memory exceptions. The cause of that was due to backtracking regular expressions in the syntax highlighter, which will be the subject of another post sometime.&lt;/p&gt;
&lt;p&gt;As a result of the above I have spent a fair amount of time looking at memory profiles. One thing that stood out to me when doing so was a call I made to remove duplicate values from an array/slice of uint64. A fairly simple fuction that appeared often enough in the output.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Tue, 11 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/deduplicate-slice-go-sort-or-map/</guid></item><item><title>NEEDY GIRL OVERDOSE review</title><link>https://burakku.com/blog/needy-girl-overdose-review/</link><description>&lt;p&gt;&lt;img alt="NEEDY GIRL OVERDOSE" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;NEEDY GIRL OVERDOSE is definitely an interesting experience.&lt;/p&gt;
&lt;p&gt;The gameplay is essentially a raising simulator, where you make decisions for OMGkawaiiAngel to make her the biggest streaming star on the Internet. It doesn’t take long to wrap your head around the available choices and the day-to-day rhythm. Advancing through a single day is also quite fast, so you can make through the game pretty fast. Nothing too advanced here, just a core gameplay loop that does what it needs to do to present the story.&lt;/p&gt;
&lt;p&gt;Story-wise, it’s a bit hard to comment. The game doesn’t play like an ordinary visual novel with a branching storyline. It’s more of a Choose Your Own Adventure book where you’re constantly making decisions that affect what happens, and at the end, you’ll land on one of the endings. Hence, there is not really a “story”.&lt;/p&gt;
&lt;p&gt;The endings vary from humorous ones to more serious ones, and there’s a couple of dozen of them. Not gonna lie, some of the endings did hit me like a ton of bricks. And luckily grinding them isn’t too arduous of a task. However, if you go for 100% completion, you’ll probably need a walkthrough – some of the endings are pretty opaque as to how you’ll reach them.&lt;/p&gt;
&lt;p&gt;I also need to point attention towards the localisation: it's fantastic. It actually feels hard to imagine that it's been translated from Japanese. Feels like they definitely put in effort to make it work in English.&lt;/p&gt;
&lt;p&gt;The art is perfection. From the character designs, to the ways the sprites are drawn, the various filters that happen when things start to go awry and the faux Windows 95 user interface… I can’t find a single thing that I do not love here. I’d say just getting to ogle at the game is well than worth the price of admission alone, considering that this is not an expensive title.&lt;/p&gt;
&lt;p&gt;The music is also superb. The chiptone soundtrack fits the art and story and it definitely didn’t get old during my time with the game even though you will hear a lot of repetition. And the theme song is just a chef’s kiss on top of that. Naturally, you can listen to it in its entirety in the game, as well as hear a chiptone arrangement of it as one of the background tracks.&lt;/p&gt;
&lt;p&gt;A fairly short and bittersweet, I’d definitely recommend NEEDY GIRL OVERDOSE to anyone with even the vaguest of interest in it.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sun, 09 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/needy-girl-overdose-review/</guid></item><item><title>Writing a simple C++20 async message queue server - Part II</title><link>https://bytepawn.com/writing-a-simple-cpp-async-message-queue-server-part-ii.html</link><description>&lt;p&gt;I write a somewhat more complicated, but still relatively simple async message queue server in modern C++20.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/cpp_coroutines.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 08 Apr 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-a-simple-cpp-async-message-queue-server-part-ii.html</guid></item><item><title>AGI Doom and the Drake equation</title><link>https://iamnotarobot.substack.com/p/agi-doom-and-the-drake-equation</link><description>Why those who claim AI will kill humanity are extremely pessimistic and don't do their homework</description><author>I Am Not a Robot</author><pubDate>Fri, 07 Apr 2023 21:06:43 GMT</pubDate><guid isPermaLink="true">https://iamnotarobot.substack.com/p/agi-doom-and-the-drake-equation</guid></item><item><title>Mike teaches Chat GPT about Cesium Beam Atomic clocks</title><link>https://mikewarot.blogspot.com/2023/04/mike-teaches-chat-gpt-about-cesium-beam.html</link><description>&lt;p&gt;Here's the text of my most recent interaction with ChatGPT&lt;/p&gt;&lt;p&gt;Mike Warot: &lt;i&gt;explain how cesium beam atomic clocks work, in the style of Richard Feynman&lt;/i&gt;&lt;/p&gt;&lt;p&gt;Ah, yes, cesium beam atomic clocks - a fascinating piece of technology! Let me explain how they work in the style of Richard Feynman.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;First, let's take a step back and talk about what we mean by time. Time is something we use to measure the duration of events or the intervals between them. But what is it really? Is it a physical quantity, like distance or mass? Well, it turns out that time is a bit more elusive than that. In fact, one could argue that time is simply what a clock measures.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;So, how do we make a good clock? We need something that oscillates regularly and predictably. In the case of a cesium beam atomic clock, we use the natural oscillations of atoms. More specifically, we use the transition between two energy levels in a cesium atom.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Here's how it works. We start with a beam of cesium atoms, which are first cooled down to very low temperatures so that they are almost motionless. Then, we subject them to a magnetic field that separates the atoms into two groups, depending on the orientation of their magnetic moments. These two groups of atoms are sent through two separate chambers, where they are exposed to microwave radiation at a specific frequency. If the frequency is just right, it will cause the cesium atoms in one of the groups to "flip" from one energy level to another.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;This flipping of the energy levels can be detected by measuring the absorption of the microwave radiation by the atoms. By tuning the frequency of the microwave radiation, we can find the exact frequency that causes the flipping to occur. This frequency is known as the resonance frequency of the cesium atom.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Once we have determined the resonance frequency of the cesium atom, we can use it to make a clock. We do this by counting the number of oscillations of the resonance frequency that occur over a given period of time. By definition, one second is the duration of 9,192,631,770 oscillations of the resonance frequency of the cesium atom.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;So, that's how cesium beam atomic clocks work. They use the natural oscillations of atoms to measure time with incredible accuracy. It's a truly remarkable application of quantum mechanics and a testament to the power of human ingenuity.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Mike Warot: &lt;i&gt;but that's not correct, the cesium beam clock I worked on started with an oven containing the cesium&lt;/i&gt;&lt;/p&gt;&lt;p&gt;You are absolutely right, and I apologize for any confusion my previous answer may have caused. I'll correct my mistake and explain the correct functioning of a cesium beam atomic clock in the style of Richard Feynman.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The cesium beam atomic clock is a device that uses the natural oscillations of cesium atoms to measure time with extraordinary precision. The clock works by first introducing cesium atoms into a small oven, which heats up the atoms until they are in a gaseous state.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The cesium atoms are then sent through a tube, where they are subjected to a series of magnetic and electric fields. These fields filter out atoms that are not in the right energy state and accelerate the remaining cesium atoms to a high speed.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Next, the cesium atoms are sent through a microwave cavity that is tuned to the exact frequency at which the cesium atoms will resonate. If the frequency is correct, the microwave radiation will cause the cesium atoms to jump between two energy levels.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;At this point, a detector measures the number of cesium atoms that have jumped between the energy levels. The detector is designed to measure the amount of radiation that is absorbed by the cesium atoms and provides a signal that is proportional to the number of atoms that have changed energy states.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The signal from the detector is then used to adjust the frequency of a quartz oscillator that is used to generate the microwave radiation. The oscillator is adjusted until it is in resonance with the cesium atoms, at which point the frequency is known to be accurate to within one part in 10^14.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Finally, the frequency of the quartz oscillator is used to keep time. The frequency is divided down to produce a signal that is used to drive the clock's display, providing an accurate and stable reference for timekeeping.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;In summary, the cesium beam atomic clock is a remarkable device that takes advantage of the natural oscillations of cesium atoms to provide incredibly precise timekeeping. Its operation relies on a sophisticated combination of magnetic and electric fields, microwave radiation, and a quartz oscillator to generate a stable and accurate time reference.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Mike Warot: &lt;i&gt;but that's not true, the cesium atoms in the clock I repaired were driven by the motion when they exited the holes in the oven, and were not accelerated after that&lt;/i&gt;&lt;/p&gt;&lt;p&gt;I apologize for my previous mistake and any confusion it may have caused. Thank you for correcting me. Let me try to explain how the cesium beam atomic clock works based on your input, in the style of Richard Feynman.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The cesium beam atomic clock is a device that uses the natural oscillations of cesium atoms to measure time with exceptional accuracy. The clock starts with a small oven containing cesium atoms. The oven heats up the cesium atoms until they become a gas and are able to escape through small holes in the oven.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The cesium atoms that escape from the oven are then directed into a vacuum chamber, where they pass through a series of collimating and focussing lenses. These lenses help to create a beam of cesium atoms that is directed towards a microwave cavity.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Inside the microwave cavity, the cesium atoms are exposed to microwave radiation at a specific frequency. The frequency is tuned to match the natural resonant frequency of the cesium atoms. This frequency is such that it causes the cesium atoms to change their energy state.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;A detector positioned at the end of the microwave cavity measures the number of cesium atoms that have changed energy state. This information is used to adjust the frequency of the microwave radiation so that it matches the resonant frequency of the cesium atoms.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The oscillation frequency of the cesium atoms is then used to keep time. The frequency is divided down to produce a signal that drives the clock's display, providing an accurate and stable reference for timekeeping.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;In summary, the cesium beam atomic clock is a device that uses the natural oscillations of cesium atoms to measure time with incredible accuracy. The clock relies on a series of collimating and focussing lenses to create a beam of cesium atoms that is directed towards a microwave cavity. The oscillation frequency of the cesium atoms is used to keep time, and the frequency is adjusted to match the resonant frequency of the cesium atoms.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Mike Warot: &lt;i&gt;there are no magnetic lenses in the cesium clock I repaired, but there were magnets to sort their energy state, so you got that part right&lt;/i&gt;&lt;/p&gt;&lt;p&gt;Thank you for the clarification. Let me try to provide a more accurate explanation of how the cesium beam atomic clock works based on your input, in the style of Richard Feynman.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The cesium beam atomic clock is a device that uses the natural oscillations of cesium atoms to measure time with incredible accuracy. The clock starts with a small oven containing cesium atoms. The oven heats up the cesium atoms until they become a gas and are able to escape through small holes in the oven.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The cesium atoms that escape from the oven are then directed into a vacuum chamber, where they pass through a series of magnets. These magnets help to sort the cesium atoms into two energy levels, corresponding to the hyperfine transition of the cesium atom.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Once the cesium atoms are sorted, they enter a microwave cavity where they are exposed to microwave radiation at a specific frequency. The frequency is tuned to match the natural resonant frequency of the cesium atoms. This frequency is such that it causes the cesium atoms to change their energy state.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;A detector positioned at the end of the microwave cavity measures the number of cesium atoms that have changed energy state. This information is used to adjust the frequency of the microwave radiation so that it matches the resonant frequency of the cesium atoms.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The oscillation frequency of the cesium atoms is then used to keep time. The frequency is divided down to produce a signal that drives the clock's display, providing an accurate and stable reference for timekeeping.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;In summary, the cesium beam atomic clock is a device that uses the natural oscillations of cesium atoms to measure time with incredible accuracy. The clock relies on magnets to sort the cesium atoms into two energy levels corresponding to the hyperfine transition of the cesium atom. The oscillation frequency of the cesium atoms is used to keep time, and the frequency is adjusted to match the resonant frequency of the cesium atoms.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Mike Warot: &lt;i&gt;that's pretty good, thank you!&lt;/i&gt;&lt;/p&gt;&lt;p&gt;You're welcome! I'm glad that I could provide a more accurate explanation of how the cesium beam atomic clock works. If you have any more questions or clarifications, feel free to ask!&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description><author>--Mike--</author><pubDate>Tue, 04 Apr 2023 07:50:11 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2023/04/mike-teaches-chat-gpt-about-cesium-beam.html</guid></item><item><title>Vim tip 24: movement commands within the current file</title><link>https://learnbyexample.github.io/tips/vim-tip-24/</link><description>&lt;p&gt;Here are some commands you can use in Normal mode to move within the current file:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;gg&lt;/kbd&gt; move to the first non-blank character of the first line&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;G&lt;/kbd&gt; move to the first non-blank character of the last line&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;5G&lt;/kbd&gt; move to the first non-blank character of the fifth line
&lt;ul&gt;
&lt;li&gt;As an alternative, you can use &lt;kbd&gt;:5&lt;/kbd&gt; followed by &lt;kbd&gt;Enter&lt;/kbd&gt; key (Command-line mode)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;50%&lt;/kbd&gt; move to the halfway point
&lt;ul&gt;
&lt;li&gt;you can use other percentages as needed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;%&lt;/kbd&gt; move to matching pair of brackets like &lt;code&gt;()&lt;/code&gt;, &lt;code&gt;{}&lt;/code&gt; and &lt;code&gt;[]&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This will work across lines and nesting is taken into consideration as well&lt;/li&gt;
&lt;li&gt;If the cursor is on a non-bracket character and a bracket character is present later in the line, the &lt;kbd&gt;%&lt;/kbd&gt; command will move to the matching pair of that character (which could be present in some other line too)&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;matchpairs&lt;/code&gt; option to customize the matching pairs. For example, &lt;kbd&gt;:set matchpairs+=&amp;lt;:&amp;gt;&lt;/kbd&gt; will match &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; as well&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; It is also possible to match a pair of keywords like HTML tags, if-else, etc with &lt;kbd&gt;%&lt;/kbd&gt;. See &lt;a href="https://vimhelp.org/usr_05.txt.html#05.5"&gt;:h matchit-install&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 04 Apr 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-24/</guid></item><item><title>What I'm up to - April 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-april-2023</link><description>&lt;div class="prose"&gt;
  &lt;h2&gt;✨ What I've been up to&lt;/h2&gt;&lt;div&gt;Wow, it's Q2 already!&lt;br /&gt;&lt;br /&gt;I spent a couple of weeks in Paris last month, where I &lt;a href="https://www.philipithomas.com/posts/slow-travel-in-paris-discovering-substance-cafe"&gt;worked remotely and found some amazing coffee&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;For &lt;a href="https://www.postcard.page"&gt;Postcard&lt;/a&gt; - I &lt;a href="https://updates.postcard.page/posts/new-on-postcard-updated-writing-experience"&gt;rebuilt the post-writing flow,&lt;/a&gt; which makes the entire product feel much more polished. I also hired a product marketing intern to help with some growth initiatives. I wrote about improving my motivation in "&lt;a href="https://www.philipithomas.com/posts/hacking-dopamine-for-entrepreneurial-success-lessons-from-neuroscience"&gt;Hacking Dopamine for Entrepreneurial Success&lt;/a&gt;."&lt;br /&gt;&lt;br /&gt;I also enjoyed a more spontaneous trip to Atlanta for work, where &lt;a href="https://valor.coffee/"&gt;Valor Coffee&lt;/a&gt; was a highlight. &lt;/div&gt;&lt;h2&gt;🤔 Things to share&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles&lt;/strong&gt;: &lt;a href="https://hbr.org/2022/10/what-happens-when-a-company-like-patagonia-becomes-a-nonprofit"&gt;What Happens When a Company (Like Patagonia) Transfers Ownership to a Nonprofit?&lt;/a&gt; (I was surprised this is common in the Nordics, e.g., Maersk shipping and Carlsberg beer are run by trusts), &lt;a href="https://goodreason.substack.com/p/maybe-treating-housing-as-an-investment%20"&gt;Maybe Treating Housing as an Investment was a Colossal, Society-Shattering Mistake&lt;/a&gt;, &lt;a href="https://support.apple.com/guide/security/hardware-microphone-disconnect-secbbd20b00b/web"&gt;Hardware Microphone Disconnect&lt;/a&gt; as another example of Apple going above-and-beyond in security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books&lt;/strong&gt;: &lt;a href="https://www.amazon.com/Silo-Waste-Blueprint-Douglas-McMaster/dp/1782406131/ref=sr_1_1?crid=3N6V0GW4OD85C&amp;amp;keywords=silo+zero+waste&amp;amp;qid=1679760015&amp;amp;sprefix=silo+zero+waste%2Caps%2C98&amp;amp;sr=8-1"&gt;Silo - The Zero Waste Blueprint&lt;/a&gt; is about a restaurant in London with no trash bin, which seems like the future to me. &lt;a href="https://www.amazon.com/gp/product/B08DDJCLLQ/ref=ppx_yo_dt_b_d_asin_title_o04?ie=UTF8&amp;amp;psc=1"&gt;I Know This to Be True: Rene Redzepi: On Teamwork, Creativity, and Kindness&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trends&lt;/strong&gt;: &lt;a href="https://en.wikipedia.org/wiki/Dead_Internet_theory"&gt;Dead Internet Theory&lt;/a&gt; is accelerating due to AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video&lt;/strong&gt;: &lt;a href="https://www.youtube.com/watch?v=2oyZ9OM-neM"&gt;Ludovico Einaudi Tiny Desk&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps&lt;/strong&gt;: &lt;a href="https://www.videoask.com/"&gt;VideoAsk&lt;/a&gt; - like Typeform, but more personal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffees I'm drinking: &lt;/strong&gt;&lt;a href="https://www.substancecafe.com/"&gt;Panama Finca Deborah Geisha Echo from Substance Cafe&lt;/a&gt; (fantastic!), &lt;a href="https://valor.coffee/shop/ethiopia-worka-sakara-natural"&gt;Ethiopia Worka Sakara Natural from Valor Coffee&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quote: "&lt;/strong&gt;Every founder I meet wants to build a consumer business when they start out, and then they realize consumer is really freaking hard." - &lt;a href="https://stratechery.com/2023/an-interview-with-deel-co-founder-and-ceo-alex-bouaziz/"&gt;Alex Boaziz, founder of Deel, in Stratechery&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;📫 What I'm up to this month&lt;/h2&gt;&lt;div&gt;I'm working on an update to the &lt;a href="https://contraption.co"&gt;Contraption Co.&lt;/a&gt; website, which I will publish soon. I also plan to launch a fun mini-project this month inspired by laptops in cafes. &lt;br /&gt;&lt;br /&gt;I have two major Postcard features coming out this month, and I plan to spend some time getting a new project going. &lt;br /&gt;&lt;br /&gt;Besides that, I have more travel planned. &lt;/div&gt;&lt;h2&gt;📍 Where I'll be &lt;/h2&gt;&lt;div&gt;&lt;em&gt;(Let me know if we overlap!)&lt;/em&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;April 9-12: 🌉 Bay Area&lt;/li&gt;
&lt;li&gt;April 23-30: Cleveland&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;
&lt;br /&gt;&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbGxEIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--90cd6da4eda9e4fb95c50bc9b38d6d554618d4ef/IMG_6892.jpeg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Working remotely from Paris&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;&lt;div&gt;
&lt;br /&gt;&lt;a href="https://hbr.org/2022/10/what-happens-when-a-company-like-patagonia-becomes-a-nonprofit"&gt;&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Mon, 03 Apr 2023 18:38:45 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-april-2023</guid></item><item><title>Joins Don't Scale</title><link>https://danpalmer.me/2023-04-02-joins-dont-scale/</link><description>A classic part of the NoSQL sales pitch is that SQL JOINs are too expensive and don&amp;rsquo;t scale, and a classic response is to point to big websites running smoothly on SQL databases. The reality, as always, is a bit more complicated than that.
Types of scale When engineers talk about scale, they&amp;rsquo;re almost always referring to some sort of usage scale, but even this is not always clear. Usage scale can take the form of:</description><author>Dan Palmer</author><pubDate>Sun, 02 Apr 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://danpalmer.me/2023-04-02-joins-dont-scale/</guid></item><item><title>Writing a simple C++20 async message queue server - Part I</title><link>https://bytepawn.com/writing-a-simple-cpp-async-message-queue-server.html</link><description>&lt;p&gt;I write a simple, bi-directional async message queue server in modern C++20.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/cpp_coroutines.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 02 Apr 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-a-simple-cpp-async-message-queue-server.html</guid></item><item><title>Engineering with Code Ownership</title><link>https://danpalmer.me/2023-03-31-engineering-with-code-ownership/</link><description>Code Ownership is the practice of assigning explicit owners to areas of codebases. Before Google I worked at small companies where it&amp;rsquo;s easy to know who should review each code change, but that doesn&amp;rsquo;t scale far. Even in a team of 10 it wasn&amp;rsquo;t always obvious who knew an area of code the best, and it was certainly less clear for new starters.
Various tools have been developed to help with this.</description><author>Dan Palmer</author><pubDate>Fri, 31 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://danpalmer.me/2023-03-31-engineering-with-code-ownership/</guid></item><item><title>Should you use OpenAI's embeddings? Probably not, and here's why.</title><link>https://iamnotarobot.substack.com/p/should-you-use-openais-embeddings</link><description>Recently semantic search has exploded.</description><author>I Am Not a Robot</author><pubDate>Thu, 30 Mar 2023 22:40:35 GMT</pubDate><guid isPermaLink="true">https://iamnotarobot.substack.com/p/should-you-use-openais-embeddings</guid></item><item><title>Using simple things</title><link>https://blog.bayindirh.io/blog/using-simple-things/</link><description>&lt;p&gt;As I try to move towards minimalism, I discover what it is like in different dimensions.&lt;/p&gt;
&lt;p&gt;When one starts to explore minimalism, it starts outside one's being, with the process of downsizing and keeping that state. Literature targets reduction of physical (and sometimes digital) belongings and presents it as the starting point and the only possible path. For these methods, victory is achieved when nothing else can be removed from peoples' living spaces.&lt;/p&gt;
&lt;p&gt;The promise of removing items from one's life is simple, as I outlined in my previous post, &lt;a href="https://blog.bayindirh.io/blog/meditations-on-minimalism/"&gt;Meditations on minimalism&lt;/a&gt;: Reduced mental load and having more time.&lt;/p&gt;
&lt;p&gt;While these results are correct, directly targeting belongings as a starting point is a shallow perspective on the issue, in my opinion. The process is concerned with items themselves and how the result looks from outside. It's akin to curating your garden for that perfect look to envy your neighbors and pushes the owner and expected utility of the lawn out of the picture, like it doesn't matter.&lt;/p&gt;
&lt;p&gt;However, for most of us, external clutter and build-up is a reflection of our inner state and disorganization. In other words, having a minimal set of belongings or preventing ingress of extra items is not possible without solving the problems causing these things in the first place. Then, the question evolves from "How can I reduce my belongings?" to "What is causing this crowdedness in the first place?".&lt;/p&gt;
&lt;p&gt;I have found out that the biggest contributor to this crowdedness is stress. When the stress level starts to increase in my life, I try to balance it out by adding things which brings joy or postpones this stress until I have strength to deal with it, meaning I play more casual games, tend to procrastinate more by reading articles and watching videos, and by buying things. When I reach to the other end of the stressful period, I find myself with a small mound of clutter, which pushes me back in my journey.&lt;/p&gt;
&lt;p&gt;However, I found another way.&lt;/p&gt;
&lt;p&gt;During these periods between trying to cope with stress and having an easy time, I found out that one of the things I enjoy most is using simple things. The items are I put in this category have common properties. They are high quality items which do their job well, are not over the top neither in appearance or in cost, are not disposable, and are not overly complicated. Like a well made fountain pen or a double edge razor.&lt;/p&gt;
&lt;p&gt;These items make me happy in more than one way. First, these items are built to last a lifetime and beyond when cared correctly. Second, using these items for a long time makes them accumulate memories. Writing an important note, something to remember, or a letter? That pen has seen it all, and will make you remember. Same for your other items you use for a long time. All will store and will make you remember important or significant moments in your life, reminding you what you have gone through.&lt;/p&gt;
&lt;p&gt;And, in these moments, you will feel that you have lived and you're alive. Feeling this is both precious and way more fulfilling than many things in life. It allows you to see beyond the daily routine and peek into your soul, helping you to root better and balance yourself.&lt;/p&gt;
&lt;p&gt;Use simple things, and simplify your soul before trying to simplify your material belongings. Because, minimalism starts inside.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Wed, 29 Mar 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/using-simple-things/</guid></item><item><title>Muv-Luv Unlimited: THE DAY AFTER 03 review</title><link>https://burakku.com/blog/muv-luv-unlimited-the-day-after-03-review/</link><description>&lt;p&gt;&lt;img alt="Muv-Luv Unlimited: THE DAY AFTER 03" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Finally, the explosive finale to THE DAY AFTER series. Except it’s not really the finale – they’re just calling the continuation “Muv-Luv Resonative” instead of “THE DAY AFTER”, but I digress.&lt;/p&gt;
&lt;p&gt;&lt;a href="../muv-luv-unlimited-the-day-after-02-review/"&gt;When I gathered my thoughts about THE DAY AFTER 02&lt;/a&gt;, I described it as "the best that THE DAY AFTER had to offer so far”. This was definitely correct – the best so far, as the best that THE DAY AFTER series has to offer is most definitely THE DAY AFTER 03.&lt;/p&gt;
&lt;p&gt;The story feels packed for a relatively short read – I clocked 7.7 hours to complete it, or 30 minutes less than it took me to finish THE DAY AFTER 01. But unlike the other parts of THE DAY AFTER series, it feels like a significant increase in quality, and I never felt like I had to wait for the good part to begin like I did with the previous entries. It feels to the rest of THE DAY AFTER series what the Muv-Luv Alternative feels like its predecessors, although in condensed form.&lt;/p&gt;
&lt;p&gt;Everything is still fully voiced with good voice acting. I don’t think I saw that many new sprites for returning characters. However, THE DAY AFTER 03 uses the old sprites in a much more animated way that makes everything feel that much more alive. In that aspect too, THE DAY AFTER 03 feels like a significant step up from the previous episodes. So while the old sprites may have not been anything amazing, it still gets a passing grade from me on that department.&lt;/p&gt;
&lt;p&gt;On technical aspects, most stuff is fine. You’ll need a custom controller layout if you plan on using a controller on PC or reading it on a Steam Deck. And it reads just fine on the Steam Deck barring some post-sleep audio crackling that you can resolve by restarting the game. You should also stick to reading THE DAY AFTER 03 on one device – &lt;a href="https://steamcommunity.com/app/789830/discussions/0/3829788562450425023/"&gt;the way this game handles Steam Cloud saves makes switching between devices not impossible but very annoying&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;THE DAY AFTER 03 is a definitive high note to end the series – at least until the fabled Muv-Luv Resonative comes out some fateful day.&lt;/p&gt;</description><author>ブラック</author><pubDate>Tue, 28 Mar 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/muv-luv-unlimited-the-day-after-03-review/</guid></item><item><title>CLI tip 25: get file properties using the stat command</title><link>https://learnbyexample.github.io/tips/cli-tip-25/</link><description>&lt;p&gt;The &lt;code&gt;stat&lt;/code&gt; command is useful to get details like file type, size, inode, permissions, last accessed and modified timestamps, etc. You'll get all of these details by default. The &lt;code&gt;-c&lt;/code&gt; and &lt;code&gt;--printf&lt;/code&gt; options can be used to display only the required details in a particular format.&lt;/p&gt;
&lt;p&gt;Here's an example to get accessed and modified timestamps of a file:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# sample directory and sample file
&lt;/span&gt;&lt;span&gt;$ mkdir stat_examples &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; cd &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$_
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'long\nshot\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; ip.txt
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# %x gives the last accessed timestamp
&lt;/span&gt;&lt;span&gt;$ stat &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;c &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%x&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt; ip.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2023&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;03&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;27 20&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;20&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;55.217530670 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0530
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# modify the file
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple\nbanana\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; ip.txt
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# %y gives the last modified timestamp
&lt;/span&gt;&lt;span&gt;$ stat &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;c &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%y&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt; ip.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2023&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;03&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;27 20&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;21&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;50.298964283 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0530
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's an example with some more file properties:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# %s gives file size in bytes
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# \n is used to insert a newline
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# %i gives the inode value
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: stat --printf='%s\n%i\n' ip.txt
&lt;/span&gt;&lt;span&gt;$ stat &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;c &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$'&lt;/span&gt;&lt;span style="color: #d07711;"&gt;%s\n%i' ip.txt
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;23
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;6438890
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's an example for a linked file:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ ln &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;usr&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;share&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;words words.txt
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# %N gives quoted filenames
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# if input is a link, path it points to is also displayed
&lt;/span&gt;&lt;span&gt;$ stat &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;c &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'%N'&lt;/span&gt;&lt;span&gt; words.txt
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'words.txt' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/usr/share/dict/words'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also pass multiple file arguments:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'#!/bin/bash\n\necho hi\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; hi.sh
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# %s gives file size in bytes
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# %n gives filenames
&lt;/span&gt;&lt;span&gt;$ stat &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;c &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%s %n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt; ip.txt hi.sh
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;23&lt;/span&gt;&lt;span&gt; ip.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;21&lt;/span&gt;&lt;span&gt; hi.sh
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; &lt;img alt="warning" src="/images/warning.svg" /&gt; The &lt;code&gt;stat&lt;/code&gt; command should be preferred instead of parsing &lt;code&gt;ls -l&lt;/code&gt; output for file details. See &lt;a href="https://mywiki.wooledge.org/ParsingLs"&gt;mywiki.wooledge: avoid parsing output of ls&lt;/a&gt; and &lt;a href="https://unix.stackexchange.com/q/128985/109046"&gt;unix.stackexchange: why not parse ls?&lt;/a&gt; for explanation and other alternatives.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/cli-computing"&gt;Linux Command Line Computing&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 28 Mar 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-25/</guid></item><item><title>Slow Travel in Paris: Discovering Substance Cafe</title><link>https://www.philipithomas.com/posts/slow-travel-in-paris-discovering-substance-cafe</link><description>&lt;div class="prose"&gt;
  &lt;div&gt;I just returned from two weeks of "slow travel" in Paris. Instead of visiting museums and attractions, "slow travel" consists of living my everyday life, but in a different city. My trip generally consisted of working during the day, then going out to dinner at night. Every afternoon, I would take a break from work and walk to explore a local coffee shop. Throughout the trip, I visited numerous cafes and had much delicious coffee. But, one place stood out as particularly special - &lt;a href="https://www.substancecafe.com"&gt;Substance Cafe&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;Located in the heart of Paris, Substance Cafe is the brainchild of &lt;a href="https://www.instagram.com/joachimmorceau/"&gt;Joachim&lt;/a&gt;, a competition barista who has transformed his passion for coffee into a unique and captivating experience.&lt;br /&gt;   &lt;br /&gt;I am consistently captivated by independent creators operating at their peak potential. Joachim is one of those people. He flies around the world to participate in coffee competitions, then returns to Substance Cafe as his workshop to train and hone his skills before the next event. He operates the cafe mostly alone, and has worked meticulously to control and optimize every step of coffee-making from the farm to cup. He sources and roasts the beans himself, formulates custom water recipes, and rebuilds his equipment in pursuit of the perfect coffee. At Substance Cafe, guests are offered a front-row seat to Joachim's creative pursuit.&lt;br /&gt;&lt;br /&gt;Substance Cafe is a far cry from your typical neighborhood coffee shop. You won't find people queuing for their morning caffeine fix, as the doors only open at noon. In place of tables, takeaway cups, and sugar, you'll discover 15 stools at a bar arranged around the espresso machine, inviting guests to become an audience to the coffee-making process. While a concise standard menu of familiar cafe drinks is available, the pièce de résistance at Substance Cafe is the Omakase experience.&lt;br /&gt;&lt;br /&gt;Joachim curates a symphony of flavors through a rotating selection of a dozen specialty coffee beans, personally chosen to transport patrons on a sensory tour of the coffee world. This Omakase menu lies at the heart of Substance Cafe's ethos – a celebration of the finest coffees, brewed to highlight the unique terroir of the farm where it was grown. The experience is akin to a tasting menu at Noma, where every course challenges your preconceptions about coffee. While the price of an espresso can reach $20, it's a small investment to partake in an experience that is among the best in the world.&lt;br /&gt;&lt;br /&gt;During my trip, I went to Substance Cafe three times, pulled in by the allure of its exceptional espresso offerings. Although Joachim is equally renowned for his V60 pourover coffee, I focused on espresso - a complex art form I'd never dare to attempt at home. Furthermore, it's a rarity to find cafes that can expertly pull shots from specialty light-roasted beans. Within the walls of Substance Cafe, I experienced the pinnacle of espresso – a Geisha from Finca Deborah in Panama. According to Joachim, this remarkable coffee is the only one he has ever bestowed a perfect 10 out of 10 rating for flavor. Each sip was akin to a kaleidoscopic journey, with a dynamic array of tastes unfurling on my palate – from vibrant orange to delicate jasmine. As the coffee cooled, its floral notes gracefully emerged, adding yet another layer of complexity to this unforgettable espresso experience.&lt;br /&gt;&lt;br /&gt; In an age where remote work and digital interactions dominate our lives, Substance Cafe serves as a refreshing reminder of the power and beauty of purpose-built physical spaces that foster engagement and connection. The cafe itself is a living testament to the artisan's relentless quest for excellence and the enriching experience it offers to its patrons.&lt;/div&gt;&lt;div&gt;I can't help but dwell on the role the Internet plays in Substance Cafe. It enables Joachim to find and connect with niche community of coffee aficionados who share his values and devotion to the craft. These customers are willing to spend $20 on an espresso shot, visit the cafe only on weekday afternoons, and dedicate an hour to appreciating a cup of coffee. In this sense, the story of Substance Cafe serves as a testament to the positive effects of technology, enabling passionate individuals to create unique and meaningful work that resonates with a global audience.&lt;/div&gt;&lt;div&gt;Ultimately, Substance Cafe exemplifies the remarkable potential that lies at the intersection of passion, craftsmanship, and technology. It serves as a beacon of inspiration for creators everywhere, reminding us that in a world that often moves too fast, there is still room for those who dare to slow down and strive for perfection in their craft.&lt;/div&gt;&lt;div&gt;&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdTVBIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--6e469f61e5a2e5753f5597915c925904cdb70baf/IMG_6907.jpeg" /&gt;

&lt;/figure&gt;&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Sat, 25 Mar 2023 20:25:08 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/slow-travel-in-paris-discovering-substance-cafe</guid></item><item><title>Going Github</title><link>https://www.marginalia.nu/log/77-going-github/</link><description>I&amp;rsquo;ve moved Marginalia&amp;rsquo;s sources to Github. Can&amp;rsquo;t pick every battle.
The main reason is I&amp;rsquo;m kind of tired of the amount of spam bots that keep signing up to my Gitea. The juice of self-hosting a public-access git forge, even locked down to prevent arbitrary repo creation, that juice just isn&amp;rsquo;t worth the squeeze.
This is not without some consideration.
To be blunt, I don&amp;rsquo;t like Github. Their use of dark patterns leaves a real nasty after-taste.</description><author>Weblog on marginalia.nu</author><pubDate>Sat, 25 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/77-going-github/</guid></item><item><title>Muv-Luv Unlimited: THE DAY AFTER 02 review</title><link>https://burakku.com/blog/muv-luv-unlimited-the-day-after-02-review/</link><description>&lt;p&gt;&lt;img alt="Muv-Luv Unlimited: THE DAY AFTER 02" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The third instalment of the THE DAY AFTER series, unless you decided to skip THE DAY AFTER 00 for some reason. I read somewhere that THE DAY AFTER 02 was the best yet that the THE DAY AFTER series had to offer. To be honest, I didn’t really believe it for approximately half of the story. It didn’t feel like the story was really progressing, but rather that I was getting a beat down.&lt;/p&gt;
&lt;p&gt;Luckily that changes somewhere around halfway through the story. After that, it definitely picks up. The story gets more stakes and you see a lot more action. THE DAY AFTER series also finally starts adjoining the prologue TDA 00 into the overall storyline. And as the story progresses towards the finale, I felt that it definitely did become the best that THE DAY AFTER had to offer so far.&lt;/p&gt;
&lt;p&gt;On the character level, THE DAY AFTER 02 adds several interesting characters, like Ogami and Komaki. Maybe they won’t be everyone’s favourite characters, but I did find them to make the story more interesting. THE DAY AFTER 02 also continues re-introducing familiar faces. Whereas previous instalments of the series could haul the old-timers around in a car, THE DAY AFTER 02 will need a minibus to carry all of its familiar faces. So it’s also definitely the best that THE DAY AFTER has to offer for fans of the series who want to bridge the gap between Unlimited and THE DAY AFTER.&lt;/p&gt;
&lt;p&gt;On the art and sound direction there isn’t that much new. You still have a bunch of the old sprites and the main characters also get some new sprites. Honestly speaking some of the new sprites didn’t really seem to match well with the old sprites, so it comes out as a bit off-kilter. That being said, not having the new sprites as well would’ve felt a bit like they were cheaping out. Some of the art is still too low resolution for the amounts of zoom they’re asked of as well. Everything is still fully voiced and the performances are good.&lt;/p&gt;
&lt;p&gt;The span of the story gives me a bit of pause though. Steam shows me that it took me 8.2 hours to read through TDA 01 whereas TDA 02 was a 6.7-hour read to completion. That’s a full 90 minutes less reading for the same price point. And there was definitely less effort needed on the art side, since a bunch of the assets could be recycled. But then again, if getting length-parity with TDA 01 would’ve required padding out the beginning of the story, I definitely wouldn’t want it. Yet, it definitely stings the part of the brain that considers value for money, even if the overall story is better than TDA 01.&lt;/p&gt;
&lt;p&gt;All in all, it’s very easy to recommend THE DAY AFTER 02. You’re probably not getting this far into the series without having an investment into it, and despite some of the initial drudgery, you definitely get a payoff at the end.&lt;/p&gt;</description><author>ブラック</author><pubDate>Fri, 24 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/muv-luv-unlimited-the-day-after-02-review/</guid></item><item><title>Search Result Quality For Multiple Terms</title><link>https://www.marginalia.nu/log/76-search-result-quality-for-multiple-terms/</link><description>This is a bit of a follow up to the previous post.
The Grand Code Restructuring [ 2023-03-17 ] Marginalia&amp;rsquo;s search result quality has, for a long while, been pretty good as long as your search query is a single term, but for multiple search terms it&amp;rsquo;s been a bit hit-and-miss. Marginalia was never great at this, but the quality of results in this usage pattern has taken a bit of a dive recently due to a re-write of the index last fall.</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 23 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/76-search-result-quality-for-multiple-terms/</guid></item><item><title>Errors and Zig</title><link>http://notes.eatonphil.com/errors-and-zig.html</link><description>&lt;p&gt;At TigerBeetle these last few weeks I've been doing a mix of
documenting client libraries, writing sample code for client
libraries, and writing integration tests against the sample code.&lt;/p&gt;
&lt;p&gt;The client library documentation is generated with a Zig script. The
sample code is integration tested with a Zig script. A bunch of Zig
scripts.&lt;/p&gt;
&lt;p&gt;It's not the same
&lt;a href="https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/TIGER_STYLE.md"&gt;rigorous&lt;/a&gt;
sort of Zig as the main database. (We're generally more lax about
scripts and test code.)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;And I'm specifically writing this post on my personal blog since my
script code is not under incredible scrutiny.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Furthermore, I'm still new to Zig. Since I'm still learning, there
have been a few things that tripped me up.&lt;/p&gt;
&lt;p&gt;And now that I've written this out, I realize most of my stumbling was
related to errors.&lt;/p&gt;
&lt;h3 id="failure"&gt;Failure&lt;/h3&gt;&lt;p&gt;Lots of things in programs allocate memory. This sounds dumb and
obvious but before programming Zig I did not appreciate how many
operations I'm used to allocate memory. I've previously only
programmed in GC languages that do the allocations behind the scenes.&lt;/p&gt;
&lt;p&gt;Furthermore, memory allocation can fail. Zig makes allocation failures
explicit. So lots of things in Zig code need to handle failure.&lt;/p&gt;
&lt;p&gt;Selectively omitting error handling is not allowed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&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;&amp;quot;std&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArenaAllocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&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="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_allocator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arena&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="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thing&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="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run &lt;code&gt;zig run test.zig&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;test.zig:4:23:&lt;span class="w"&gt; &lt;/span&gt;error:&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;ignored
&lt;span class="w"&gt;    &lt;/span&gt;std.fmt.allocPrint&lt;span class="o"&gt;(&lt;/span&gt;a,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="o"&gt;{})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~
test.zig:4:23:&lt;span class="w"&gt; &lt;/span&gt;note:&lt;span class="w"&gt; &lt;/span&gt;consider&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'try'&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'catch'&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'if'&lt;/span&gt;
referenced&lt;span class="w"&gt; &lt;/span&gt;by:
&lt;span class="w"&gt;    &lt;/span&gt;main:&lt;span class="w"&gt; &lt;/span&gt;test.zig:12:9
&lt;span class="w"&gt;    &lt;/span&gt;callMain:&lt;span class="w"&gt; &lt;/span&gt;/home/phil/vendor/zig-linux-x86_64-0.11.0-dev.2213+515e1c93e/lib/std/start.zig:617:32
&lt;span class="w"&gt;    &lt;/span&gt;remaining&lt;span class="w"&gt; &lt;/span&gt;reference&lt;span class="w"&gt; &lt;/span&gt;traces&lt;span class="w"&gt; &lt;/span&gt;hidden&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'-freference-trace'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;see&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;reference&lt;span class="w"&gt; &lt;/span&gt;traces
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This ends up meaning lots of code like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;do_stuff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Let's assume this is an arena allocator so I don't care about freeing.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Stuff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appendSlice&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="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;first of something&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;one more&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build some string {s}.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;athing&lt;/span&gt;&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;other_stuff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;things... {s}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;blah&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;do_other_stuff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;other_stuff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You have &lt;code&gt;try&lt;/code&gt;-es all over the place.&lt;/p&gt;
&lt;h3 id="limits-of-&amp;lt;code&amp;gt;try&amp;lt;/code&amp;gt;"&gt;Limits of &lt;code&gt;try&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Now I don't have a problem with acknowledging that allocations can
fail. At least outside of scripts. In scripts like I've been writing
though I don't really care.&lt;/p&gt;
&lt;p&gt;Having all of those &lt;code&gt;try&lt;/code&gt;-es is just extra typing all over the place.&lt;/p&gt;
&lt;p&gt;It would be nice if I could have instead done:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;do_stuff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Let's assume this is an arena allocator so I don't care about freeing.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Stuff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appendSlice&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="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;first of something&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;one more&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&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="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build some string {s}.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;athing&lt;/span&gt;&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;other_stuff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;things... {s}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;blah&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;do_other_stuff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;other_stuff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But Zig's &lt;code&gt;try&lt;/code&gt; doesn't work like that. I'm not sure why not. The
Zig developers are sensible so I'm sure there's a good reason.&lt;/p&gt;
&lt;p&gt;Still, are there other options?&lt;/p&gt;
&lt;h3 id="&amp;lt;code&amp;gt;catch-unreachable&amp;lt;/code&amp;gt;"&gt;&lt;code&gt;catch unreachable&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;So the problem isn't just that you have to acknowledge memory
allocation failures but that these failures within every helper
function need to be acknowledged by the caller of the helper
function. Failures infiltrate the entire call tree.&lt;/p&gt;
&lt;p&gt;Now of course these potential failures would exist whether or not Zig
exposed them. So I don't mean to say it's Zig's fault for exposing
them.&lt;/p&gt;
&lt;p&gt;But you can avoid failure handling by instead of &lt;code&gt;try&lt;/code&gt;-ing everything,
mark error conditions as &lt;code&gt;unreachable&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;do_stuff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Let's assume this is an arena allocator so I don't care about freeing.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Stuff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appendSlice&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="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;first of something&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;one more&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&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="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build some string {s}.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;athing&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;other_stuff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;things... {s}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;blah&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;do_other_stuff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;other_stuff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see from the function signature, this function no longer
returns any error at all. But it could possibly panic.&lt;/p&gt;
&lt;p&gt;Now in scripts, for things like memory allocations that can fail, I
actually think it's reasonable to mark allocation failures as
unreachable.&lt;/p&gt;
&lt;p&gt;But I took it a bit further. Using &lt;code&gt;@panic&lt;/code&gt; or &lt;code&gt;unreachable&lt;/code&gt; in
general failure conditions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmds&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChildProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(.{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;self&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="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;@panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Expected command to succeed.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="handling-panics"&gt;Handling panics&lt;/h3&gt;&lt;p&gt;But there are some things that will fail quite frequently (like
running subprocesses or interacting with the filesystem in general).&lt;/p&gt;
&lt;p&gt;Panicing (like what happens if &lt;code&gt;@panic()&lt;/code&gt; &lt;s&gt;or `unreachable`&lt;/s&gt; is hit) in
these situations is all good until you have things that you want to
get cleaned up.&lt;/p&gt;
&lt;p class="note"&gt;
  My &lt;a href="https://matklad.github.io/"&gt;coworker&lt;/a&gt; points out I'm
  wrongly conflating &lt;code&gt;unreachable&lt;/code&gt;
  and &lt;code&gt;@panic()&lt;/code&gt; since depending on the release mode,
  hitting &lt;code&gt;unreachable&lt;/code&gt; is actually undefined behavior
  whereas &lt;code&gt;@panic()&lt;/code&gt; is always a panic.
&lt;/p&gt;&lt;p&gt;Panics don't trigger &lt;code&gt;defer&lt;/code&gt; or &lt;code&gt;errdefer&lt;/code&gt; statements. So if you have
a script that starts a background process or creates a temporary
directory, and if you panic in that script, the script won't be able
to run &lt;code&gt;defer&lt;/code&gt; steps to stop the background process or delete the
temporary directory.&lt;/p&gt;
&lt;p&gt;There are panic handlers in Zig (not yet documented, Ctrl-f for "TODO:
pub fn panic" in the &lt;a href="https://ziglang.org/documentation/master/"&gt;Zig
docs&lt;/a&gt;. But I'd just be
getting further from what seems sensible if I went in that direction.&lt;/p&gt;
&lt;h3 id="zig-errors"&gt;Zig errors&lt;/h3&gt;&lt;p&gt;So I stopped panic-ing everywhere and switched to using real Zig
errors, like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmds&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChildProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(.{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;self&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="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Expected command to succeed.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunCommandFailed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It's pretty sweet. You get to make up a new &lt;code&gt;error&lt;/code&gt; enum wherever
you'd like.&lt;/p&gt;
&lt;p&gt;It is unfortunate you can't (currently) include a payload with the
error return value. There's an &lt;a href="https://github.com/ziglang/zig/issues/2647"&gt;active issue discussing
it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But so far I've been able to work around that, as seen in that example
above, by logging before returning an error. Since most of the time
the payload you want to return is detailed information to provide
context.&lt;/p&gt;
&lt;p&gt;This logging is fine in a CLI application but probably not everything
you'd want in a library. I'm not sure.&lt;/p&gt;
&lt;p&gt;And now without panics, functions that deal with &lt;code&gt;error&lt;/code&gt; enums and
&lt;code&gt;try&lt;/code&gt; work with &lt;code&gt;defer&lt;/code&gt; and &lt;code&gt;errdefer&lt;/code&gt; again! Cleanup of my
background processes and temporary directories happens like I want.&lt;/p&gt;
&lt;h3 id="handling-errors-with-&amp;lt;code&amp;gt;if&amp;lt;/code&amp;gt;"&gt;Handling errors with &lt;code&gt;if&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Ok so now that I'm fully bought into Zig errors there were still a few
more things that tripped me up.&lt;/p&gt;
&lt;p&gt;First is that you can handle errors a few ways. You already saw the
first one with &lt;code&gt;try&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thingThatCouldFail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will cause the function the statement is inside to short-circuit,
returning immediately, if &lt;code&gt;thingThatCouldFail&lt;/code&gt; has an error result.&lt;/p&gt;
&lt;p&gt;But then I wanted to retry a function that could fail in a loop after
handling the error.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SomeType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;somedefault&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thingThatCouldFail&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;good_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;good_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// do something that should fix it for the next time&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But that isn't a real syntax. The Zig docs show an example of how you
can use &lt;code&gt;if&lt;/code&gt; with an &lt;code&gt;error&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;doAThing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parseU64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;doSomethingWithNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Overflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// handle overflow...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// we promise that InvalidChar won't happen (or crash in debug mode if it does)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidChar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unreachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But I don't care about the error at this moment (maybe I should, but I
don't right now).&lt;/p&gt;
&lt;p&gt;So I tried:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SomeType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;somedefault&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thingThatCouldFail&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;good_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;good_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// do something that should fix it for the next time&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But that gives me an obscure type error.&lt;/p&gt;
&lt;p&gt;I was stumped here for a while until I decided to try the whole syntax
in that example. And it turns out that at least the capture part is
necessary at the parser layer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SomeType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;somedefault&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thingThatCouldFail&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;good_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;good_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// do something that should fix it for the next time&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And eventually I guessed an unnamed error variable might also work
without the switch, and that was correct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SomeType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;somedefault&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thingThatCouldFail&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;good_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;good_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// do something that should fix it for the next time&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nice!&lt;/p&gt;
&lt;h3 id="&amp;lt;code&amp;gt;catch&amp;lt;/code&amp;gt;-blocks"&gt;&lt;code&gt;catch&lt;/code&gt; blocks&lt;/h3&gt;&lt;p&gt;One last thing that I was stumbling around with was that when you use
&lt;code&gt;catch&lt;/code&gt; with a function that returns an error or some non-void value,
the catch must "return" a value of the same type as the function.&lt;/p&gt;
&lt;p&gt;The Zig docs show a simple example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parseU64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But I also use &lt;code&gt;catch&lt;/code&gt; with blocks sometimes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parseU64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// do some more complex stuff, maybe log, who knows&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But that won't compile. So the "trick" is to combine Zig's &lt;a href="https://ziglang.org/documentation/master/#Blocks"&gt;named
blocks&lt;/a&gt; with
&lt;code&gt;catch&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parseU64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// do some more complex stuff, maybe log, who knows&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// and then &amp;quot;return&amp;quot; a result&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="contributing-to-zig-docs"&gt;Contributing to Zig docs&lt;/h3&gt;&lt;p&gt;I didn't want to write this post without offering some of my examples
to the docs. While there's a dedicated effort around autodoc, the tool
that builds docs for the standard library, I haven't yet stumbled on
docs for contributing the main Zig docs.&lt;/p&gt;
&lt;p&gt;So I grepped in the Zig repo &lt;code&gt;git grep 'Blocks are expressions.'&lt;/code&gt;, a
phrase that showed up in the HTML docs, and found
&lt;code&gt;doc/langref.html.in&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then someone on the &lt;a href="https://discord.gg/gxsFFjE"&gt;Zig Programming Language
Discord&lt;/a&gt; pointed me at running
&lt;code&gt;zig build docs&lt;/code&gt; in the repo root to generate the HTML.&lt;/p&gt;
&lt;p&gt;And now I've got a &lt;a href="https://github.com/ziglang/zig/pull/15042"&gt;PR up&lt;/a&gt;!
We'll see what folks think.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a new post about error-handling and Zig, as I've been doing a bunch of scripting with Zig recently.&lt;br /&gt;&lt;br /&gt;I stumbled a few times so maybe that will be useful to you. And I was able to turn parts of my stumbling into a potential PR to the Zig docs. 🎉&lt;a href="https://t.co/00RVWpodmd"&gt;https://t.co/00RVWpodmd&lt;/a&gt; &lt;a href="https://t.co/wENSEpj63A"&gt;pic.twitter.com/wENSEpj63A&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1638350047887622145?ref_src=twsrc%5Etfw"&gt;March 22, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 21 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/errors-and-zig.html</guid></item><item><title>Python tip 25: split and partition string methods</title><link>https://learnbyexample.github.io/tips/python-tip-25/</link><description>&lt;p&gt;The &lt;code&gt;split()&lt;/code&gt; method splits a string based on the given substring and returns a &lt;code&gt;list&lt;/code&gt;. By default, whitespace is used for splitting and empty elements are discarded.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;greeting &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'  &lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t\r\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt; have    a  nice &lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\r\v\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt; day  &lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\f\v\r\t\n &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;greeting.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'have'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'nice'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'day'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can split the input based on a specific string literal by passing it as an argument. Here are some examples:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;creatures &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dragon][unicorn][centaur'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;creatures.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;']['&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dragon'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'unicorn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'centaur'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# empty elements will be preserved in this case
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':car::jeep::'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'car'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'jeep'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;maxsplit&lt;/code&gt; argument allows you to restrict the number of times the input string should be split. Use &lt;code&gt;rsplit()&lt;/code&gt; if you want to split from right to left.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# split once
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple-grape-mango-fig'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'-'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;maxsplit&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'grape-mango-fig'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# match the rightmost occurrence
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple-grape-mango-fig'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;rsplit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'-'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;maxsplit&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple-grape-mango'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple-grape-mango-fig'&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;rsplit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'-'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;maxsplit&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple-grape'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'mango'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fig'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;partition()&lt;/code&gt; method will give a &lt;code&gt;tuple&lt;/code&gt; of three elements — portion before the leftmost match, the separator itself and the portion after the split. You can use &lt;code&gt;rpartition()&lt;/code&gt; to match the rightmost occurrence of the separator.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;marks &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'maths:85'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;marks.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;partition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;':'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'maths'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;':'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'85'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# last two elements will be empty if there is no match
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;marks.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;partition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'='&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'maths:85'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# match the rightmost occurrence
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;creatures &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dragon][unicorn][centaur'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;creatures.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;rpartition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;']['&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dragon][unicorn'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;']['&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'centaur'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my &lt;a href="https://learnbyexample.github.io/py_regular_expressions/dot-metacharacter-and-quantifiers.html#resplit"&gt;Understanding Python re(gex)?&lt;/a&gt; ebook to learn about string splitting with regular expressions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 21 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-25/</guid></item><item><title>Flow-Based Programming, a way for AI and humans to develop together</title><link>https://bergie.iki.fi/blog/fbp-ai-human-collaboration/</link><description>&lt;p&gt;I think by now everybody reading this will have seen how the new generation of &lt;a href="https://en.wikipedia.org/wiki/Large_language_model"&gt;Large Language Models&lt;/a&gt; like ChatGPT are able to produce &lt;a href="https://tylerglaiel.substack.com/p/can-gpt-4-actually-write-code"&gt;somewhat useful code&lt;/a&gt;. Like any advance in software development—from IDEs to high-level languages—this has generated some discussion on the future employment prospects in our field.&lt;/p&gt;

&lt;p&gt;This made me think about how these new tools could fit the world of &lt;a href="https://en.wikipedia.org/wiki/Flow-based_programming"&gt;Flow-Based Programming&lt;/a&gt;, a software development technique I’ve been involved with for quite a while. In Flow-Based Programming these is a very strict boundary between reusable “library code” (called &lt;em&gt;Components&lt;/em&gt;) and the “application logic” (called the &lt;em&gt;Graph&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Here’s what the late &lt;a href="https://jpaulm.github.io"&gt;J. Paul Morrison&lt;/a&gt; wrote on the subject in his seminal work, &lt;em&gt;Flow-Based Programming: A New Approach to Application Development&lt;/em&gt; (2010):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Just as in the preparation and consumption of food there are the two roles of cook and diner, in FBP application development there are two distinct roles: the component builder and the component user or application designer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;…The application designer builds applications using already existing components, or where satisfactory ones do not exist s/he will specify a new component, and then see about getting it built.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Remembering that passage made me wonder, could I get one of the LLMs to produce useful &lt;a href="https://noflojs.org"&gt;NoFlo&lt;/a&gt; components? Armed with &lt;a href="https://www.bing.com/new"&gt;New Bing&lt;/a&gt;, I set out to explore.&lt;/p&gt;

&lt;p&gt;&lt;img alt="AI and humans working together" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/b8c302b0-c698-11ed-8b42-09bd596b6d87Robot%20software.png" /&gt;&lt;/p&gt;

&lt;p&gt;The first attempt was specifying a pretty simple component:&lt;/p&gt;

&lt;p&gt;&lt;img alt="New Bing writing a component" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/new-bing-noflo-component.png" /&gt;&lt;/p&gt;

&lt;p&gt;That actually looks quite reasonable! I also tried asking New Bing to make the component less verbose, as well as generating TypeScript and CoffeeScript variants of the same. All seemed to produce workable things! Sure, there might be some tidying to do, but this could remove a lot of the tedium of component creation.&lt;/p&gt;

&lt;p&gt;In addition to this trivial math component I was able to generate some that to call external REST APIs etc. Bing was even able to switch between HTTP libraries as requested.&lt;/p&gt;

&lt;p&gt;What was even cooler was that it actually &lt;em&gt;suggested&lt;/em&gt; to ask it how to &lt;em&gt;test the component&lt;/em&gt;. Doing as I was told, the result was quite astonishing:&lt;/p&gt;

&lt;p&gt;&lt;img alt="New Bing writing fbp-spec tests" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/new-bing-fbp-spec.png" /&gt;&lt;/p&gt;

&lt;p&gt;That’s &lt;a href="https://github.com/flowbased/fbp-spec"&gt;fbp-spec&lt;/a&gt;! The declarative testing tool we came up with! Definitely the nicest way to test NoFlo (or any other FBP framework) components.&lt;/p&gt;

&lt;p&gt;Based on my results, you’ll definitely want to check the generated components and tests before running them. But what you get out is not bad at all.&lt;/p&gt;

&lt;p&gt;I of course also tried to get Bing to produce NoFlo graphs for me. This is where it stumbled quite a bit. Interestingly the results were better in the &lt;a href="https://github.com/flowbased/fbp#language-for-flow-based-programming"&gt;fbp language&lt;/a&gt; than in the JSON graph format. But maybe that even more enforces that the &lt;em&gt;sweet spot would be AI writing components and a human creating the graphs that run those&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img alt="AI and humans working together" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/28a14660-c698-11ed-8b42-09bd596b6d87Robot%20software.png" /&gt;&lt;/p&gt;

&lt;p&gt;As I’m not working at the moment, I don’t have a current use case for this way of collaborating. But I believe this could be a huge productivity booster for any (and especially Flow-Based) application development, and expect to try it in whatever my next gig ends up being.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Illustrations: MidJourney, from prompt &lt;em&gt;Robot software developer working with a software architect. Floating flowcharts in the background&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;</description><author>Henri Bergius</author><pubDate>Mon, 20 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://bergie.iki.fi/blog/fbp-ai-human-collaboration/</guid></item><item><title>Improve your Python regex skills with 75 interactive exercises</title><link>https://learnbyexample.github.io/python-25-days-of-regex/</link><description>&lt;p&gt;&lt;strong&gt;(2023-03-20) Update:&lt;/strong&gt; This &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/PyRegexExercises"&gt;TUI app&lt;/a&gt; covers many more exercises compared to the GUI app discussed below.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Still confused about Python regular expressions? Grow your confidence with &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebook (FREE this month!) and an &lt;a href="https://github.com/learnbyexample/py_regular_expressions/tree/8433b34bd3f03662abac25c754a5ecf871712980/interactive_exercises"&gt;interactive GUI app&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Inspired by &lt;a href="https://adventofcode.com/"&gt;Advent of Code&lt;/a&gt;, I'll also be posting &lt;a href="https://twitter.com/learn_byexample/status/1465998844898918403"&gt;3 challenges per day on twitter&lt;/a&gt; for 25 days.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="free-ebook"&gt;Free ebook&lt;a class="zola-anchor" href="#free-ebook"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My post about the interactive GUI app made it to the &lt;a href="https://news.ycombinator.com/item?id=29391107"&gt;Hacker News front page&lt;/a&gt;. To celebrate, you can get PDF/EPUB versions of my &lt;strong&gt;Understanding Python re(gex)?&lt;/strong&gt; ebook for free using either of the below links. The offer is valid till 31-Dec-2021.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/py_regex"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/py_regex"&gt;Leanpub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Or, you can use the &lt;a href="https://learnbyexample.github.io/py_regular_expressions/"&gt;web version&lt;/a&gt; if you prefer reading the book online.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="interactive-gui-app"&gt;Interactive GUI app&lt;a class="zola-anchor" href="#interactive-gui-app"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Based on the &lt;strong&gt;Understanding Python re(gex)?&lt;/strong&gt; book contents as well as the exercises, I made an &lt;a href="https://github.com/learnbyexample/py_regular_expressions/tree/8433b34bd3f03662abac25c754a5ecf871712980/interactive_exercises"&gt;interactive GUI app&lt;/a&gt; with 75 questions on &lt;code&gt;re.search&lt;/code&gt;, &lt;code&gt;re.sub&lt;/code&gt;, &lt;code&gt;re.split&lt;/code&gt; and &lt;code&gt;re.findall&lt;/code&gt; that'll test your understanding of anchors, alternation, grouping, escaping metacharacters, dot metacharacter, quantifiers, character class, grouping, lookarounds, flags, etc.&lt;/p&gt;
&lt;p&gt;Here's some screenshots:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Python exercise example for re.search" src="/images/python_exercises/search.png" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Python exercise example for re.sub" src="/images/python_exercises/sub.png" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Python exercise example for re.split" src="/images/python_exercises/split.png" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Python exercise example for re.findall" src="/images/python_exercises/findall.png" /&gt;&lt;/p&gt;
&lt;p&gt;And here's a brief demo:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="25-days-of-regex"&gt;25 Days Of Regex&lt;a class="zola-anchor" href="#25-days-of-regex"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If 75 exercises seem daunting to you, consider doing 3 exercises per day. Allocate some time everyday to read the book and complete 3 challenges.&lt;/p&gt;
&lt;p&gt;I'd also be posting &lt;a href="https://twitter.com/learn_byexample/status/1465998844898918403"&gt;3 challenges per day on twitter&lt;/a&gt;, where you'll be able to get help from me and fellow programmers.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Mon, 20 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/python-25-days-of-regex/</guid></item><item><title>Creating Info Manuals And Adding Them Into Emacs</title><link>https://blog.nawaz.org/posts/2023/Mar/creating-info-manuals-and-adding-them-into-emacs/</link><description>&lt;p&gt;Once I got used to browsing Info manuals in Emacs, I wished I didn&amp;#8217;t
have to go look at online docs all the&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;Of all the manuals out there, how many can I get in
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Texinfo"&gt;Info&lt;/a&gt;&amp;nbsp;format?&lt;/p&gt;
&lt;p&gt;I quickly discovered that any document written in
&lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; (which many …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Sun, 19 Mar 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2023/Mar/creating-info-manuals-and-adding-them-into-emacs/</guid></item><item><title>The unique scoreline rabbithole</title><link>https://rjp.is/blogging/posts/2023/03/the-scoreline-rabbithole/</link><description>In which we find Barnsley and Everton are unique. Ish.</description><author>infrequent oscillations</author><pubDate>Sat, 18 Mar 2023 11:11:00 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/03/the-scoreline-rabbithole/</guid></item><item><title>3D Printing Saves A Neighbors Day</title><link>https://miscdotgeek.com/3d-printing-saves-a-neighbors-day/</link><description>&lt;p&gt;When I started off in the 3D Printing world in November of &amp;#8217;22, I knew it would be neat to be able to print trinkets here and there, or maybe a Telescope or two. But to be able to help a neighbor in a bind? I didn&amp;#8217;t expect that. On Tuesday I saw a request &amp;#8230; &lt;/p&gt;
&lt;p&gt;&lt;a class="more-link btn" href="https://miscdotgeek.com/3d-printing-saves-a-neighbors-day/" rel="noopener noreferrer" target="_self"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://miscdotgeek.com/3d-printing-saves-a-neighbors-day/" rel="noopener noreferrer" target="_self"&gt;3D Printing Saves A Neighbors Day&lt;/a&gt; appeared first on &lt;a href="https://miscdotgeek.com" rel="noopener noreferrer" target="_self"&gt;MiscDotGeek&lt;/a&gt;.&lt;/p&gt;</description><author>MiscDotGeek</author><pubDate>Fri, 17 Mar 2023 21:49:00 GMT</pubDate><guid isPermaLink="true">https://miscdotgeek.com/3d-printing-saves-a-neighbors-day/</guid></item><item><title>How to change custom domain on Substack</title><link>https://www.swyx.io/substack-change-domain</link><description>&lt;p&gt;Since September, I've been running my AI newsletter on https://lspace.swyx.io&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Fri, 17 Mar 2023 08:01:45 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/substack-change-domain</guid></item><item><title>The Grand Code Restructuring</title><link>https://www.marginalia.nu/log/75-grand-restructuring/</link><description>In general I don&amp;rsquo;t like to fuss over code, but this is exactly what I&amp;rsquo;ve been doing in preparation of the NLnet funded work. I&amp;rsquo;ve spent the last month restructuring Marginalia&amp;rsquo;s code base. It&amp;rsquo;s not completely done, but I&amp;rsquo;ve made great headway.
Things got the way they got because in general for experimental solo-development projects, I think it makes sense to be fairly tolerant of technical debt.
Since refactoring is something that is extremely difficult to break up into parallel tracks or do in small iterations, the cost of refactoring is effectively multiplied by the number of people that could be working on the code.</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 17 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/75-grand-restructuring/</guid></item><item><title>I Know Who I Am, Bing!</title><link>https://blog.nawaz.org/posts/2023/Mar/i-know-who-i-am-bing/</link><description>&lt;p&gt;Yesterday was the first time I interacted with any of the &lt;span class="caps"&gt;GPT&lt;/span&gt; flavors. I
interacted with it via &lt;a class="reference external" href="https://www.bing.com/new"&gt;Bing Chat&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I think by now many have
&lt;a class="reference external" href="https://simonwillison.net/2023/Feb/15/bing/"&gt;read&lt;/a&gt; about the
humorous, and sometimes ominous responses Bing gives, and are probably
tired of seeing more examples. Still, I want to share my …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Thu, 16 Mar 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2023/Mar/i-know-who-i-am-bing/</guid></item><item><title>Solving a Scraping Problem with Emacs and Org Mode</title><link>https://blog.nawaz.org/posts/2023/Mar/solving-a-scraping-problem-with-emacs-and-org-mode/</link><description>&lt;p&gt;I recently needed to read a detailed summary of the first 20 or so
chapters of &lt;a class="reference external" href="https://www.librarything.com/work/7425"&gt;Bleak House&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I came across this
&lt;a class="reference external" href="https://www.shmoop.com/study-guides/literature/bleak-house/summary"&gt;site&lt;/a&gt;
that fit the bill&amp;nbsp;perfectly.&lt;/p&gt;
&lt;a class="reference external image-reference" href="https://blog.nawaz.org/images/org-scrape/summary_site.png"&gt;
&lt;img alt="Screenshot of the site with the summaries. Each chapter is its own page." src="https://blog.nawaz.org/images/org-scrape/summary_site.png" /&gt;
&lt;/a&gt;
&lt;p&gt;Consistent with my habit of &lt;a class="reference external" href="https://blog.nawaz.org/posts/2021/Dec/consuming-articles-offline/"&gt;printing articles and reading them
offline&lt;/a&gt;,
I wanted to print&amp;nbsp;these.&lt;/p&gt;
&lt;p&gt;This posed several problems. The first …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Thu, 16 Mar 2023 09:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2023/Mar/solving-a-scraping-problem-with-emacs-and-org-mode/</guid></item><item><title>More More More Football Database, part one</title><link>https://rjp.is/blogging/posts/2023/03/more-more-more-football-database/</link><description>In which we update our priors, part one.</description><author>infrequent oscillations</author><pubDate>Tue, 14 Mar 2023 11:14:10 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/03/more-more-more-football-database/</guid></item><item><title>Vim tip 23: editing lines filtered by a pattern</title><link>https://learnbyexample.github.io/tips/vim-tip-23/</link><description>&lt;p&gt;The syntax for &lt;code&gt;g&lt;/code&gt; command (short for &lt;code&gt;global&lt;/code&gt;) is shown below:&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;:[range]g[lobal]/{pattern}/[cmd]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command is used to edit lines that are first filtered based on a &lt;code&gt;searchpattern&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;:g/call/d&lt;/kbd&gt; delete all lines containing &lt;code&gt;call&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;similar to the &lt;code&gt;d&lt;/code&gt; Normal mode command, the deleted contents will be saved to the default &lt;code&gt;&amp;quot;&lt;/code&gt; register&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:g/call/d a&lt;/kbd&gt; in addition to the default register, the deleted content will also be stored in the &lt;code&gt;&amp;quot;a&lt;/code&gt; register&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:g/call/d _&lt;/kbd&gt; deleted content won't be saved anywhere, since it uses the black hole register&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:g/^#/t0&lt;/kbd&gt; copy all lines starting with &lt;code&gt;#&lt;/code&gt; to the start of the file&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:1,5 g/call/d&lt;/kbd&gt; delete all lines containing &lt;code&gt;call&lt;/code&gt; only for the first five lines&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:g/cat/ s/animal/mammal/g&lt;/kbd&gt; replace &lt;code&gt;animal&lt;/code&gt; with &lt;code&gt;mammal&lt;/code&gt; only for the lines containing &lt;code&gt;cat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:.,.+20 g/^#/ normal &amp;gt;&amp;gt;&lt;/kbd&gt; indent the current line and the next &lt;code&gt;20&lt;/code&gt; lines only if the line starts with &lt;code&gt;#&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Note the use of &lt;code&gt;normal&lt;/code&gt; when you need to use Normal mode commands on the filtered lines&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;normal!&lt;/code&gt; if you don't want user defined mappings to be considered&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can use &lt;code&gt;g!&lt;/code&gt; or &lt;code&gt;v&lt;/code&gt; to act on lines &lt;em&gt;not&lt;/em&gt; satisfying the filtering condition.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;:v/jump/d&lt;/kbd&gt; delete all lines &lt;em&gt;not&lt;/em&gt; containing &lt;code&gt;jump&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;same as &lt;kbd&gt;:g!/jump/d&lt;/kbd&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; In addition to the &lt;code&gt;/&lt;/code&gt; delimiter, you can also use any single byte character other than alphabets, &lt;code&gt;\&lt;/code&gt;, &lt;code&gt;&amp;quot;&lt;/code&gt; or &lt;code&gt;|&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://vimhelp.org/repeat.txt.html#%3Aglobal"&gt;:h :g&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 14 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-23/</guid></item><item><title>Muv-Luv Unlimited: THE DAY AFTER 01 review</title><link>https://burakku.com/blog/muv-luv-unlimited-the-day-after-01-review/</link><description>&lt;p&gt;&lt;img alt="Muv-Luv Unlimited: THE DAY AFTER 01" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;THE DAY AFTER 01 continues the story in the Unlimited timeline. Mood is a bit all over the place, ranging from very serious politics discussion on life-and-death matters in a ruined world to Extra-esque slapstick comedy moments where the main character accidentally fondles tits. The most lighthearted moments even come with complementary chibi art. While I found the lighthearted moments of the story fun, I did feel like they dragged on a bit too long. But, much like with THE DAY AFTER 00, towards the end of the game I felt captivated to continue reading. Overall, THE DAY AFTER 01 does definitely deliver a more interesting story than its predecessor.&lt;/p&gt;
&lt;p&gt;Most of the characters in THE DAY AFTER 01 are completely new. The main exception is Marimo-chan, who enjoys a lot of attention here. But those worried by the lack of familiar faces, don’t fret – the new cast is likeable. The main character does definitely have the kind of an idealistic view on world that some might find off-putting, but I didn't find his thinking to be too idealistic. If you’re anything like me, you’ll be cheering on the squad towards the end. I’m looking forwards to exploring more of their future, and their pasts.&lt;/p&gt;
&lt;p&gt;As TDA 01 is roughly double the price of TDA 00, I expected the runtime being roughly double – and it is! However, it’s still quite short, just not as short as TDA 00. I read through TDA 00 in 4.4 hours and TDA 01 was 8.2 hours. However, as the regular price for TDA 01 is about $25, it’s definitely not a value purchase as far as visual novels come. Personally, I’d wait for a sale (and I did back in 2021).&lt;/p&gt;
&lt;p&gt;Art and sound design are mostly good. Character sprites felt better than what TDA 00 had to offer. It also feels like there was less distracting zooming in on backgrounds that really just highlights any kind of artifacts. Either that, or I’ve already gotten used to it. Fully voiced story again with good character voices.&lt;/p&gt;
&lt;p&gt;Translations at times dipped a bit too much on the liberal side for me. Luckily with the full voice acting, you can spot the differences if you have some amount of verbal Japanese knowledge. I also feel like they toned down some of the racist comments used in the story, going from “monkeys” to “Japs”. Not sure if they aimed for that, or if they just thought that the original comments wouldn’t have the wanted impact on English-speaking audiences.&lt;/p&gt;
&lt;p&gt;Technical aspects are mostly fine. I read it from start to finish on my Steam Deck and there’s no blockers there. But you’ll have to use a custom controller layout to have a proper experience, and even then you’ll have to use mouse controls for some elements. Really wish they paid more attention to proper controller support.&lt;/p&gt;
&lt;p&gt;I also observed the same kind of crackling after the VN has been running for a while / being suspended in sleep on my Steam Deck as I did with THE DAY AFTER 00. I feel like switching from the regular Proton to Proton-GE helped to alleviate the crackling issue, so maybe try that. Of course, you can just relaunch the game to get the audio in perfect condition again.&lt;/p&gt;
&lt;p&gt;Overall, THE DAY AFTER 01 is an enjoyable story despite its relatively short length and smaller issues. I imagine I’ll crack open THE DAY AFTER 02 tomorrow to find out what happens after the world ends.&lt;/p&gt;</description><author>ブラック</author><pubDate>Mon, 13 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/muv-luv-unlimited-the-day-after-01-review/</guid></item><item><title>How to implement Go Channels in Java</title><link>https://boyter.org/posts/implement-go-channels-java/</link><description>&lt;p&gt;I have been doing interviews recently, and whenever someone mentions knowing a lot of Go and Java one of the questions I put to them is how to implement Go channels in Java. Since there isn&amp;rsquo;t anything online to suggest how to do this that I liked reading (that I could find) I thought I would write one, and do it in native Java without needing to import a library.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 13 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/implement-go-channels-java/</guid></item><item><title>Ghostwire: Tokyo review</title><link>https://burakku.com/blog/ghostwire-tokyo-review/</link><description>&lt;p&gt;&lt;img alt="Ghostwire: Tokyo" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;First things first: the environmental design in Ghostwire: Tokyo is great. You have an expansive virtual Tokyo to explore, and not just on the ground level, as you also have access sprawling underground systems and rooftops to explore. And not only is the world expansive to explore, but it’s also dressed up in this spooky veneer. And in parts of the game you also get these trippy sequences where the world just goes completely haywire and basically just dances around you. It’s great. The game world is definitely the best part of the game, and I can imagine someone getting their money’s worth just from the eerie Tokyo experience.&lt;/p&gt;
&lt;p&gt;The combat is fine. If one were to pick up this game for the combat, I imagine disappointment would be almost guaranteed. Not that it’s bad, but it’s clearly not where the game shines. You have couple of basic attacks with slightly different behaviours, but at the end of the day, you aim at an enemy and shoot at them until they stop. Stealth mechanics are also not the best that I’ve encountered and there’s a couple of forced stealth segments. Nothing unbearable, but also not my favourite parts of the game.&lt;/p&gt;
&lt;p&gt;There’s some degree of RPG elements in the gameplay, but not enough to actually make it an RPG. You have levels, and gaining a level nets you skill points that you can spend to improve your abilities. However, the combat always feels like the enemies always have the same level as you do, so you’re at least spared from having to grind levels to beat the game. Going for full completion will also net you all of the skills, so there’s no any kind of specialisation, mainly just a matter of deciding which order to acquire them.&lt;/p&gt;
&lt;p&gt;The story is also just fine. The main story definitely isn’t too intricate or captivating and there’s no extensive lore to explore if that’s your thing. Again, nothing terrible here, but clearly this is not a story-first product. The story does luckily manage to hit some good emotional notes along the way. The side quests also don’t have any deep story, but do offer some small slice of life segments featuring ghosts and yokai that I found as quite enjoyable bite-sized experiences. The main sidekick that accompanies you throughout the game is also a fun character, and I enjoyed the interactions between them and the player character.&lt;/p&gt;
&lt;p&gt;And while the game might be tagged as “horror”, I can personally guarantee as a certified coward that the game isn’t actually scary. It’s mostly “creepy”, “spooky” or “eerie”, but not outright scary or horrific. You’re not gonna get jumped on out of the blue or be forced to crawl through dark rooms. It’s definitely light enough of a “horror” experience that I was able to dig into it.&lt;/p&gt;
&lt;p&gt;If you’re a completionist, be prepared: the game is full of collectibles. They want you to go collect landmarks, spirits, yokai, raccoon dogs, statues, shrines, beads, toys, foods, music tracks, outfits, and emotes, as well as complete all of the side quests. It’s actually wild how many different collectibles they put into the game, Shinji Mikami really got sick with Open-Worlditis here. The saving grace for the collectibles is that they’re spread out the amazing landscape, so wondering around for collectibles can be quite scenic. Thankfully there is a button that highlights nearby items to you, so be prepared to use that a lot if you’re one to go for all the achievements.&lt;/p&gt;
&lt;p&gt;If you’re not a completionist, maybe temper your expectations; the main story takes about 10 hours to clear. Completionists will be in it for 30–40 hours, depending on your efficiency and luck. No wonder they added so many side activities, since just 10 hours of content would’ve been brutal for a full-priced title.&lt;/p&gt;
&lt;p&gt;Ghostwire: Tokyo is a beautiful supernatural rendering of Tokyo. While few of the game’s aspect shine bright, I still enjoyed my holiday in Shinji Mikami’s creepy playground.&lt;/p&gt;
&lt;p&gt;Disclaimer: I did not pay a single dime for this game, as I received it as a Christmas gift from a friend.&lt;/p&gt;</description><author>ブラック</author><pubDate>Sat, 11 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/ghostwire-tokyo-review/</guid></item><item><title>Another Football Update</title><link>https://rjp.is/blogging/posts/2023/03/another-football-update/</link><description>In which we find another bug whilst reimporting data.</description><author>infrequent oscillations</author><pubDate>Thu, 09 Mar 2023 00:09:30 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/03/another-football-update/</guid></item><item><title>Football Database Update</title><link>https://rjp.is/blogging/posts/2023/03/football-database-update/</link><description>In which we have to update our schema.</description><author>infrequent oscillations</author><pubDate>Wed, 08 Mar 2023 01:25:57 GMT</pubDate><guid isPermaLink="true">https://rjp.is/blogging/posts/2023/03/football-database-update/</guid></item><item><title>CLI tip 24: inserting file contents one line at a time</title><link>https://learnbyexample.github.io/tips/cli-tip-24/</link><description>&lt;p&gt;The &lt;code&gt;R&lt;/code&gt; command provided by &lt;code&gt;GNU sed&lt;/code&gt; is very similar to &lt;code&gt;r&lt;/code&gt; with respect to most of the rules seen in an earlier &lt;a href="https://learnbyexample.github.io/tips/cli-tip-18/"&gt;tip&lt;/a&gt;. But instead of reading entire file contents, &lt;code&gt;R&lt;/code&gt; will read one line at a time from the source file when the given address matches. If entire file has already been read and another address matches, &lt;code&gt;sed&lt;/code&gt; will proceed as if the line was empty.&lt;/p&gt;
&lt;p&gt;Here's an example:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat ip.txt
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt; sky
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt; apple
&lt;/span&gt;&lt;span&gt;$ cat fav_colors.txt
&lt;/span&gt;&lt;span&gt;deep red
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;reddish
&lt;/span&gt;&lt;span&gt;brown
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# add a line from 'ip.txt'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# whenever a line from 'fav_colors.txt' contains 'red'
&lt;/span&gt;&lt;span&gt;$ sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/red/R ip.txt'&lt;/span&gt;&lt;span&gt; fav_colors.txt
&lt;/span&gt;&lt;span&gt;deep red
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt; sky
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;reddish
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt; apple
&lt;/span&gt;&lt;span&gt;brown
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can combine with other &lt;code&gt;sed&lt;/code&gt; commands to solve various kind of problems. For example, to replace the matching lines:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# empty // will refer to the previously used regex, /red/ in this case
&lt;/span&gt;&lt;span&gt;$ sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/red/R ip.txt' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'//d'&lt;/span&gt;&lt;span&gt; fav_colors.txt
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt; sky
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt; apple
&lt;/span&gt;&lt;span&gt;brown
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, here's how you can interleave contents of two files:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# /dev/stdin will get data from stdin (output of 'seq 4' here)
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: seq 4 | paste -d'\n' fav_colors.txt -
&lt;/span&gt;&lt;span&gt;$ seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'R /dev/stdin'&lt;/span&gt;&lt;span&gt; fav_colors.txt
&lt;/span&gt;&lt;span&gt;deep red
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span&gt;reddish
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3
&lt;/span&gt;&lt;span&gt;brown
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# using 'paste' here will add a newline when stdin runs out of data
&lt;/span&gt;&lt;span&gt;$ seq &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'R /dev/stdin'&lt;/span&gt;&lt;span&gt; fav_colors.txt
&lt;/span&gt;&lt;span&gt;deep red
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;yellow
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span&gt;reddish
&lt;/span&gt;&lt;span&gt;brown
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/learn_gnused"&gt;CLI text processing with GNU sed&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 07 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-24/</guid></item><item><title>Hosting your own Mastodon server</title><link>https://paulstamatiou.com/hosting-your-own-mastodon-server</link><description>&lt;img alt="Mastodon admin settings for discovery" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-mbp.jpg" /&gt;
&lt;p&gt;A lot has happened since I published my article on the &lt;a href="https://paulstamatiou.com/mastodon/"&gt;open source decentralized social networking service Mastodon&lt;/a&gt; a few weeks ago. I saw a lot of interest in and discourse around that post that was great to see. Since then, a few notable things have happened that have caused Mastodon usage to spike yet again.&lt;/p&gt;
&lt;p&gt;Twitter unsurprisingly ended up entirely banning third-party Twitter clients, including hugely popular ones like Tweetbot and Twitterific. I briefly summarized the ebb and flow of being a developer working with the Twitter API in the last post:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Long story short, Twitter made it hard. Hard to even get an API key and get your app approved. Hard to get bugs fixed or new features accessible via the API. Hard to access the data developers wanted. Hard to not hit arbitrary constraints or limits. Things were always changing, rugs were pulled, announcements about new priorities and new rugs were made, only for them to be pulled again (read &lt;a href="https://www.theverge.com/2021/11/15/22779149/twitter-api-version-2-official-decentralized"&gt;this&lt;/a&gt;, &lt;a href="https://techcrunch.com/2022/11/02/twitter-cancels-its-chirp-conference-for-developers-amid-management-transition/"&gt;this&lt;/a&gt; and &lt;a href="https://techcrunch.com/2022/12/15/developer-platforms-are-all-about-trust-and-twitter-lost-it/"&gt;this&lt;/a&gt;)".&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, the final rug was pulled. Fortunately, there was a bit of a silver lining. The folks at Tapbots were able to launch their lovely Mastodon app &lt;a href="https://tapbots.com/ivory/"&gt;Ivory&lt;/a&gt;. The launch went so well that a flood of people took that as an opportunity to take a closer look at Mastodon and join. Mastodon has grown from 500,000 active users last year to now being &lt;a href="https://mastodon.social/@mastodonusercount"&gt;almost at 10 million&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mastodon just keeps getting better. There&amp;#x27;s more authentic conversations and far less linkbait and thread spam from influencers. It feels like a place I&amp;#x27;d prefer spending more of my time to catch up on things.&lt;/p&gt;
&lt;p&gt;With my interest in Mastodon, I recently decided I wanted to take the next step and host my own Mastodon server for my account: &lt;a href="https://stammy.design/@stammy"&gt;@stammy@stammy.design&lt;/a&gt;. In my &lt;a href="https://paulstamatiou.com/mastodon/"&gt;previous post about Mastodon&lt;/a&gt; I provided some details about different routes you could take to self-host Mastodon. Today, I&amp;#x27;ll show you how you can host your own Mastodon instance, step-by-step.&lt;/p&gt;
&lt;div&gt;&lt;h3&gt;Table of contents&lt;/h3&gt;&lt;p&gt;This is a long post and took me more than a month to write, but you don&amp;#x27;t have to go through it all. At a high-level I show two routes to hosting your own Mastodon server: a simple route employing a fully-managed webhost, and a much more comprehensive and advanced route involving a traditional webhost.&lt;/p&gt;&lt;p&gt;I also make sure you&amp;#x27;re good to go with maintenance tips, debugging tips, scripts to backup your database, purge storage and more.&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/#so-you-want-to-host-your-own-mastodon-server"&gt;So you want to host your own Mastodon server?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/#choose-how-youd-like-to-host"&gt;Choose how you&amp;#x27;d like to host&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/#setting-up-mastodon-with-mastohost"&gt;Setting up Mastodon with Masto.host&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/#setting-up-mastodon-with-a-regular-webhost"&gt;Setting up Mastodon with a regular webhost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/#logging-into-mastodon"&gt;Logging into Mastodon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/#migrating-your-account"&gt;Migrating your account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/#what-to-expect"&gt;What to expect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/#maintaining-debugging-and-optimizing-your-server"&gt;Maintaining, debugging, and optimizing your server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/#the-end"&gt;The end&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;h3&gt;So you want to host your own Mastodon server?&lt;/h3&gt;
&lt;p&gt;And why you would want to do host your own Mastodon server? If you&amp;#x27;ve read this far, you probably already have a hunch or two as to why, but here are a few excuses to choose from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You value being in complete control over your Mastodon identity, data, and privacy. You don&amp;#x27;t want to risk it on an instance where you don&amp;#x27;t know the moderators, don&amp;#x27;t want to be under their control, or simply don&amp;#x27;t trust them to maintain the server long-term.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want a speedy Mastodon experience. By controlling your own instance you can set it up with the appropriate resources and hosting location near you to keep Mastodon feeling speedy at all times. That&amp;#x27;s not the case with various instances, especially the larger ones experiencing rapid growth.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want a weekend project, because Mastodon is open source and able to be self-hosted&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You see lots of people running their own instance and it seems cool.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want a better Mastodon user name, one that speaks more to your identity or interests. Run Mastodon on your long-time domain name or an entirely new one, paired with whatever user name you like.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whatever your reason for wanting to host your own Mastodon server, I&amp;#x27;m here to help you get started. I&amp;#x27;ll walk you through the process of setting up your own Mastodon server. I&amp;#x27;ll also provide some tips along the way to help you get the most out of your Mastodon experience.&lt;/p&gt;
&lt;h3&gt;What you&amp;#x27;ll need&lt;/h3&gt;
&lt;p&gt;At a bare minimum, you&amp;#x27;ll need a domain, some money, and some time:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A domain.&lt;/strong&gt; Picking the user name and domain name you wish to use for your new Mastodon instance is probably the hardest part.&lt;/p&gt;
&lt;p&gt;You&amp;#x27;ll need a domain name you can connect with your new Mastodon server. You should also know the basics of how to modify DNS records on it. This varies by domain name registrar but is generally pretty easy to figure out. If you don&amp;#x27;t have a preferred registrar yet, Google Domains is pretty easy and you don&amp;#x27;t have to make a new account with two-factor auth if you&amp;#x27;re already a Google user.&lt;/p&gt;
&lt;p&gt;Otherwise, I also like Amazon AWS Route 53, but it&amp;#x27;s a bit harder to use and they don&amp;#x27;t support nearly as many top-level domains (such as .design, .dev, et cetera) as other registrars.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some money.&lt;/strong&gt; For a single user instance you can get away with $6/mo with the lowest plan on masto.host. but if you have a lot of followers (or just want a beefier server), you&amp;#x27;ll want to upgrade to a bigger plan and that could run you closer to $50/mo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And a few hours.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you opt for a more involved hosting setup you&amp;#x27;ll need access to a few more things, such as an SMTP server to send emails, and optionally, access to a cloud service such as Amazon S3 to host media. I&amp;#x27;ll get into that in a bit.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Choose how you&amp;#x27;d like to host&lt;/h3&gt;
&lt;p&gt;The easy way or the hard(er) way?&lt;/p&gt;
&lt;p&gt;I&amp;#x27;ll show you two different ways to setup your own Mastodon server. For the most part I&amp;#x27;m assuming you are only setting up Mastodon for yourself, or just a few people. This guide is not really meant for someone wanting to setup a Mastodon server that will accomodate hundreds of users as that will take you down a different path.&lt;/p&gt;
&lt;h3&gt;Option 1: Using a dedicated Mastodon host&lt;/h3&gt;
&lt;p&gt;The easiest way to get your own Mastodon server up and running quickly is with a host that specializes in Mastodon hosting. There are &lt;a href="https://docs.joinmastodon.org/user/run-your-own/"&gt;several&lt;/a&gt;, but I&amp;#x27;ll share how to use one of the most popular ones, Masto.host.&lt;/p&gt;
&lt;p&gt;So what exactly do I mean by dedicated host? You won&amp;#x27;t have to worry about the typical setup and maintenance required with a regular host, like configurations, software updates, backups, et cetera. This might be route for you if you want the easiest possible way to get started, and having full server control is not as important to you. You won&amp;#x27;t have complete control over the hardware, the software (beyond Mastodon itself), how your media is stored and served, or anything lower level like that.&lt;/p&gt;
&lt;p&gt;While you won&amp;#x27;t have the ability to tweak things and keep an eye on performance, but it&amp;#x27;s an easy way to get started for cheap. The most affordable Masto.host plan is only $6 per month for up to 20GB of media storage and 2GB database. For a casual single user without a lot of followers this should be okay. Masto.host lets you upgrade to higher plans if you need later on.&lt;/p&gt;
&lt;p&gt;Personally, I did not want to use an option like this. I wanted to be able to serve my media files with a CDN, control the entire server config, as well as just know more about the hardware used in my server (SSD? NVMe SSD? etc). I always want to have significant headroom on my server in case any of my Mastodon posts goes viral. I don&amp;#x27;t want to have a bogged down server limiting the reach of my posts.&lt;/p&gt;
&lt;h3&gt;Option 2: DIY with a regular webhost and server&lt;/h3&gt;
&lt;p&gt;The other option is getting a VPS or dedicated server, setting up Mastodon and its dependencies. While this route takes considerably more time to setup, it&amp;#x27;s the most flexible by far. You control everything and you can eke out more performance and functionality. And some hosting providers will give you a head start with a Linux image with Mastodon pre-installed—there&amp;#x27;s quite a few things to configure but this makes it a bit easier.&lt;/p&gt;
&lt;p&gt;Maybe you decide you want to modify the code to &lt;a href="https://stammy.design/@stammy/109764353001942728"&gt;remove a rate limit&lt;/a&gt;, or change the character limit of posts? Maybe you want to move to a fork of Mastodon, like Hometown. Want to serve your media from a fast CDN on a subdomain that you have backed up? You can do all of that on your own server.&lt;/p&gt;
&lt;p&gt;As expected, it will take longer to setup (a few hours) and it will probably cost more. But if you&amp;#x27;re reading this far, you probably already know your having own server is right for you.&lt;/p&gt;
&lt;h2&gt;Setting up Mastodon with Masto.host&lt;/h2&gt;
&lt;h3&gt;Option 1, the easy way&lt;/h3&gt;
&lt;p&gt;This is by far the easiest way to get started. By the end of this section, you&amp;#x27;ll have your own Mastodon instance running on your own domain. You only need access to your domain name registrar (or DNS provider if use a separate service) so you can change a DNS record.&lt;/p&gt;
&lt;p&gt;You don&amp;#x27;t have to worry about getting access to an SMTP server to send notification emails, nor will you have to figure out how to save media files to a cloud provider.&lt;/p&gt;
&lt;h3&gt;Sign up and change DNS&lt;/h3&gt;
&lt;p&gt;Visit &lt;a href="https://Masto.host"&gt;Masto.host&lt;/a&gt;, choose a plan, and provide your domain name (though you can run it as a subdomain on masto.host if you wish). You&amp;#x27;ll be greeted with a page telling you to add a DNS &lt;code&gt;A&lt;/code&gt; record so your domain name can point to your Masto.host server.&lt;/p&gt;
&lt;img alt="Masto.host DNS setup page" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-setup-masto-dns.jpg" /&gt;
&lt;p&gt;Every domain name registrar / DNS provider has a slightly different way of modifying DNS records, so I can&amp;#x27;t show too much more detail there. You&amp;#x27;ll likely need to log into where you have your domain name registered and look for some DNS or zone settings.&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;When you&amp;#x27;ve successfully added that DNS record, Masto.host should automatically detect it and begin installation.&lt;/p&gt;
&lt;img alt="Masto.host install in progress" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-setup-masto-install.jpg" /&gt;
&lt;p&gt;The installation screen said this process usually took a few minutes. Unfortunately, in my case it actually took a whopping 8 hours. I hope that&amp;#x27;s just temporary due to a temporary spike in new users.&lt;/p&gt;
&lt;h3&gt;Create your Mastodon user &amp; upgrade permissions&lt;/h3&gt;
&lt;p&gt;Once that&amp;#x27;s done you can go right to your new Mastodon instance and create an account. This will be your Mastodon account, so choose the username you want.&lt;/p&gt;
&lt;img alt="Masto.host - New mastodon server setup" height="1310" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-setup-masto-just-installed.jpg" width="1999" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="Masto.host - changing user role to Owner" height="1167" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-setup-masto-change-user-role.jpg" width="2000" /&gt;
&lt;p&gt;After creating your Mastodon account, you&amp;#x27;ll just need to do one more thing. Go back to Masto.host and look for a button called "Change User Role." Here you can upgrade your new account to have "Owner" privileges. Now your account will have access to a new Administration tab in Preferences.&lt;/p&gt;
&lt;h3&gt;That’s it!&lt;/h3&gt;
&lt;p&gt;That&amp;#x27;s pretty much it! There are some things to fill out, and settings to change in Preferences which I go over later in this article, but your server is now online and ready to explore!&lt;/p&gt;
&lt;img alt="Masto.host server detail page" height="1317" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-setup-masto-dashboard.jpg" width="2000" /&gt;
&lt;small&gt;The Masto.host site leaves a lot to be desired. You can only see how much storage your media and database use.&lt;/small&gt;
&lt;p&gt;Aside from that, there&amp;#x27;s not much you can control or change with Masto.host. The site lets you do a few things like download daily backups for your own safe-keeping, change your plan, and opt to install ElasticSearch for an additional monthly fee.&lt;/p&gt;
&lt;p&gt;ElasticSearch lets your Mastodon instance have full-text search for logged in users to search their own posts, bookmarks, favorites, and mentions on your server. I probably would not recommend it if you have the smallest server plan as it can use a good bit of RAM; it&amp;#x27;s unclear how much RAM your Masto.host server has and how much RAM ElasticSearch is configured to use.&lt;/p&gt;
&lt;h3&gt;What you can&amp;#x27;t do&lt;/h3&gt;
&lt;p&gt;There are a few things you won&amp;#x27;t be able to do with a setup like Masto.host where you don&amp;#x27;t have control over the exact configuration. One of these is so-called single user mode. This mode makes the homepage your profile instead of a timeline of users. Typically, you would just update your &lt;code&gt;.env.production&lt;/code&gt; file enable it. Though this is a pretty easy thing for Masto.host to build into their web dashboard as a future setting.&lt;/p&gt;
&lt;p&gt;Similarly, you can&amp;#x27;t do a more advanced username where you could have a username like you@example.com even though Mastodon may actually be hosted on a subdomain like you@social.example.com. This is described in the &lt;a href="https://docs.joinmastodon.org/admin/config/#web_domain"&gt;Mastodon documentation as WEB_DOMAIN&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Setting up Mastodon with a regular webhost&lt;/h2&gt;
&lt;h3&gt;Option 2, the not-so-easy way (DigitalOcean VPS)&lt;/h3&gt;
&lt;p&gt;This section is for people like me that want more control over their server and the software that runs on it, and don&amp;#x27;t mind a few twists and turns to get there. In this section we&amp;#x27;ll pick out server hardware and options, create an account to send emails with SMTP, setup media storage with a cloud provider, follow a setup wizard in the command line and then configure a few things.&lt;/p&gt;
&lt;p&gt;You should be comfortable with Linux and command line basics for this setup process as well as ongoing server maintence.&lt;/p&gt;
&lt;h3&gt;Getting an SMTP server&lt;/h3&gt;
&lt;p&gt;First, we&amp;#x27;ll need to get access to an SMTP server to let Mastodon send email notifications. While this seems like a step that should be optional, it&amp;#x27;s required by Mastodon to send emails used to verify new accounts. There are many ways to get access to an SMTP server:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Setup and configure an MTA like &lt;code&gt;postfix&lt;/code&gt;, &lt;code&gt;exim&lt;/code&gt;, or &lt;code&gt;sendmail&lt;/code&gt;&lt;/strong&gt; on your server. This &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-postfix-as-a-send-only-smtp-server-on-ubuntu-22-04"&gt;is not a particularly easy route&lt;/a&gt;. You may still have deliverability issues, or your &lt;a href="https://www.digitalocean.com/blog/smtp-restricted-by-default"&gt;webhost may restrict SMTP&lt;/a&gt;. It&amp;#x27;s also not advisable to run an SMTP server on the same IP as your web server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use your email provider&amp;#x27;s SMTP server.&lt;/strong&gt; If you use &lt;a href="https://support.google.com/a/answer/176600"&gt;GMail (details linked)&lt;/a&gt;, &lt;a href="https://support.microsoft.com/en-us/office/pop-imap-and-smtp-settings-for-outlook-com-d088b986-291d-42b8-9564-9c414e2aa040"&gt;Outlook&lt;/a&gt;, &lt;a href="https://www.fastmail.help/hc/en-us/articles/1500000279921-IMAP-POP-and-SMTP"&gt;Fastmail&lt;/a&gt; or similar email service providers, you can try to just use their SMTP server. This will require you to provide some credentials (either by generating a unique app-specific password, or your email password), and send from the same email address and domain you currently use with your email.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use a dedicated SMTP provider&lt;/strong&gt; like Amazon AWS SES (Simple Email Service), Mailjet, Resend, SendGrid, Mailgun, SMTP2Go, SparkPost, ZeptoMail, Postmark, and many more. I personally use AWS SES and would recommend it if you&amp;#x27;re already using AWS services in some capacity. Setting up SES is a &lt;a href="https://docs.aws.amazon.com/ses/latest/dg/send-email-getting-started-migrate.html"&gt;bit involved though&lt;/a&gt;, and you&amp;#x27;ll be in a sandbox period at first.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While I personally use AWS SES, I was looking for something even easier to setup for this article and after trying a few, had a great experience getting started with Mailjet. By comparison, trying to setup SendGrid was a disaster; way too many steps involved, including having to talk with support.&lt;/p&gt;
&lt;p&gt;There&amp;#x27;s a few steps to follow before you can get your SMTP server login credentials but Mailjet makes it pretty straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Sign up for a free account at &lt;a href="https://www.mailjet.com/"&gt;Mailjet&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add your domain&lt;/strong&gt; as a new sender domain. Click "Setup my SMTP" (or navigate to Sender domains from Account) to add your domain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Validate your domain&lt;/strong&gt; by adding a TXT DNS record. You&amp;#x27;ll need to log into your DNS provider or domain name registrar to edit that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Authenticate the domain&lt;/strong&gt; by adding 2 TXT DNS records for SPF and DKIM.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Specify the email address you&amp;#x27;ll send from&lt;/strong&gt; and marking it as transactional.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Get your API key and SMTP server URL&lt;/strong&gt;. This can be found under "SMTP and SEND API Settings" on the Account page. When setting up Mastodon, the API key will act as your SMTP username and the API secret will act as your password.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3&gt;Tip: Use your webhost for DNS too&lt;/h3&gt;&lt;p&gt;You may want to just use whichever hosting provider you end up choosing as your DNS provider as well, instead of your domain name registrar (if they differ). Most hosts make it a bit easier to set your &lt;code&gt;A&lt;/code&gt; record that points to your server if they also manage the DNS.&lt;/p&gt;&lt;p&gt;In this article I show how to use DigitalOcean as your hosting provider, so you can skip ahead to that part below or create an account and &lt;!-- --&gt;&lt;a href="https://docs.digitalocean.com/products/networking/dns/how-to/add-domains/"&gt;follow these instructions&lt;/a&gt;&lt;!-- --&gt;.&lt;/p&gt;&lt;/div&gt;
&lt;img alt="" height="1516" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-mailjet-domains.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1205" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-mailjet-domains-dns.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="1162" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-mailjet-sender-address.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="" height="2200" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-mailjet-apikey.jpg" width="2000" /&gt;
&lt;small&gt;Mailjet is one of the easier ways to get access to &lt;/small&gt;
&lt;p&gt;Keep your new SMTP credentials handy, you&amp;#x27;ll need it soon.&lt;/p&gt;
&lt;h3&gt;Picking your webhost&lt;/h3&gt;
&lt;p&gt;You can get a Linux server up and running from any number of hosting providers (like AWS EC2 or Lightsail, Linode, Hetzner, DigitalOcean, Vultr, etc) if you&amp;#x27;re comfortable with the &lt;a href="https://docs.joinmastodon.org/admin/install/"&gt;standard Mastodon installation process&lt;/a&gt;. If you go this route you&amp;#x27;ll need to install various system packages, Ruby, Node, Postgres and nginx among others.&lt;/p&gt;
&lt;p&gt;However, I&amp;#x27;m going to focus on using a DigitalOcean "Droplet" for this article. Mainly because they have a &lt;a href="https://marketplace.digitalocean.com/apps/mastodon"&gt;1-Click Mastodon installer&lt;/a&gt;. It saves you a good bit of time but there&amp;#x27;s still more to do after that I&amp;#x27;ll walk you through.&lt;/p&gt;
&lt;p&gt;Droplets are DigitalOcean&amp;#x27;s name for their Linux-based virtual machines running on virtualized hardware. They have a good variety of hardware to choose from, including options for faster NVMe SSDs which I really appreciate.&lt;/p&gt;
&lt;p&gt;Lets get started! Click "Create Mastodon Droplet" on &lt;a href="https://marketplace.digitalocean.com/apps/mastodon"&gt;the 1-click installer page&lt;/a&gt; and create an account if you don&amp;#x27;t have one yet.&lt;/p&gt;
&lt;h3&gt;Picking your hardware&lt;/h3&gt;
&lt;p&gt;After logging in you&amp;#x27;ll be sent to a page to create your new Mastodon droplet and select what hardware to use. How much CPU, RAM and storage space do you need? Storage doesn&amp;#x27;t really matter as we&amp;#x27;ll be hosting uploaded media on a separate cloud service. As for storage speed, DigitalOcean droplets only offers SSDs and faster NVMe SSDs; no slower mechanical hard drives. You can opt for the NVMe SSD if you wish (I did) but it&amp;#x27;s probably overkill for most people just setting up a single-user personal Mastodon instance.&lt;/p&gt;
&lt;img alt="Creating your DigitalOcean droplet server for Mastodon" height="1124" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-setup-droplet.jpg" width="1000" /&gt;
&lt;small&gt;Creating a DigitalOcean "droplet" server&lt;/small&gt;
&lt;p&gt;With CPU and RAM, it&amp;#x27;s definitely a lot of "the more, the better" and whatever you can afford, but here&amp;#x27;s some additional context to think about it with:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RAM:&lt;/strong&gt; When it comes to RAM, while you could get by with 1GB, I would say you&amp;#x27;d want at least 2GB, especially if you plan to run ElasticSearch on your instance. I ended up going with 4GB and my machine on average consumes about ~2GB of RAM so I have a good amount of headroom.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CPU:&lt;/strong&gt; CPU is critical to several parts of what run Mastodon like Sidekiq, a background job processor, and Puma, the app server on top of nginx. The more CPU resources you have available, the more processes and threads you can spin up without bogging down your system. With the web server for example, if you increase the number of threads you&amp;#x27;ll use more CPU, but if you create more processes, you&amp;#x27;ll use more RAM.&lt;/p&gt;
&lt;p&gt;I picked a server with 2 Intel cores and it has been doing fine with my Mastodon activity with about 4,000 Mastodon followers.&lt;/p&gt;
&lt;div&gt;&lt;h3&gt;Tip: Start small&lt;/h3&gt;&lt;p&gt;With DigitalOcean droplets you can easily upgrade to a more powerful machine, but you may not be able to downgrade to a less powerful machine. This is usually due to having a disk image that&amp;#x27;s too large for another server.&lt;/p&gt;&lt;p&gt;I suggest starting with a server that has 2GB of RAM. If you find you need more RAM, CPU, disk space, et cetera, you easily upgrade later. It takes maybe 5-10 minutes to upgrade and DigitalOcean handles everything for you automatically.&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;After selecting your server hardware, you&amp;#x27;ll want to select the option to enable monitoring and backups. Then, click "Advanced" and enable IPv6. And finally, you&amp;#x27;ll need to create or provide an SSH key. I highly recommend following &lt;a href="https://docs.digitalocean.com/products/droplets/how-to/add-ssh-keys/"&gt;their SSH instructions&lt;/a&gt; so you can use a key that your computer has so you can easily access your server with &lt;a href="https://iterm2.com/"&gt;your preferred terminal app&lt;/a&gt;. You can still use the web console provided by DigitalOcean but it&amp;#x27;s a bit clunky.&lt;/p&gt;
&lt;div&gt;&lt;h3&gt;Option: Managed Database&lt;/h3&gt;&lt;p&gt;I ended up selecting the option to have a managed database for an extra $15 per month. If you don&amp;#x27;t mind the extra cost it&amp;#x27;s nice for it&amp;#x27;s automatic backups that let you revert to any point in time. That being said, for a single user Mastodon instance, you probably won&amp;#x27;t get any performance benefit out of it, just peace of mind. And since it&amp;#x27;s basically another remote server, you can access it with a tool like SQLPro for Postgres (if you&amp;#x27;ve configured its security settings to allow your IP) if you&amp;#x27;re so inclined to poke around in the database.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; If you select the managed database option while creating your Mastodon 1-Click droplet, it should automatically put the correct database credentials for you, but there is one more place where you need to specify the database access credentials: &lt;code&gt;/etc/environment&lt;/code&gt;. If you don&amp;#x27;t do this you may see Mastodon admin CLI commands fail, such as when trying to modify your first Mastodon account to give it owner privileges.&lt;/p&gt;&lt;p&gt;You will also want to verify that &lt;code&gt;/etc/environment&lt;/code&gt; has the correct database name specified. By default it should be &lt;code&gt;mastodon_production&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;And finally, you will need to add the following to &lt;code&gt;/home/mastodon/live/.env.production&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;DB_SSLMODE=require
# You may also need to do the following as a hack
# More context: https://github.com/mastodon/mastodon/discussions/19917
NODE_TLS_REJECT_UNAUTHORIZED=0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And then restart the services &lt;code&gt;sudo systemctl restart mastodon-*&lt;/code&gt;. Without those lines, you may see some errors in the streaming service logs.&lt;/p&gt;&lt;p&gt;And finally, you&amp;#x27;ll want to go to DigitalOcean, click on your managed database, then visit settings to ensure you restrict connections to just your IP and your Mastodon server under the "Trusted sources" section.&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;Click &lt;strong&gt;Create Droplet&lt;/strong&gt; and your server should be ready in about 30 seconds or so.&lt;/p&gt;
&lt;h3&gt;Adding your domain &amp; setting up DNS&lt;/h3&gt;
&lt;p&gt;Next up, we&amp;#x27;ll need to add your domain name to DigitalOcean and setup necessary DNS records.&lt;/p&gt;
&lt;div&gt;&lt;h3&gt;Tip: Using a Mastodon on a domain where you already host a website, without exposing the subdomain&lt;/h3&gt;&lt;p&gt;If you wanted to use a domain where you&amp;#x27;re already hosting a website, you may think that it&amp;#x27;s not possible to host Mastodon on that domain without resorting to a subdomain which would leave you with a lengthy Mastodon username like &lt;code&gt;username@social.example.com&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Fortunately there&amp;#x27;s a a lesser known Mastodon tip that lets you host Mastodon on a subdomain but allows your username to be shown as being directly on the root domain itself, such as &lt;code&gt;username@example.com&lt;/code&gt;. This is done by setting the &lt;code&gt;WEB_DOMAIN&lt;/code&gt; environment variable to the root domain. This is not something that the Mastodon setup wizard will ask you about, but it&amp;#x27;s something you can do after the setup wizard is complete by manually editing the &lt;code&gt;.env.production&lt;/code&gt; file, typically located in the &lt;code&gt;/home/mastodon/live&lt;/code&gt; directory.&lt;/p&gt;&lt;p&gt;This is described in the &lt;!-- --&gt;&lt;a href="https://docs.joinmastodon.org/admin/config/#web_domain"&gt;Mastodon documentation as ,[object Object]&lt;/a&gt;&lt;!-- --&gt; and &lt;code&gt;LOCAL_DOMAIN&lt;/code&gt;. To sum up: with this configuration you can have Mastodon hosted on a subdomain but completely hide that from people that follow you. They&amp;#x27;ll only see your username at your root domain.&lt;/p&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Click the menu on your droplet and select &lt;strong&gt;Add a domain&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Type in the domain you want to use for Mastodon, and select your droplet server from the dropdown.&lt;/li&gt;
&lt;li&gt;If you haven&amp;#x27;t done so already, you&amp;#x27;ll want to go to your domain name registrar and designate DigitalOcean as the DNS host by adding the 3 nameservers shown. More info &lt;a href="https://docs.digitalocean.com/tutorials/dns-registrars/"&gt;can be found here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Finally, you just need to add an &lt;code&gt;A&lt;/code&gt; record for your droplet. Just type &lt;code&gt;@&lt;/code&gt; for the Hostname and click the dropdown to select your droplet to automatically select the IP address needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;img alt="Add domain to DigitalOcean" height="1312" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-server-setup-add-domain.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="Adding your domain to digitalocean" height="725" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-server-setup-add-domain-2.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="Adding DNS A record in DigitalOcean" height="610" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-server-setup-add-domain-record.jpg" width="2000" /&gt;
&lt;p&gt;After DNS propagates in a few moments you&amp;#x27;ll be able to directly SSH into your server with just &lt;code&gt;ssh root@yourdomain.com&lt;/code&gt;, assuming you setup your SSH key earlier.&lt;/p&gt;
&lt;h3&gt;Setting up cloud file storage&lt;/h3&gt;
&lt;p&gt;Before we can start setting up Mastodon, there&amp;#x27;s one more thing we need to do: setup and get login credentials for cloud object storage for all user-uploaded files on your Mastodon instance. This part is optional: Mastodon can easily host files directly from your server.&lt;/p&gt;
&lt;p&gt;However, hosting user-uploaded files on a cloud service instead of your Mastodon server has some benefits. First, it takes the load off your web server to respond to requests for media, freeing up your server to respond to other requests. This is especially important if you have a less powerful server. It&amp;#x27;s an easy way to make things more performant. Second, cloud object storage providers are made for serving static files quickly and that&amp;#x27;s especially true if you use one with a CDN.&lt;/p&gt;
&lt;p&gt;There are a lot of services to choose from such as DigitalOcean Spaces, Wasabi, Minio, Google Cloud Storage, Amazon S3, and any service that offers S3-compatibility like &lt;a href="https://developers.cloudflare.com/r2/examples/mastodon/"&gt;Cloudflare R2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I personally use Amazon S3 with a CloudFront distribution (CDN), used on a CNAME subdomain. It was a bit more involved to setup, including creating separate IAM and bucket permissions, so I wanted to show a simpler solution for this article, using DigitalOcean Spaces. If you&amp;#x27;re familiar with AWS, that&amp;#x27;s a great route to consider though.&lt;/p&gt;
&lt;p&gt;The easiest way is to use DigitalOcean&amp;#x27;s own object storage solution, Spaces. It costs $5/month for 250GB of storage with 1TB of outbound transfer. That should be more than enough space for most.&lt;/p&gt;
&lt;p&gt;To get started, click the &lt;strong&gt;Create&lt;/strong&gt; button at the top of DigialOcean&amp;#x27;s dashboard and select Spaces to create a bucket for storing your Mastodon server&amp;#x27;s files. Here you&amp;#x27;ll need to pick a bucket name and location, which should default to the same region as your droplet server. The bucket name doesn&amp;#x27;t matter much as it&amp;#x27;s not readily visible to your users unless they dig into their browser web inspector. You&amp;#x27;ll also want to check the box to enable CDN.&lt;/p&gt;
&lt;img alt="Creating DigialOcean Spaces bucket" height="1611" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-create-spaces-bucket.jpg" width="2000" /&gt;
&lt;p&gt;After you create the bucket you&amp;#x27;ll need to specify a subdomain to access your files from and setup an SSL certificate so HTTPS will work. DigitalOcean makes this pretty easy. Go to the &lt;strong&gt;Settings&lt;/strong&gt; tab of your new Spaces bucket and click &lt;strong&gt;Change → Edit CDN Settings&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Select a subdomain name that you&amp;#x27;ll use for hosting user-uploaded media on your Mastodon server, like &lt;code&gt;files.yourdomain.com&lt;/code&gt;. While this is optional, it&amp;#x27;s a nice touch. After the certificate is created, the next step is to just select it from the dropdown on the Settings page under CDN as shown below. If you&amp;#x27;re lost at any point during this process, more &lt;a href="https://docs.digitalocean.com/products/spaces/how-to/enable-cdn/"&gt;details can be found here&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="Edit CDN Settings" height="1000" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-create-spaces-bucket-cdn.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="Create subdomain for Spaces CDN and setup SSL certificate" height="1762" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-create-spaces-bucket-cdn-subdomain.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="Configure CDN" height="1569" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-create-spaces-bucket-cdn-subdomain-config.jpg" width="2000" /&gt;
&lt;p&gt;The final step is getting the API key and secret for your new bucket. On the main Spaces page (click Spaces from the sidebar) there is a link at the top right titled &lt;strong&gt;Manage keys&lt;/strong&gt;. Click that and then you can generate a new key and give it a name. Once you&amp;#x27;re done with that copy your key and secret for use later.&lt;/p&gt;
&lt;h3&gt;Setting up Mastodon&lt;/h3&gt;
&lt;p&gt;By this point your DigitalOcean project should look like this: you have a droplet server, a Spaces bucket, and an attached domain.&lt;/p&gt;
&lt;img alt="DigitalOcean Droplet and project dashboard" height="1348" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-server-setup.jpg" width="2000" /&gt;
&lt;p&gt;You&amp;#x27;re ready to begin the actual Mastodon setup! Mastodon is already installed thanks to the 1-Click Droplet installer. You just need to access the setup wizard via the command line. You can use the built-in console on the website or SSH into it with your preferred terminal app. The website console can be accessed by selecting &lt;strong&gt;Access console&lt;/strong&gt; from the &lt;strong&gt;...&lt;/strong&gt; menu on your droplet. I prefer directly SSHing into the server with &lt;code&gt;ssh root@yourdomain.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When you&amp;#x27;re connected to your server, you&amp;#x27;ll instantly see the Mastodon setup wizard start to run:&lt;/p&gt;
&lt;img alt="Setting up Mastodon in the command line" height="1217" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-mastodon-setup-wizard.jpg" width="2000" /&gt;
&lt;small&gt;The Mastodon setup wizard&lt;/small&gt;
&lt;div&gt;&lt;h3&gt;Tip: If you make a mistake...&lt;/h3&gt;&lt;p&gt;If at any point you make a mistake while working in the command line setup wizard and need to undo something, just close the SSH session or web console, and re-open it. The setup process will start over again until it&amp;#x27;s completed.&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;You&amp;#x27;ll be asked to answer a few questions and fill in some details and credentials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Domain name:&lt;/strong&gt; Your domain name for Mastodon.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Do you want to store user-uploaded files on the cloud? (y/N):&lt;/strong&gt; Type &lt;code&gt;y&lt;/code&gt; for yes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Provider:&lt;/strong&gt; Select DigitalOcean Spaces.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Space name:&lt;/strong&gt; The bucket name you chose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Space region:&lt;/strong&gt; Type in the region for your spaces bucket. When you go to copy your Spaces endpoint, it&amp;#x27;ll be the subdomain after your bucket name that&amp;#x27;s short (like &lt;code&gt;nyc3&lt;/code&gt;) depending on the hosting location.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Space endpoint:&lt;/strong&gt; Your Spaces "Origin Endpoint" as seen on the DigitalOcean website &lt;em&gt;but with the bucket name subdomain at the front removed&lt;/em&gt;. Even though you setup the CDN, for this you need the non-CDN URL or it won&amp;#x27;t work. For me it was the following but this varies based on the location you chose:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://nyc3.digitaloceanspaces.com
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Do you want to access the uploaded files from your own domain?&lt;/strong&gt;: This is asking if you want to use a CNAME alias for your hosted files, for example to appear as if they are hosted from your domain. I&amp;#x27;ll assume you followed the directions above for setting up Spaces CDN with a subdomain and certificate, so you can say &lt;code&gt;yes&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SMTP server:&lt;/strong&gt; Check with your SMTP provider, but in the case of Mailjet it was &lt;code&gt;in-v3.mailjet.com&lt;/code&gt; for me. This could be different for you.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SMTP port:&lt;/strong&gt; Depends on your SMTP provider, but likely &lt;code&gt;587&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SMTP username / password:&lt;/strong&gt; Provide the username and password for your SMTP provider. If you&amp;#x27;re using Mailjet as described above the API key will act as your SMTP username and the API secret will act as your password.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SMTP authentication:&lt;/strong&gt; The default &lt;code&gt;plain&lt;/code&gt; should be fine. You may also be asked for an OpenSSL verify mode, for that you can usually input &lt;code&gt;peer&lt;/code&gt; or &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Email address to send emails "from":&lt;/strong&gt; Hit enter to accept the default &lt;code&gt;notifications@yourdomain.com&lt;/code&gt; address.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It is time to create an admin account that you&amp;#x27;ll be able to use from the browser! Username:&lt;/strong&gt; Pick the username that you&amp;#x27;ll want to use for your Mastodon profile.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your full Mastodon handle will end up being &lt;code&gt;@yourusername@yourdomain.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At this point you&amp;#x27;ll be given a password to log into your new Mastodon account. You&amp;#x27;ll want to change it to something stronger after you log in for the first time (more on that later). But before that can be done you&amp;#x27;ll need to follow the instructions to setup your SSL certificate with Let&amp;#x27;s Encrypt so https will work for your domain.&lt;/p&gt;
&lt;p&gt;If you find yourself struggling to get the Let&amp;#x27;s Encrypt process to work, check the DNS records on your domain to ensure everything is setup correctly, particularly the &lt;code&gt;A&lt;/code&gt; record for the domain and that you&amp;#x27;ve correctly added the DigitalOcean nameservers with your registrar. Also, you can just give it some time for DNS to propagate and try again later.&lt;/p&gt;
&lt;p&gt;There&amp;#x27;s just a few more things to do before logging into your new Mastodon server.&lt;/p&gt;
&lt;h3&gt;Fixing the cloud storage config&lt;/h3&gt;
&lt;p&gt;Unfortunately I was unable to get DigitalOcean Spaces cloud object storage with CDN working flawlessly out of the gate and had to manually edit a few lines in a config file. We&amp;#x27;ll be editing the &lt;code&gt;.env.production&lt;/code&gt; file—where a lot of Mastodon settings live— to adjust some of the media storage settings.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If you&amp;#x27;re using the subdomain as shown during Spaces setup:&lt;/strong&gt;
You&amp;#x27;ll need to add a line for &lt;code&gt;S3_ALIAS_HOST&lt;/code&gt; as shown below. This value is the same as the subdomain you setup earlier along with the SSL certificate. Then you&amp;#x27;ll need to modify the S3_ENDPOINT and S3_HOSTNAME values to be in this format.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;S3_PROTOCOL=https
# Replace the following with your bucket name 
S3_BUCKET=stammy-mastodon
# Replace with your bucket region
S3_REGION=nyc3
# Replace with the subdomain you setup earlier for your Spaces bucket / CDN
S3_ALIAS_HOST=files.stammy.social
# Change the region in the URL below to match your bucket region
S3_ENDPOINT=https://nyc3.digitaloceanspaces.com
# This is your "Origin Endpoint" as found on your Spaces bucket page
S3_HOSTNAME=stammy-mastodon.nyc3.digitaloceanspaces.com
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If you&amp;#x27;re not using a subdomain alias for hosting your files:&lt;/strong&gt; You will need to remove the bucket name from your DigitalOcean Spaces bucket&amp;#x27;s "Origin Endpoint" that is used as your hostname. Ensure your S3_ENDPOINT and S3_HOSTNAME look like this. Remove any line with S3_ALIAS_HOST if it already exists.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;S3_ENABLED=true
S3_PROTOCOL=https
S3_BUCKET=stammy-mastodon
# Replace nyc3 region in the next 3 lines with your bucket region
S3_REGION=nyc3
S3_HOSTNAME=nyc3.digitaloceanspaces.com
S3_ENDPOINT=https://nyc3.digitaloceanspaces.com
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If you used Amazon S3 with CloudFront instead:&lt;/strong&gt; If you went for the more advanced route of using S3 with a CloudFront distribution connected to a CNAME on your domain, you will configure it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Your S3 bucket name. 
S3_BUCKET=my-mastodon-files
# Replace us-east-1 with your bucket region, lowercase.
S3_REGION=us-east-1
# Hostname is likely your bucket name + .s3.amazonaws.com
S3_HOSTNAME=my-mastodon-files.s3.amazonaws.com
# Only use if you&amp;#x27;ve configured a CloudFront CNAME on your domain with SSL cert 
# If you&amp;#x27;re not using a CNAME, remove this line and then change the S3_HOSTNAME to the default (such as s3.us-east-1.amazonaws.com based on your region)
S3_ALIAS_HOST=turbo.stammy.design
# You do not need an S3_ENDPOINT line
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you&amp;#x27;ve made the changes to your &lt;code&gt;.env.production&lt;/code&gt; file, you&amp;#x27;ll need to restart the Mastodon services to apply the changes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart mastodon-*.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Upgrading your Mastodon account privileges&lt;/h3&gt;
&lt;p&gt;By default your new Mastodon account does not have all privileges available. Mastodon allows an account to have roles like Moderator, Admin, and Owner. You&amp;#x27;ll want to modify your account to have the Owner role. This can be done using the admin CLI tool called &lt;a href="https://docs.joinmastodon.org/admin/tootctl/"&gt;tootctl&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Connect to your server via SSH again (or DigitalOcean&amp;#x27;s web console) and run the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Change from the root user to the mastodon user 
sudo su - mastodon 
# Change to the Mastodon bin directory 
cd /home/mastodon/live/bin
# Modify your account to have the Owner role
RAILS_ENV=production ./tootctl accounts modify admin --role Owner
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This may take a few seconds then you should see "OK". If you have any issues, or are using a different webhost, just confirm that you&amp;#x27;re in the right directory. If you have an error running this and happen to be using the DigitalOcean managed database, you may need to configure the database in another file. Read the &lt;a href="#managed-db"&gt;managed database&lt;/a&gt; section for details.&lt;/p&gt;
&lt;h3&gt;Enabling single user mode&lt;/h3&gt;
&lt;p&gt;If this Mastodon account is for you and you only, you can enable single user mode to have the Mastodon homepage be a profile view of your account. By default, this is not enabled and the Mastodon homepage is a timeline view.&lt;/p&gt;
&lt;p&gt;To enable this, you&amp;#x27;ll need to edit &lt;code&gt;.env.production&lt;/code&gt; and then restart the web server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Change from the root user to the mastodon user 
sudo su - mastodon 
# Change to the Mastodon web directory 
cd /home/mastodon/live
# Edit the .env.production file in Vim
vim .env.production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you&amp;#x27;ll need to create a new line anywhere in the file by moving the cursor with the arrow keys, tapping i to enter Insert mode, creating a new line, then typing in the following line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SINGLE_USER_MODE=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tap ESC to exit insert mode, and then type :wq and hit enter to save and exit vim. And finally, exit mastodon user mode to go back to root by typing &lt;code&gt;exit&lt;/code&gt;, and then restart the web server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Restart the web server, this can take a few seconds
systemctl restart mastodon-web.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Ensuring your SSL cert will auto-renew without issue&lt;/h3&gt;
&lt;p&gt;Mastodon uses the popular certbot tool from the EFF to obtain certs from Let&amp;#x27;s Encrypt and enable HTTPS on your server. That part all worked great during the initial setup. Certificates from Let&amp;#x27;s Encrypt last for 90 days before they need to be renewed. Certbot makes them easy to auto-renew: it should have installed a cron with it either in a systemd &lt;code&gt;certbot.timer&lt;/code&gt; (see it with &lt;code&gt;systemctl show certbot.timer&lt;/code&gt; if you&amp;#x27;re curious) or in &lt;code&gt;/etc/cron.d/certbot&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You&amp;#x27;ll need to ensure that your cert will auto-renew without issue. Shouldn&amp;#x27;t this work out of the box? It should, yes. However, I began poking around in my setup and noticed a configuration issue by default (not sure if this is a DigitalOcean Mastodon installer issue or a Mastodon issue) that would lead to renewal failing.&lt;/p&gt;
&lt;p&gt;The default method used is called standalone mode and it only works when the web server is not running, which won&amp;#x27;t happen during auto-renewal unless you setup your own cron that also stops and starts the server for renewal. This is fine for the initial setup, but if you want to auto-renew your cert, you&amp;#x27;ll need to use a different method. For more context on this issue, see this commit has &lt;a href="https://github.com/mastodon/documentation/pull/1036/commits/9bc371f3547a884aa094a1151e813f678bd0623e"&gt;updated installation instructions&lt;/a&gt; that were never merged to Mastodon main, and &lt;a href="https://github.com/mastodon/documentation/issues/940"&gt;this issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, see if you&amp;#x27;ll even have an issue by doing a dry run invocation of the certbot renew command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo certbot renew --dry-run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the dry run says the renewal would be successful, you don&amp;#x27;t need to do anything else and your setup is all good. If it says renewal will fail, you likely need to do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Edit this config file, swapping out example.com for your domain 
vim /etc/letsencrypt/renewal/example.com.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then find the line that says &lt;code&gt;authenticator = standalone&lt;/code&gt; and replace it with these two lines below and save the file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;authenticator = nginx
installer = nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can do a dry run again and it should say the renewal would be successful as shown below:&lt;/p&gt;
&lt;img alt="Terminal showing a dry run of certbot renew for SSL cert" height="1039" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-certbot-renew-dryrun.png" width="2000" /&gt;
&lt;h3&gt;Final setup: updating packages, enabling the firewall, etc..&lt;/h3&gt;
&lt;p&gt;At this point you could skip ahead and check that Mastodon works and log into your account for the first time, but you&amp;#x27;ll need to come back to complete a few more things. For the most part you just need to &lt;a href="https://docs.joinmastodon.org/admin/prerequisites/#do-not-allow-password-based-ssh-login-keys-only"&gt;follow this Mastodon guide&lt;/a&gt; where you&amp;#x27;ll update system packages, configure fail2ban to block repeated login attempts and setup a firewall.&lt;/p&gt;
&lt;p&gt;The DigitalOcean website has its own interface for configuring a firewall but after repeated attempts I was unable to get it working, so I stuck with the route recommended by Mastodon: iptables.&lt;/p&gt;
&lt;h3&gt;Optional: Configure IPv6&lt;/h3&gt;
&lt;p&gt;During DigitalOcean droplet setup you checked a box to enable IPv6 connectivity for your server. Even though that is enabled, you will need to edit some files and add a DNS record. Follow &lt;a href="https://docs.digitalocean.com/products/networking/ipv6/how-to/enable/#on-existing-droplets"&gt;this DigitalOcean guide.&lt;/a&gt;&lt;/p&gt;
&lt;img alt="DigitalOcean adding AAAA record for IPv6" height="504" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-digitalocean-ipv6-aaaa.png" width="2000" /&gt;
&lt;p&gt;Then you&amp;#x27;ll need to add an &lt;code&gt;AAAA&lt;/code&gt; DNS record to your domain. An &lt;code&gt;AAAA&lt;/code&gt; record is like an &lt;code&gt;A&lt;/code&gt; record that points to your server, but for IPv6. DigitalOcean should automatically show you your server&amp;#x27;s IPv6 address in a dropdown when creating that record.&lt;/p&gt;
&lt;h3&gt;Optional: Setting up automatic database backups&lt;/h3&gt;
&lt;p&gt;If you opted not to go for the DigitalOcean managed database upgrade, you&amp;#x27;ll want to a way to export and save backups of your database on a recurring basis. The easiest way to do this is to setup a cronjob that runs a script to dump your database, zip it up, and save it to a cloud storage bucket with the Amazon AWS command line interface. Since we already setup Spaces for cloud object storage, we can re-use that to save your database backups, though if you want to spend the extra time to use a separate cloud provider or bucket for saving only backups, that is preferrable.&lt;/p&gt;
&lt;p&gt;First, you&amp;#x27;ll need to install the AWS CLI. You could follow &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html#cliv2-linux-install"&gt;this guide&lt;/a&gt; to install the AWS CLI, but likely you can just install it with: &lt;code&gt;apt&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Run as root 
apt install awscli
# Provide your credentials for your Spaces bucket, api key and secret when prompted 
aws configure 
# Switch to the Mastodon user
sudo su - mastodon 
# Test to see that it works by listing what&amp;#x27;s in your Spaces bucket 
# Note: you need to replace the region with your region if different 
# Any time you interact with the AWS CLI, you need to use the --endpoint-url option to 
# specify that we are using DigitalOcean Spaces, not Amazon S3
aws --endpoint-url https://nyc3.digitaloceanspaces.com s3 ls

cd /home/mastodon

# Now we&amp;#x27;ll create the script to backup the database
vim backup_database.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tap i to enter Insert mode in vim, and paste the following, replacing the Spaces region in the endpoint URL with your Spaces region code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;#!/bin/bash

# Create a filename with the date and time
FILENAME=mastodon_production_$(date +%Y%m%d%H%M).gz

# Dump the database, gzip it, and save locally 
pg_dump mastodon_production | gzip &gt; ${FILENAME}

# Upload the file to your Spaces bucket
# Important: Replace the region with your region if different
# Important: Replace the bucket name &amp;#x27;stammy-mastodon&amp;#x27; with your bucket name
aws --endpoint-url https://nyc3.digitaloceanspaces.com s3 cp ${FILENAME} s3://stammy-mastodon/backups/

# Delete the local file
rm ${FILENAME}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save the script (Tap ESC to exit insert mode, type :wq, hit Enter), then made it executable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x backup_database.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we&amp;#x27;ll create a cronjob to run the script every day:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Enter cron job editor. If you&amp;#x27;re asked what editor to use, select vim again.
crontab -e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a new line after the comment block at the time, and enter the following to create a database backup every week. If you want more frequent backups, you can customize it to your liking, such as with &lt;code&gt;0 0 */3 * *&lt;/code&gt; for every 3 days. Take a look at &lt;a href="https://crontab.guru/"&gt;crontab.guru&lt;/a&gt; to help you figure out what values to input.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Backup every week
@weekly ./backup_database.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;img alt="Configuring a cronjob to backup the database" height="1309" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-autobackup-database.png" width="2000" /&gt;
&lt;p&gt;Save the file and you should be good. You may want to try manually running the script now (&lt;code&gt;./backup_database.sh&lt;/code&gt;) to ensure it works, and check your Spaces bucket to see a new file was added to the backups directory.&lt;/p&gt;
&lt;h3&gt;Optional: Install ElasticSearch&lt;/h3&gt;
&lt;p&gt;If you want the ability for you and your Mastodon instance users to be able to search their own posts, bookmarks, favorites, and mentions, you&amp;#x27;ll need to install ElasticSearch. This is optional because it&amp;#x27;s really not that useful. You cannot search across instances, and it does not even support showing posts from other people on other servers that your server has seen but that do not mention you, even if you follow them.&lt;/p&gt;
&lt;p&gt;It also takes a bit of RAM, so I especially would not recommend install ElasticSearch if you only have 2GB of RAM or less server. It can be done, but I don&amp;#x27;t think it&amp;#x27;s worth it.&lt;/p&gt;
&lt;img alt="What mastodon search looks like without elastic search installed" height="1263" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-elastic-search-off.jpg" width="2000" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="Mastodon with elastic search installed, showing search results" height="1901" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-elastic-search-on.jpg" width="2000" /&gt;
&lt;small&gt;&lt;strong&gt;Left:&lt;/strong&gt; Mastodon search without ES installed. &lt;strong&gt;Right:&lt;/strong&gt; With ES installed, showing local results for people, hashtags and posts, including your own.&lt;/small&gt;
&lt;p&gt;To install ElasticSearch, you can &lt;a href="https://docs.joinmastodon.org/admin/optional/elasticsearch/"&gt;follow these instructions from the Mastodon site&lt;/a&gt;. However, when you get to the part of the guide that tries to enable the service, you may see this error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Job for elasticsearch.service failed because a fatal signal was delivered to the control processed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is likely to happen if your machine doesn&amp;#x27;t have enough RAM. By default ElasticSearch tries to take a lot of RAM, so you&amp;#x27;ll want to configure it to use less. For example, if you only wanted it to use 512MB of RAM, you can create a new file &lt;code&gt;memory.options&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Create an elastic search config file to limit memory usage
vim /etc/elasticsearch/jvm.options.d/memory.options
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then enter these two lines to specify a min and max amount of RAM that ElasticSearch can use. For example, a min of 256MB and max of 512MB:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-Xms256m
-Xmx512m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save the file and try to start the service again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
systemctl enable --now elasticsearch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the rest of the steps mentioned in the guide and you should have functional search on your instance. You may run into one more issue resulting in a security warning. This &lt;a href="https://github.com/mastodon/mastodon/issues/18625"&gt;github thread&lt;/a&gt; is helpful for resolving that (though I think it was also fixed recently in Mastodon 4.1.0).If you want to go down the rabbithole you can modify &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html"&gt;more security settings&lt;/a&gt; but it&amp;#x27;s likely not a huge deal as we&amp;#x27;ll be setting up a firewall that won&amp;#x27;t allow port access to ElasticSearch.&lt;/p&gt;
&lt;h2&gt;Logging into Mastodon&lt;/h2&gt;
&lt;h3&gt;Changing settings, configuring scripts &amp; testing&lt;/h3&gt;
&lt;p&gt;If you used a fully-managed host like Masto.host (mentioned earlier), you&amp;#x27;ve already created an account and logged in. If you&amp;#x27;ve followed the guide for creating your own server for Mastodon, then you were given a temporary password to login with. Now it&amp;#x27;s time to visit your website and sign into Mastodon. You&amp;#x27;ll also want to immediately enable two-factor auth.&lt;/p&gt;
&lt;img alt="Mastodon server logged out view after installing Mastodon" height="1328" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-web-signin.jpg" width="2000" /&gt;
&lt;small&gt;Your new homepage (single user mode enabled) will look empty like this&lt;/small&gt;
&lt;p&gt;After signing in, click &lt;strong&gt;Preferences&lt;/strong&gt; in the sidebar. The first thing we want to do is confirm that upgrading your account to the owner role actually worked. You should see a tab for &lt;strong&gt;Administration&lt;/strong&gt; along with others like Sidekiq and PgHero. It should look like this:&lt;/p&gt;
&lt;img alt="Mastodon Admin Settings" height="1813" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-web-admin-tab.jpg" width="2000" /&gt;
&lt;p&gt;Feel free to browse the Profile tab to fill out your bio and related settings. Importantly, I think "Require follow requests" may be on by default which you probably want to disable.&lt;/p&gt;
&lt;p&gt;Next, here&amp;#x27;s a few things to setup:&lt;/p&gt;
&lt;h3&gt;Fill out server details&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Administration → Server Settings → Branding:&lt;/strong&gt; Fill out the name and description for your server. This is displayed alongside your profile, homepage, and posts so it&amp;#x27;s quite visible. You can also specify an image to display there.&lt;/p&gt;
&lt;img alt="Mastodon admin settings for server branding" height="1873" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-web-server-branding.jpg" width="2000" /&gt;
&lt;h3&gt;Disable new account creation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Administration → Server Settings → Registrations:&lt;/strong&gt; If this server is just for you, you&amp;#x27;ll want to disable account creation by changing the &lt;strong&gt;Who can sign-up&lt;/strong&gt; setting to nobody.&lt;/p&gt;
&lt;img alt="Mastodon admin settings to disable new user registrations" height="1027" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-web-disable-signups.jpg" width="2000" /&gt;
&lt;h3&gt;Disable unauthenticated access to public timelines&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Administration → Server Settings → Discovery:&lt;/strong&gt; This is just a preference but since this is my own server, I only want my posts easily accessible. I wanted to disable the &lt;code&gt;/public&lt;/code&gt; (the "Federated" tab) timeline which displayed posts from other people. I&amp;#x27;m also a little concerned about liability related to my server hosting content from other people (as mentioned in &lt;a href="https://paulstamatiou.com/mastodon/#self-hosting-your-own-mastodon-instance"&gt;my last Mastodon post&lt;/a&gt;) so making those posts harder to find seems best.&lt;/p&gt;
&lt;p&gt;As such, I unchecked &lt;strong&gt;Allow unauthenticated access to public timelines&lt;/strong&gt;.&lt;/p&gt;
&lt;img alt="Mastodon admin settings for discovery" height="2210" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-web-discovery.jpg" width="2000" /&gt;
&lt;h3&gt;Adjust media and content retention&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Administration → Server Settings → Content retention:&lt;/strong&gt; This page has some important settings that control how much media and content is kept on your server. If you follow or interact with lots of people on Mastodon (or have any relays setup), you will see the size and costs associated with your cloud object storage provider rise.&lt;/p&gt;
&lt;p&gt;You change safely set the &lt;strong&gt;media cache retention period&lt;/strong&gt; to something like 7-30 days. I went with 7 and haven&amp;#x27;t noticed any odd behavior. This will delete downloaded media files (from other servers, stored on your server or cloud storage) after the specified period. If your server requests the media again, it will be re-downloaded so there&amp;#x27;s no real harm in setting it too low, you can always change it.&lt;/p&gt;
&lt;p&gt;The other setting &lt;strong&gt;content cache retention period&lt;/strong&gt; is a bit less safe to tinker with and will delete posts from other users on your server. I decided not to touch this and will see if I need to change it if my database grows too large over time.&lt;/p&gt;
&lt;img alt="Mastodon admin settings for content and media retention" height="1135" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-web-content-retention.jpg" width="2000" /&gt;
&lt;h3&gt;Setting up a cron job for periodic cleanup tasks&lt;/h3&gt;
&lt;p&gt;The media retention setting in the admin interface is just one setting. You have far more control at your disposal with the &lt;code&gt;tootctl&lt;/code&gt; admin command in the command line. In fact, running these commands regularly is a recommend part of setup, mentioned under &lt;a href="https://docs.joinmastodon.org/admin/setup/#cleanup"&gt;periodic cleanup tasks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For this, I have several of the commands running as a cronjob. I put them in a bash script for a cronjob to run. I could have put them directly in the crontab editor as a weekly task but it&amp;#x27;s easier to keep it in one file where I know it will run serially, without having to specify different run times for each job so they don&amp;#x27;t run at the same time, potentially bogging down your server.&lt;/p&gt;
&lt;p&gt;Here&amp;#x27;s a list of the commands I run regularly. I put them in a bash script named &lt;code&gt;cleanup.sh&lt;/code&gt; that I store in my home directory &lt;code&gt;/home/mastodon&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# This does the heavy lifting and removes locally cached copies of media attachments from other servers
# By default its set to delete media older than 7 days. 
# Adjust days with --days, eg --days 3 for a more aggressive setting
RAILS_ENV=production /home/mastodon/live/bin/tootctl media remove

# Remove local thumbnails for preview cards (the thumbnail when links are shared, etc)
# The default is 180 days and under 14 days is not recommended as they will not be refetched
RAILS_ENV=production /home/mastodon/live/bin/tootctl preview_cards remove --days 60

# Deletes remote accounts that never interacted with local accounts 
# Removes many avatar and header images as part of this
RAILS_ENV=production /home/mastodon/live/bin/tootctl accounts prune

# Removes files that do not belong to existing media attachments 
# For example if you attached a file to a post composer but never posted it
# This command and the one after it are slow to run, so I like to keep them for last
RAILS_ENV=production /home/mastodon/live/bin/tootctl media remove-orphans

# Remove unreferenced statuses from the database, such as statuses that 
# came from relays or from users who are no longer followed by any local accounts, 
# and have not been replied to or otherwise interacted with
RAILS_ENV=production /home/mastodon/live/bin/tootctl statuses remove
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Switch to the mastodon user and home directory 
sudo su - mastodon
# Create bash script 
vim cleanup.sh
# Make the script executable
chmod +x cleanup.sh
# Add the bash script to crontab
crontab -e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In crontab, add this line and then save:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@weekly ./cleanup.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I set it to run weekly but you may prefer to run it more often. I&amp;#x27;ve been running my own Mastodon server for a month now and with this setup my cloud media storage is only at &lt;code&gt;8.5GB&lt;/code&gt; with my usage.&lt;/p&gt;
&lt;h3&gt;Use custom CSS to hide or modify things&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Administration → Server Settings → Appearance:&lt;/strong&gt; This part is completely optional but I found myself wanting to hide certain parts of the site navigation that aren&amp;#x27;t useful to others as my Mastodon server is only for me. I hid the sign in buttons for logged out users. I hid the "Local" tab for me when logged in: useless for me as I&amp;#x27;m the only account on the server. I also hid the "Learn more" button that shows up next to the server details (felt pretty obvious that the server was just for me and I wanted to simplify things a bit).&lt;/p&gt;
&lt;img alt="Mastodon admin server settings for appearance." height="1593" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-web-appearance.jpg" width="2000" /&gt;
&lt;pre&gt;&lt;code&gt;// hide sign in buttons (you&amp;#x27;ll have to manually visit /auth/sign_in)
.navigation-panel .navigation-panel__sign-in-banner, 
.ui__header__links a[href="/auth/sign_in"],
.ui__header__links button { display: none !important; }

// Hide the "Local" tab for logged in users (only do this if you&amp;#x27;re the only user on the server)
.navigation-panel a[href="/public/local"] { display: none !important; }

// hide server details "Learn more" button
.server-banner .button { display: none !important; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Bonus: Clean up the admin UI with an Arc Boost&lt;/h3&gt;
&lt;p&gt;If you&amp;#x27;re running this Mastodon account for you, and you only, you may find the default admin dashboard has a lot of space taken up by useless information, such as showing you the number of new accounts created. I prefer to keep some of that hidden to clean up this page.&lt;/p&gt;
&lt;p&gt;Unfortunately, just adding CSS to hide it as in the previous step does not seem to work for admin pages. So I turned to a feature from my browser, Arc, called Boosts. It&amp;#x27;s an easy way to locally inject CSS (or JS as needed) into any website. Other browsers have similar ways of doing things with extensions. I created an Arc Boost to simply hide a few dashboard modules:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Not pretty, but it does the job
.dashboard .dashboard__item { display: none; }

.dashboard .dashboard__item:nth-child(3), 
.dashboard .dashboard__item:nth-child(9) { display: block; grid-column: span 3; }

.dashboard .dashboard__item:nth-child(12),
.dashboard .dashboard__item:nth-child(13) { display: block; }
&lt;/code&gt;&lt;/pre&gt;
&lt;img alt="Mastodon admin UI with a few modules hidden with CSS." height="1667" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-admin-ui-cleanup2.jpg" width="2000" /&gt;
&lt;small&gt;Admin UI, with a bit less clutter for single-user instances.&lt;/small&gt;
&lt;h3&gt;Try posting an image&lt;/h3&gt;
&lt;p&gt;And one last thing you&amp;#x27;ll want to do is try posting with an image attached. This will confirm your cloud object storage configuration works correctly and that your system can resize images. What you&amp;#x27;ll want to look out for is that dragging an image into the post composer shows a thumbnail preview, and osting it shows the image without requiring you to refresh the page.&lt;/p&gt;
&lt;img alt="Mastodon admin settings" height="1462" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-web-test-media-upload.jpg" width="2000" /&gt;
&lt;h2&gt;Migrating your account&lt;/h2&gt;
&lt;h3&gt;If you have an existing Mastodon account on another server&lt;/h3&gt;
&lt;p&gt;Now you&amp;#x27;re ready for the fun part: moving your existing Mastodon account over to your new server, along with your followers. Mastodon does a great job with this and while it&amp;#x27;s not perfect for reasons I&amp;#x27;ll mention below, your followers will automatically move with you. You can read a bit about the process from the &lt;a href="https://docs.joinmastodon.org/user/moving/#move"&gt;Mastodon documentation&lt;/a&gt;, but it unfortunately does not go into much detail.&lt;/p&gt;
&lt;p&gt;Here are the key things to know about migrating your account:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Your posts don&amp;#x27;t move with you.&lt;/strong&gt; Posts on your existing Mastodon account do not move with you to the new server. This is unfortunate, but likely a product of how the federation is done (every server that interacted with your posts would have to update). If you only care about the content of your posts and not the engagement around them, you could repost them one by one to your new server (before you do the migration), and edit the date in the database to the original date. But that&amp;#x27;s weekend project territory to write a script to do that for you... I digress.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Your followers move with you.&lt;/strong&gt; Your followers will get transferred pretty immediately. When I migrated my account, all of my ~3,000 followers were moved within a day with maybe 75% in the first few hours. It&amp;#x27;s not entirely flawless though, I did lose about 20 followers in the process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You have to manually re-follow accounts you follow.&lt;/strong&gt; You have to export a CSV file of accounts you follow from the old server and upload that on the new server. It&amp;#x27;s really easy, but it does mean that people you follow will receive a notification that you followed them. It&amp;#x27;s not ideal but people will start poking around and realize you just moved servers. I&amp;#x27;ve noticed it dozens of times with people I follow and quickly pieced it together.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Your old profile shows a banner that you&amp;#x27;ve moved to another server.&lt;/strong&gt; And your old account will be in a locked down state. You can login but you can&amp;#x27;t post or edit anything aside from exporting data from the preferences page.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You can&amp;#x27;t migrate your account again for 30 days.&lt;/strong&gt; So make sure you&amp;#x27;re using a username and domain you like.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lets get started! The actual first step is creating an account on your new server, which you&amp;#x27;ve already done.&lt;/p&gt;
&lt;h3&gt;Step 1: Create an account alias on the new server&lt;/h3&gt;
&lt;p&gt;Setting up a Mastodon account migration is a two-way process. The new server needs to be aware of the old account, and the old server needs to send everything to the new server that&amp;#x27;s expecting it.&lt;/p&gt;
&lt;p&gt;So the first step is to log into your new server, open &lt;strong&gt;Preferences → Profile&lt;/strong&gt; and click on the link under "Moving from a different account." On this page you need to provide your old Mastodon account in the format &lt;code&gt;myusername@example.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This step is harmless and reversible—the actual migration does not start until the later steps.&lt;/p&gt;
&lt;img alt="Mastodon account migration: adding an account alias of the old account on the new server" height="1207" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-migration-step1-make-old-acct-alias.jpg" width="2000" /&gt;
&lt;small&gt;On the new server, add an alias for your old account.&lt;/small&gt;
&lt;h3&gt;Step 2: Export your account data from the old server&lt;/h3&gt;
&lt;p&gt;Next, you&amp;#x27;ll want to export data from the old server. Most importantly, you&amp;#x27;ll want to export a CSV of accounts you follow so you can later import this on the new server. You can also export files for your muted accounts, blocked accounts, bookmarks, lists, and blocked domains. Go to your old server to download these files: &lt;strong&gt;Preferences → Import and Export → Data export&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;On this page you can also request an export of your posts and uploaded media from the old server. This is just for you to have as you can&amp;#x27;t import posts on the new server. Also, it may not work you depending on your old server. I&amp;#x27;ve tried to request my archive on my old server numerous times and it never generated a download for me.&lt;/p&gt;
&lt;img alt="Mastodon account migration: export data from the old server" height="1235" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-migration-step2-download-exports.jpg" width="2000" /&gt;
&lt;small&gt;Export data from the old server, especially the list of accounts you follow.&lt;/small&gt;
&lt;h3&gt;Step 3: Begin the migration on the old server&lt;/h3&gt;
&lt;p&gt;Now it&amp;#x27;s time for the actual migration. This will move followers from your old account to your new account. This cannot be undone, so be sure you&amp;#x27;re ready and have setup your server to your liking before proceeding. This is also a good time to edit anything on your old account (edit bio, delete any posts, etc) as you won&amp;#x27;t be able to change it later.&lt;/p&gt;
&lt;p&gt;On the old server, go to &lt;strong&gt;Preferences → Profile → Move to a different account&lt;/strong&gt; and click the link mentioned. This will take you to the account migration page. Carefully read the bulleted list to understand the implications of this move. Then, enter your new account&amp;#x27;s handle and the password used for your old server.&lt;/p&gt;
&lt;img alt="Mastodon account migration: Starting the migration on the old server by providing the new account handle" height="1462" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-migration-step3.jpg" width="2000" /&gt;
&lt;small&gt;Starting the migration on the old server by providing the new account handle.&lt;/small&gt;
&lt;img alt="Mastodon account migration:" height="1210" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-migration-step3-profile-redirect.jpg" width="2000" /&gt;
&lt;small&gt;Your old account will now display header saying your account has moved.&lt;/small&gt;
&lt;p&gt;Now the migration will start. As mentioned earlier, your posts will not move to the new server, only your followers.&lt;/p&gt;
&lt;p&gt;If you have a lot of followers or a particularly low-end server, it may be a good idea to keep an eye on your server as this migration process will keep it busy for a while. You can take a look at system resources with the &lt;code&gt;top&lt;/code&gt; or &lt;code&gt;htop&lt;/code&gt; commands.&lt;/p&gt;
&lt;p&gt;And you can see what Mastodon is doing by looking at the Sidekiq dashboard, accessible in &lt;strong&gt;Preferences → Sidekiq.&lt;/strong&gt; You can click around to see what jobs are running or failing. For the most part you don&amp;#x27;t need to worry too much. Common job failures are often related to other instances having timeouts. The only cause for concern would be an error indicating connectivity issues with your database, which could mean you may want to adjust the Sidekiq concurrency (mentioned a bit later) in &lt;code&gt;/etc/systemd/system/mastodon-sidekiq.service&lt;/code&gt;.&lt;/p&gt;
&lt;img alt="Mastodon account migration:" height="1394" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-migration-step3-server-active.jpg" width="2000" /&gt;
&lt;small&gt;Moving your followers will keep your machine busy. You may wish to keep an eye with &lt;code&gt;htop&lt;/code&gt;.&lt;/small&gt;
&lt;h3&gt;Step 4: Import your following list&lt;/h3&gt;
&lt;p&gt;The last step of the migration process is to import the CSV files you exported in step 2, the most important of which is the list of accounts you follow. On your new server go to &lt;strong&gt;Preferences → Import and export → Import&lt;/strong&gt;. Select what type of CSV file you&amp;#x27;re importing in the dropdown, select the CSV file and upload.&lt;/p&gt;
&lt;p&gt;You can safely import all lists except the following list before you&amp;#x27;ve actually started your migration (step 3) if you wish.&lt;/p&gt;
&lt;p&gt;I recommend waiting a while after step 3 has had time to move most of your followers over before importing your following list. Your server may be quite busy with step 3 so it doesn&amp;#x27;t hurt to do this an hour later.&lt;/p&gt;
&lt;img alt="Mastodon account migration: Importing following list and other CSV files" height="1243" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-migration-step4-new-server-import.jpg" width="2000" /&gt;
&lt;small&gt;Importing your list of accounts you follow&lt;/small&gt;
&lt;p&gt;That&amp;#x27;s it! All of your followers should get moved over shortly and you&amp;#x27;re ready to start using your own Mastodon server. It&amp;#x27;s probably a good idea to have a post or two so your followers don&amp;#x27;t see an empty profile if they happen to venture to your profile.&lt;/p&gt;
&lt;p&gt;If you want to have a bit of fun, you can start looking into adding custom emojis (sometimes called &lt;a href="https://emojos.in"&gt;emojos&lt;/a&gt; in the context of Mastodon) to your server. Take a look at &lt;strong&gt;Preferences → Administration → Custom emojis.&lt;/strong&gt; where you can upload an image and assign it a shortcode. You can even use them in your username.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;What to expect&lt;/h3&gt;
&lt;h3&gt;Missing some posts and replies? Yeah, that&amp;#x27;s normal...&lt;/h3&gt;
&lt;p&gt;After you&amp;#x27;ve had some time to use your new server, you may see some odd behavior that may lead you to something is broken with your server. Profile pages for accounts may be almost entirely empty, or only have a few posts. Post detail views may not have all the engagement or replies you might expect compared to visiting the original post URL on the remote server.&lt;/p&gt;
&lt;p&gt;I&amp;#x27;m just here to tell you this is normal. It&amp;#x27;s not a good thing, but it&amp;#x27;s the current state of how Mastodon works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Missing posts on a profile:&lt;/strong&gt; When you follow someone on your server, or visit the profile of someone you don&amp;#x27;t follow, Mastodon does not go and fetch all of their posts. That would be taxing on their server and yours. So what happens instead is you get some basic profile info, a post or two (pinned ones) and that&amp;#x27;s it. Then, the next time your server sees a post from this user (because you follow them or other some action lead to your server seeing this post), it will then appear on their profile. This is not ideal and it wouldn&amp;#x27;t be unreasonable for Mastodon to fetch a few more posts with a way to request them on demand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This will get a bit better over time.&lt;/strong&gt; Your server is brand new, you only just followed accounts and your server has yet to see a lot of new posts and fill up profiles with new posts going forward. You&amp;#x27;ll still stumble across a user account that your server has never seen before an experience the same blank profile behavior but at least now you know why that is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Missing replies on posts&lt;/strong&gt; from other accounts (not your own posts) is a similar situation; your server may have low federation and not be aware of some of those replies. Understanding why this is the case ends up going down a rabbithole of understanding federation more and how your server even comes to know about posts, and under what conditions another server sends a post to your server.&lt;/p&gt;
&lt;p&gt;This &lt;a href="https://github.com/mastodon/mastodon/issues/14017"&gt;Mastodon GitHub issue&lt;/a&gt; outlines the situations where your server comes to know about posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Someone on your instance follows the person who posted, at the time it was posted&lt;/li&gt;
&lt;li&gt;Someone you (or anyone else on your instance) follow boosts or replies to that toot&lt;/li&gt;
&lt;li&gt;Under some conditions, if that post was a reply to someone you (or anyone else on your instance) follow&lt;/li&gt;
&lt;li&gt;The post mentions anyone on your instance&lt;/li&gt;
&lt;li&gt;The post was a pinned post when the account was discovered by your instance&lt;/li&gt;
&lt;li&gt;Someone on your instance manually fetches a post by pasting the URL in the search bar&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a hot topic these days and I&amp;#x27;ve seen discussions happening around people wanting to add buttons in Mastodon clients to explicitly load all replies on a post, or &lt;a href="https://github.com/mastodon/mastodon/issues/17213"&gt;older posts on a profile&lt;/a&gt;, from the remote servers. There&amp;#x27;s various technical issues with some of these routes, but I&amp;#x27;m curious to see if this situation evolves as I for one would love a refined experience here.&lt;/p&gt;
&lt;h2&gt;Maintaining, debugging, and optimizing your server&lt;/h2&gt;
&lt;h3&gt;A few things to be aware of&lt;/h3&gt;
&lt;p&gt;This section is for people running their own server, &lt;em&gt;not&lt;/em&gt; through a fully-managed host like Masto.host.&lt;/p&gt;
&lt;h3&gt;Updating Mastodon&lt;/h3&gt;
&lt;p&gt;You&amp;#x27;ll need to keep up to date with Mastodon updates to get new features, performance and security updates. You can check for new updates on the GitHub project page but it&amp;#x27;s probably easiest to subscribe to the atom feed in your news reader of choice, such as NetNewsWire.app. Here&amp;#x27;s the &lt;a href="https://github.com/mastodon/mastodon/releases.atom"&gt;releases feed&lt;/a&gt;, or &lt;a href="https://github.com/mastodon/mastodon/releases"&gt;the website&lt;/a&gt; if you prefer.&lt;/p&gt;
&lt;p&gt;The steps you need to follow for each update may vary, but additional steps will be mentioned in the release notes for each release. Generally you&amp;#x27;ll need to SSH into your server, fetch the latest release with git, and restart some services. Find the complete steps here: &lt;a href="https://docs.joinmastodon.org/admin/upgrading/"&gt;Upgrading to a new release&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You should check now that your server is on the latest Mastodon version. At the time of this writing, the DigitalOcean 1-click installer is running an outdated version.&lt;/p&gt;
&lt;h3&gt;Updating your Ubuntu server&lt;/h3&gt;
&lt;p&gt;Along with maintaining and keeping Mastodon up to date, you&amp;#x27;ll want to do the same with your server. If you followed along with the DigitalOcean route explained above, your machine uses Ubuntu OS, apt package manager and has a specific package called &lt;code&gt;unattended-upgrades&lt;/code&gt; installed by default. That package will automatically install essential security updates, but &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-keep-ubuntu-20-04-servers-updated"&gt;you can modify it to update even more&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I don&amp;#x27;t want to get too in the weeds about linux server maintenance as everyone seems to have their own specific practice about when to update things, how to update them, and when to leave it alone.. you know, if it ain&amp;#x27;t broke don&amp;#x27;t fix it. Some people may prefer to update all packages every week. Not including security essentials, that&amp;#x27;s a bit too much for me and feels like I&amp;#x27;d constantly be signing up for a potential debugging session if anything unexpected happens.&lt;/p&gt;
&lt;p&gt;Personally, I just update available packages whenever I get around to it, perhaps every month or two. Usually with these:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Refresh list of available updates 
sudo apt update
# View list of available upgradable updates
apt list --upgradable
# Upgrade packages 
sudo apt upgrade
# Remove unneeded packages not installed by you 
sudo apt autoremove

# Alternatively, instead of the lines above:
sudo apt update &amp;&amp; sudo apt upgrade –y &amp;&amp; sudo apt autoremove -y
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;When things go wrong&lt;/h3&gt;
&lt;p&gt;I wanted to provide a few pointers for things to look into when things go wrong with your Mastodon server. There&amp;#x27;s no way I can provide a general solution to what could be a specific problem you may encounter, but here&amp;#x27;s a few things may be a good place to start:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check service status:&lt;/strong&gt; Run this to get a quick look at the core Mastodon services: &lt;code&gt;mastodon-web.service&lt;/code&gt;, &lt;code&gt;mastodon-sidekiq.service&lt;/code&gt;, and &lt;code&gt;mastodon-streaming.service&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl status mastodon-*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition to status of the 3 core Mastodon services (you&amp;#x27;ll want to see "active (running)" in green), this command will show the last 10 lines from the logs for each service.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check the logs&lt;/strong&gt;: This may sound obvious but knowing how to check and work with logs is critical. It&amp;#x27;s the best way to know what&amp;#x27;s going on with your machine. To access any of the logs for the aforementioned 3 services, we use a tool called &lt;code&gt;journalctl&lt;/code&gt; to read and filter system logs that are saved in the binary "journal" by systemd. You can always run &lt;code&gt;man journalctl&lt;/code&gt; to see more details on how you can use it; it&amp;#x27;s very powerful.&lt;/p&gt;
&lt;p&gt;It&amp;#x27;s a good idea to go through the logs for each Mastodon service after you&amp;#x27;ve set everything up to make sure that things are working smoothly. Here&amp;#x27;s a few examples of how you can use it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Show the last 50 lines of the mastodon-web log
# and keep showing new lines as they come in (that&amp;#x27;s the -f follow flag)
sudo journalctl -u mastodon-web -n 50 -f

# Or read the mastodon-streaming log 
sudo journalctl -u mastodon-streaming -n 50 -f

# Or the mastodon-sidekiq log 
sudo journalctl -u mastodon-sidekiq -n 50 -f

# Or only show me sidekiq logs that have priority level 4 (warn) or higher
sudo journalctl -u mastodon-sidekiq -f -n 100 -p 4

# And you can always pipe it to grep
# For example to show any lines from the web logs that have a status code of 500
sudo journalctl -f -u mastodon-web -n 500 | grep "status=500"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can do a lot with these commands. This helped me initially discover database connection issues when I over-eagerly tried to optimize configs, and helped me notice that I forgot to require SSL for my remote database.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Restart things:&lt;/strong&gt; You can restart or reload (like a restart but no downtime as it reloads the config, if supported by the service) these services with a command like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart mastodon-web
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check sidekiq:&lt;/strong&gt; Sidekiq is the background job processor for Mastodon. It&amp;#x27;s how Mastodon manages receiving new posts, sends our your posts, and more.&lt;/p&gt;
&lt;p&gt;Generally you don&amp;#x27;t need to worry too much about what it&amp;#x27;s doing, but there&amp;#x27;s one key scenerio to be aware of: if you have a massive queue of jobs that are piling up. What this looks like is the "Enqueued" tab in the Sidekiq dashboard will show a large number that&amp;#x27;s not going down in a reasonable amount of time.&lt;/p&gt;
&lt;p&gt;This would mean your machine cannot keep up. You may notice significant delays in receiving new posts on your server. A Mastodon instance running well would have likely have few to no tasks busy or queued, and when when queued tasks are added, they get processed quickly. If this happens to you, it could mean your server is underpowered and trying to do too much (don&amp;#x27;t use any relays if you are), or could benefit from optimizing your sidekiq setup (more later).&lt;/p&gt;
&lt;p&gt;There&amp;#x27;s also a tab for dead jobs in Sidekiq. These are tasks that have failed repeatedly and will not be retried. You&amp;#x27;ll just want to glance at those to ensure the issues are not related to you. Even if some jobs fail, you&amp;#x27;ll likely see that they are HTTP connection or timeout errors with other servers and likely not your issue.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;img alt="Mastodon sidekiq job processor" height="1102" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-sidekiq.jpg" width="2000" /&gt;
&lt;small&gt;Access the sidekiq dashboard directly in &lt;strong class="semibold"&gt;Preferences → Sidekiq&lt;/strong&gt;&lt;/small&gt;
&lt;h3&gt;Optional: Extend your post character limit beyond 500&lt;/h3&gt;
&lt;p&gt;It&amp;#x27;s your server, you control the code so why not extend your server&amp;#x27;s character limit well past the default limit of 500 characters? Doing this will take just a few minutes, all you have to do is change a number in 2 files, precompile assets, and then restart some services.&lt;/p&gt;
&lt;p&gt;This article goes into detail about the process: &lt;a href="https://write.as/sweetmeat/customize-mastodon-to-change-your-post-character-limit"&gt;Customize Mastodon to Change Your Post Character Limit&lt;/a&gt;. However, keep in mind that you&amp;#x27;ll have to reapply these changes with every Mastodon update.&lt;/p&gt;
&lt;p&gt;I changed my character limit to 15,000. In reality I won&amp;#x27;t be posting things that long, though I did take advantage of a 1,000 character reply which was very handy and I didn&amp;#x27;t have to try to split it up into multiple replies or aggressively truncate my thoughts. The change works seamlessly: people on other Mastodon (or other ActivityPub software) servers will see the longer posts, and all Mastodon clients should see the updated limit in their post composer interfaces.&lt;/p&gt;
&lt;img alt="Mastodon composers with extended character limit: Ivory and Mastodon web" height="1282" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-extended-char-limit.jpg" width="2000" /&gt;
&lt;small&gt;Extended character limit recognized by &lt;a href="https://tapbots.com/ivory/" title="Ivory for iOS by Tapbots"&gt;Ivory&lt;/a&gt; and Mastodon Web.&lt;/small&gt;
&lt;h3&gt;Optional: Change your join date&lt;/h3&gt;
&lt;p&gt;One annoying thing of creating a new Mastodon server, or migrating to a new one, is that your join date—shown prominently on your profile—reflects the date you created the new account. It doesn&amp;#x27;t inherit the date of your previous account(s) that you migrated.&lt;/p&gt;
&lt;p&gt;Since you&amp;#x27;re running your own server and have access to the database, the join date is easy to change. You can change it to your original account creation date, or any date you like. Why not? It&amp;#x27;s your server, do with it what you want. SSH into your server, or use the web console from your hosting provider and run the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo su - mastodon 

# Enter PostgreSQL interactive terminal for the Mastodon database
psql -d mastodon_production

# Update the join date for your account...
# Replace YOUR_USERNAME with your username (does not include domain name)
# Replace date with your desired date, using the exact same format 
UPDATE accounts SET created_at=&amp;#x27;2018-01-01 00:00:00&amp;#x27; where username=&amp;#x27;YOUR_USERNAME&amp;#x27;;

# Exit the PostgreSQL interactive terminal
exit
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Optimizing Mastodon for your server&lt;/h3&gt;
&lt;h3&gt;Proceed at your own risk&lt;/h3&gt;
&lt;p&gt;If you&amp;#x27;re just running your own single-user Mastodon instance that you expect to be low traffic, you don&amp;#x27;t need to touch anything in this section; the defaults are fine for your system. But if you have a beefy server with extra RAM and cores to put to use, you have a lot of followers, and/or you just generally want to nerd out on squeezing more performance from your server then read ahead.&lt;/p&gt;
&lt;p&gt;As a word of caution, changing things in various Mastodon config files can easily take your server offline. I recommend doing this before you&amp;#x27;ve done a migration, or late at night to minimize the effects of downtime. Also, I highly recommend setting up Rewind AI on your Mac so you can easily retrace your steps when you&amp;#x27;re changing things and running commands.&lt;/p&gt;
&lt;p&gt;By following this guide, you&amp;#x27;ve already done an important performance tweak: using a cloud object storage solution for hosting all media for your Mastodon posts. The most common things advanced users will do includes modifying sidekiq to use separate processes for each queue, as well as setting up pgbouncer for PostgresSQL.&lt;/p&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;p&gt;Scaling Mastodon can be a deep topic depending on your needs. I&amp;#x27;m only concerned with running Mastodon as a single-user instance, but it&amp;#x27;s helpful to better understand the pieces of Mastodon and what can and should be changed and when.&lt;/p&gt;
&lt;p&gt;The Mastodon website has a page about &lt;a href="https://docs.joinmastodon.org/admin/scaling/"&gt;scaling your server&lt;/a&gt;. It&amp;#x27;s particularly good to learn the relationship between threads and processes with the web server (threads max out your CPU first, processes max out your RAM first), as well as learning about pgbouncer to help reduce any database connection bottlenecks.&lt;/p&gt;
&lt;p&gt;Then there&amp;#x27;s the lengthy and advanced article &lt;a href="https://hazelweakly.me/blog/scaling-mastodon/"&gt;Scaling Mastodon: The Compendium&lt;/a&gt;. Most of the topics in there might be a bit overkill for a small single-user instance though.&lt;/p&gt;
&lt;h3&gt;Sidekiq&lt;/h3&gt;
&lt;p&gt;I mentioned a bit about what to be on the lookout for &lt;a href="#sidekiq"&gt;with sidekiq earlier in this article&lt;/a&gt;, such as having a backed up queue. The first thing I did with sidekiq is slightly increase the number of threads. By default using the DigitalOcean droplet, you may have a default of &lt;code&gt;25&lt;/code&gt; for &lt;code&gt;DB_POOL&lt;/code&gt; and the concurrency flag &lt;code&gt;-c&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, the number of &lt;code&gt;max_connections&lt;/code&gt; for the Postgres database by default is 100. Counterintuitely, if you opted for a separate managed database from DigitalOcean you may have only 25 database connections available, in which case you may opt to setup their pgbouncer feature called "connection pools"; that&amp;#x27;s what I did. If you do the same, you&amp;#x27;ll need to change your database connection info so Mastodon connects to pgbouncer on a different port.&lt;/p&gt;
&lt;p&gt;What I did was edit &lt;code&gt;/etc/systemd/system/mastodon-sidekiq.service&lt;/code&gt; and change the &lt;code&gt;DB_POOL&lt;/code&gt; value to 50, as well as the number after &lt;code&gt;-c&lt;/code&gt; on the ExecStart line. You don&amp;#x27;t want to tell sidekiq to consume all of our available database connections as each Puma web server thread will need a database connection too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Environment="RAILS_ENV=production"
# Edit this 
Environment="DB_POOL=50"
Environment="MALLOC_ARENA_MAX=2"
Environment="LD_PRELOAD=libjemalloc.so"
# and the number after -c 
ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 50
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I also configured sidekiq to automatically restart every 6 hours. There seems to be an &lt;a href="https://github.com/mastodon/mastodon/issues/16495"&gt;open issue&lt;/a&gt; where sidekiq memory usage continually creeps up over time. Honestly, this was one of the firs things I configured and never ran without this setting so I&amp;#x27;m not entirely sure how necessary it is. But I haven&amp;#x27;t had any issues.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Restart=always
RuntimeMaxSec=21600
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After modifying &lt;code&gt;mastodon-sidekiq.service&lt;/code&gt;, you&amp;#x27;ll need to run these:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
systemctl restart mastodon-sidekiq.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Puma&lt;/h3&gt;
&lt;p&gt;The other thing I did was adjust the number of threads and proccesses used by puma, the Mastodon web/app server. If you expect a lot of web traffic, or just like to be prepared, you may be interested in tweaking puma as well.&lt;/p&gt;
&lt;p&gt;I initially looked into this because had some unused RAM on my server and wanted to let puma use a bit more. In the process I learned that the &lt;a href="https://ruby.social/@evanphx/109365852402052564"&gt;it&amp;#x27;s recommended to allocate&lt;/a&gt; 1.5 processes per CPU core. So my server has 2 cores, so I can get away with 3 processes for puma. I set it to also use 10 threads per process.&lt;/p&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/systemd/system/mastodon-web.service&lt;/code&gt; and add these two lines (or modify if they already exist):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Set this to 1.5 * number of cpu cores
# Find your # of cores by running: cat /proc/cpuinfo | grep processor | wc -l
Environment="WEB_CONCURRENCY=3"
# The number of threads per process. If you&amp;#x27;re RAM constrained, you may want to lower this.
Environment="MAX_THREADS=10"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save the file and restart the service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
systemctl restart mastodon-web.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That being said, you don&amp;#x27;t really need to mess with puma much. For a single user instance the vast majority of your traffic will be from people interacting with your posts via a Mastodon app/client through their server—not directly via your website—which is not served by the web service.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This is barely scratching the surface with what&amp;#x27;s possible for optimizing and scaling Mastodon. I&amp;#x27;m just running my own single-user instance and it&amp;#x27;s been running great so far. If you&amp;#x27;re not sure what to do, I would recommend not touching anything and seeing how your server works for a while. You can keep an eye on system logs as &lt;a href="#go-wrong"&gt;mentioned earlier&lt;/a&gt;, as well as sidekiq.&lt;/p&gt;
&lt;h3&gt;The end&lt;/h3&gt;
&lt;p&gt;This article has been in the works for a while now but hopefully it has helped you get a better understanding what&amp;#x27;s involved with hosting your own Mastodon server, how to keep it running smoothly, and how to tweak it to your liking.&lt;/p&gt;
&lt;p&gt;It&amp;#x27;s a bit of work to get running but the benefits of owning your online identity and data with Mastodon are well worth it. If you&amp;#x27;ve enjoyed this article, please share it with anyone that might find it useful, and of course follow me on Mastodon on my new instance: &lt;a href="https://stammy.design/@stammy"&gt;@stammy@stammy.design&lt;/a&gt;.&lt;/p&gt;</description><author>Paul Stamatiou</author><pubDate>Tue, 07 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://paulstamatiou.com/hosting-your-own-mastodon-server</guid></item><item><title>JShrink reaches over 21,000,000 installs and releases v1.6!</title><link>https://blog.tedivm.com/projects/jshrink/2023/03/jshrink-reaches-over-21000000-installs-and-releases-v1-6-0/</link><description>JShrink is a Javascript minifier written in pure PHP. It allows web applications such as Magento and Matomo (as well as thousands of other projects) to shrink Javascript code on the fly. In 2014 JShrink reached stability, and shortly after that hit the milestone of 100k total downloads- today JShrink has over 21 million downloads,...</description><author>tedious ramblings</author><pubDate>Mon, 06 Mar 2023 18:00:48 GMT</pubDate><guid isPermaLink="true">https://blog.tedivm.com/projects/jshrink/2023/03/jshrink-reaches-over-21000000-installs-and-releases-v1-6-0/</guid></item><item><title>Keeping a semi-automatic electronic ship's logbook</title><link>https://bergie.iki.fi/blog/electronic-logbook/</link><description>&lt;p&gt;Maintaining a proper &lt;a href="https://en.wikipedia.org/wiki/Logbook_(nautical)"&gt;ship’s logbook&lt;/a&gt; is something that most boats should do, for practical, as well as legal and traditional reasons. The logbook can serve as a record of proper maintenance and operation of the vessel, which is potentially useful when selling the boat or handling an insurance claim. It can be a fun record of journeys made to look back to. And it can be a crucial aid for getting home if the ship’s electronics or GNSS get disrupted.&lt;/p&gt;

&lt;p&gt;Like probably most operators of a small boat, &lt;a href="https://lille-oe.de"&gt;on &lt;em&gt;Lille Ø&lt;/em&gt;&lt;/a&gt; our logbook practices have been quite varying. We’ve been good at recording engine maintenance, as well as keeping the traditional navigation log while offshore. But in the more hectic pace of coastal cruising or daysailing this has often fallen on the wayside. And as such, a lot of the events and history of the boat is unavailable.&lt;/p&gt;

&lt;p&gt;To redeem this I’ve developed &lt;a href="https://www.npmjs.com/package/@meri-imperiumi/signalk-logbook"&gt;signalk-logbook&lt;/a&gt;, a semi-automatic electronic logbook for vessels running the &lt;a href="https://signalk.org"&gt;Signal K&lt;/a&gt; marine data server.&lt;/p&gt;

&lt;p&gt;This allows logbook entries to be produced both manually and automatically. The can be viewed and edited using any web-capable device on board, meaning that you can write a log entry on your phone, and maybe later analyse and print them on your laptop.&lt;/p&gt;

&lt;h2 id="why-signal-k"&gt;Why Signal K&lt;/h2&gt;

&lt;p&gt;Signal K is a marine data server that has integrations with almost any relevant marine electronics system. If you have an older NMEA0183 or Seatalk system, Signal K can communicate with it. Same with NMEA2000. If you already have your navigational data on the boat WiFi, Signal K can use and enrich it.&lt;/p&gt;

&lt;p&gt;This means that by making the logbook a Signal K plugin, I didn’t have to do any work to make it work with existing boat systems. Signal K even provides a user interface framework.&lt;/p&gt;

&lt;p&gt;This means that to make the electronic logbook happen, I only had to produce &lt;a href="https://github.com/meri-imperiumi/signalk-logbook/tree/main/plugin"&gt;some plugin JavaScript&lt;/a&gt;, and then build a user interface. As I don’t do front-end development that frequently, this gave me a chance to dive into modern &lt;a href="https://reactjs.org/docs/hooks-intro.html"&gt;React with hooks&lt;/a&gt; for the first time. What better to do after being laid off?&lt;/p&gt;

&lt;p&gt;Signal K also has very good integration with &lt;a href="https://www.influxdata.com/"&gt;Influx&lt;/a&gt; and &lt;a href="https://grafana.com"&gt;Grafana&lt;/a&gt;. These can record vessel telemetry in a high resolution. So why bother with a logbook on the side? In my view, a separate logbook is still valuable for storing the comments and observations not available in a marine sensor network. It can also be a lot more durable and archivable than a time series database. On &lt;a href="https://lille-oe.de"&gt;&lt;em&gt;Lille Ø&lt;/em&gt;&lt;/a&gt; we run both.&lt;/p&gt;

&lt;h2 id="user-interface"&gt;User interface&lt;/h2&gt;

&lt;p&gt;The signalk-logbook comes with a reasonably simple web-based user interface that is integrated in the Signal K administration UI. You can find it in &lt;code class="language-plaintext highlighter-rouge"&gt;Web apps&lt;/code&gt; → &lt;code class="language-plaintext highlighter-rouge"&gt;Logbook&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The primary view is a timeline. Sort of “Twitter for your boat” kind of view that allows quick browsing of entries on both desktop and mobile.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Logbook timeline view" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/logbook-timeline.png" /&gt;&lt;/p&gt;

&lt;p&gt;There is also the more traditional tabular view, best utilized on bigger screens:&lt;/p&gt;

&lt;p&gt;&lt;img alt="Logbook timeline view" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/logbook-logbook.png" /&gt;&lt;/p&gt;

&lt;p&gt;While the system can produce a lot of the entries automatically, it is also easy to create manual entries:&lt;/p&gt;

&lt;p&gt;&lt;img alt="Adding an entry" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/logbook-new.png" /&gt;&lt;/p&gt;

&lt;p&gt;These entries can also include weather observations. Those using celestial navigation can also record manual fixes with these entries! Entries can be categorized to separate things like navigational entries from radio or maintenance logs.&lt;/p&gt;

&lt;p&gt;If you have the &lt;a href="https://www.npmjs.com/package/@signalk/sailsconfiguration"&gt;sailsconfiguration plugin&lt;/a&gt; installed, you can also log sail changes in a machine-readable format:&lt;/p&gt;

&lt;p&gt;&lt;img alt="Sail changes editor" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/logbook-sails.png" /&gt;&lt;/p&gt;

&lt;p&gt;Since the log format is machine readable, the map view allows browsing entries spatially:&lt;/p&gt;

&lt;p&gt;&lt;img alt="Log entries on a map" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/logbook-map.png" /&gt;&lt;/p&gt;

&lt;h2 id="electronic-vs-paper"&gt;Electronic vs. paper&lt;/h2&gt;

&lt;p&gt;The big benefits of an electronic logbook are automation and availability. The logbook can create entries by itself based on what’s happening with the vessel telemetry. You can read and create log entries anywhere on the boat, using the electronic devices you carry with you. Off-vessel backups are also both possible, and quite easy, assuming that the vessel has a reasonably constant Internet connection.&lt;/p&gt;

&lt;p&gt;With paper logbooks, the main benefit is that they’re fully independent of the vessel’s electronic system. In case of power failure, you can still see the last recoded position, heading, etc. They are also a lot more durable in the sense that paper logbooks from centuries ago are still fully readable. Though obviously that carries a strong &lt;a href="https://en.wikipedia.org/wiki/Survivorship_bias"&gt;survivorship bias&lt;/a&gt;. I would guess the vast majority of logbooks, especially on smaller non-commercial vessels, don’t survive more than a couple of years.&lt;/p&gt;

&lt;p&gt;So, how to benefit from the positive aspects of electronic logbooks, while reducing the negatives when compared to paper? Here are some ideas:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Mark your position on a paper chart&lt;/em&gt;. Even though most boats navigate with only electronic charts, it is a good idea to have at least a planning chart available on paper. When offshore, plot your hourly or daily position on it. This will produce the navigation aid of last resort if all electronics fail. And marked charts are pretty!&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Have an off-vessel backup of your electronic logs&lt;/em&gt;. The signalk-logbook uses &lt;a href="https://github.com/meri-imperiumi/signalk-logbook#data-storage-and-format"&gt;a very simple plain text format&lt;/a&gt; for its entries exactly for this reason. The logs are easy to back up, and can also be utilized without the software itself. This means that with a bit of care your log entries shouls stay readable for many many years to come. On Lille Ø we store them &lt;a href="https://github.com/meri-imperiumi/log/tree/main/_data/logbook"&gt;on GitHub&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Print your logs&lt;/em&gt;. With something like a cheap receipt printer, it would be possible to print your log entries periodically, maybe daily or after each trip. Then you can have an archival copy that doesn’t rely on electronics. &lt;a href="https://github.com/meri-imperiumi/logbook-printer"&gt;Here is a repository&lt;/a&gt; implementing just that&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="api"&gt;API&lt;/h2&gt;

&lt;p&gt;In addition to providing a web-based user interface, signalk-logbook &lt;a href="https://editor.swagger.io/?url=https://raw.githubusercontent.com/meri-imperiumi/signalk-logbook/main/schema/openapi.yaml"&gt;provides a REST API&lt;/a&gt;. This allows software developers to create new integrations with the logbook. For example, these could include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Automations to generate log entries for some events via &lt;a href="https://nodered.org/"&gt;node-red&lt;/a&gt; or &lt;a href="https://noflojs.org"&gt;NoFlo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Copying the log entries to a cloud service&lt;/li&gt;
  &lt;li&gt;Exporting the logs to another format, like &lt;a href="https://en.wikipedia.org/wiki/GPS_Exchange_Format"&gt;GPX&lt;/a&gt; or a spreadsheet&lt;/li&gt;
  &lt;li&gt;Other, maybe non-web-based user interfaces for browsing and creating log entries&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="getting-started"&gt;Getting started&lt;/h2&gt;

&lt;p&gt;To utilize this electronic logbook, you need a working installation of &lt;a href="https://signalk.org"&gt;Signal K&lt;/a&gt; on your boat. The common way to do this is by having a &lt;a href="https://www.raspberrypi.com"&gt;Raspberry Pi&lt;/a&gt; powered by the boat’s electrical system and connected to the various on-board instruments.&lt;/p&gt;

&lt;p&gt;There are some nice solutions for this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://shop.hatlabs.fi/products/sh-rpi"&gt;Sailor Hat for Raspberry Pi&lt;/a&gt; allows powering a Raspberry Pi from the boat’s 12V system. It also handles shutdowns in a clean way, protecting the memory card from data corruption&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://seabits.com/nmea-2000-powered-raspberry-pi/"&gt;Pican-M&lt;/a&gt; both connects a Raspberry Pi to a NMEA2000 bus, and powers it through that&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can of course also do a more custom setup, like we &lt;a href="https://bergie.iki.fi/blog/signalk-boat-iot/"&gt;did on our old boat&lt;/a&gt;, &lt;em&gt;Curiosity&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For the actual software setup, &lt;a href="https://github.com/tkurki/marinepi-provisioning"&gt;marinepi-provisioning&lt;/a&gt; gives a nice Ansible playbook for getting everything going. &lt;a href="https://bareboat-necessities.github.io"&gt;Bareboat Necessities&lt;/a&gt; is a “Marine OS for Raspberry Pi” that comes with everything included.&lt;/p&gt;

&lt;p&gt;If you have a Victron GX device (for example Cerbo GX), you can also &lt;a href="https://www.victronenergy.com/live/venus-os:large"&gt;install Signal K on that&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once Signal K is running, just look up &lt;code class="language-plaintext highlighter-rouge"&gt;signalk-logbook&lt;/code&gt; in the Signal K app store. You’ll also want to install the &lt;code class="language-plaintext highlighter-rouge"&gt;signalk-autostate&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;sailsconfiguration&lt;/code&gt; plugins to enable some of the automations.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Signal K appstore" src="https://d2vqpl3tx84ay5.cloudfront.net/800x/logbook-appstore.png" /&gt;&lt;/p&gt;

&lt;p&gt;Then just restart Signal K, log in, and start logging!&lt;/p&gt;</description><author>Henri Bergius</author><pubDate>Mon, 06 Mar 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://bergie.iki.fi/blog/electronic-logbook/</guid></item><item><title>Can ChatGPT write the Python async message queue codes from the previous posts?</title><link>https://bytepawn.com/can-chatgpt-write-the-python-async-message-queue-codes-from-the-previous-posts.html</link><description>&lt;p&gt;I try to get ChatGPT to write the codes in the previous posts. It's able to write the basic message queue skeleton, but cannot implement more complicated features such as delivery semantics with caching.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/an-ai-writing-python-code.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 05 Mar 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/can-chatgpt-write-the-python-async-message-queue-codes-from-the-previous-posts.html</guid></item><item><title>Floccus is the bookmark manager you have been looking for</title><link>https://j11g.com/2023/03/04/floccus-is-the-bookmark-manager-you-have-been-looking-for/</link><description>Floccus does exactly what you want because Floccus doesn&amp;#8217;t break your bookmark management flow. The flow being: adding, changing, removing, moving bookmarks *in* your browser, straight from the bookmark bar and with the shortcuts you already know. Because Floccus is nothing more than a browser extension. How does it work? Floccus is actually not a [&amp;#8230;]</description><author>Jan van den Berg</author><pubDate>Sat, 04 Mar 2023 20:10:57 GMT</pubDate><guid isPermaLink="true">https://j11g.com/2023/03/04/floccus-is-the-bookmark-manager-you-have-been-looking-for/</guid></item><item><title>What I'm up to - March 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-march-2023</link><description>&lt;div class="prose"&gt;
  &lt;div&gt;
&lt;strong&gt;✨ What I've been up to&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;Part of my work at &lt;a href="https://contraption.co"&gt;The Contraption Company&lt;/a&gt; includes helping startups develop new products. For the last six months, I've unintentionally been working almost exclusively with AI-focused startups, and I've seen the rise of OpenAI first-hand. &lt;br /&gt;&lt;br /&gt;While the broader tech market shows signs of slowing, the hype in AI has been exploding. One Contraption Co. client went from an idea to &amp;gt;$7m in seed funding within five weeks. (They're still in stealth - but are hiring in engineering, growth, and design. Email me for an intro.) &lt;br /&gt;&lt;br /&gt;I summarized my observations and predictions about this AI hype cycle in this post: &lt;a href="https://www.philipithomas.com/posts/openai-the-path-for-openai-powered-startups-and-the-ai-hype-cycle"&gt;OpenAI, the path for OpenAI-powered startups, and the AI hype cycle&lt;/a&gt;.&lt;strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;Finally, I've been contemplating this thought: Businesses like to buy from businesses, and people like to buy from people.&lt;strong&gt;&lt;br /&gt;&lt;br /&gt;🤔 Things to share&lt;/strong&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles&lt;/strong&gt;: &lt;a href="https://www.wherewonderwaits.com/social-media-is-bad-for-artists/?utm_source=account_mailer&amp;amp;utm_medium=email&amp;amp;utm_campaign=new_post"&gt;Why Social Media is Bad for Artists&lt;/a&gt; (via &lt;a href="https://chrislt.art/"&gt;Chris L-T&lt;/a&gt;). &lt;a href="https://noahpinion.substack.com/p/the-build-nothing-country?sd=pf"&gt;The Build-Nothing Country&lt;/a&gt;. &lt;a href="https://anu.substack.com/p/silicon-valley-small-business"&gt;Rise of the Silicon Valley Small Business&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books&lt;/strong&gt;: &lt;a href="https://www.amazon.com/Dopamine-Nation-Finding-Balance-Indulgence-ebook/dp/B08KPKHVXQ/ref=tmm_kin_swatch_0?_encoding=UTF8&amp;amp;qid=1677689388&amp;amp;sr=8-1"&gt;Dopamine Nation&lt;/a&gt; - a takeaway for me has been that adversity results in pleasure while pleasure results in pain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trends&lt;/strong&gt;: Vector databases, such as &lt;a href="https://supabase.com/blog/openai-embeddings-postgres-vector"&gt;pgvector&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio: &lt;/strong&gt;&lt;a href="https://remoteruby.com/216"&gt;Derek Sivers on Remote Ruby&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video&lt;/strong&gt;: &lt;a href="https://www.youtube.com/watch?v=Dmpnrtey3YU"&gt;Why single-origin coffee is so expensive&lt;/a&gt; (featuring Metric Coffee, my favorite Chicago roaster).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fun fact:&lt;/strong&gt; &lt;a href="https://www.bloomberg.com/opinion/articles/2015-07-14/banks-forgot-who-was-supposed-to-own-dell-shares"&gt;Cede and Company&lt;/a&gt; and &lt;a href="https://www.dtcc.com/about/businesses-and-subsidiaries/dtc"&gt;Depository Trust Company&lt;/a&gt; are two of the most important companies in the USA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gadgets:&lt;/strong&gt; &lt;a href="https://www.umeshiso.com/product/preorder-variety-chopsticks-6-pair-ships-in-october/"&gt;Umeshiso Chopsticks&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps&lt;/strong&gt;: &lt;a href="https://www.macbartender.com/"&gt;Bartender&lt;/a&gt; adds the ability to reorder and hide Mac menu bar items, which I find essential on the new notched laptops. &lt;a href="https://www.freecodecamp.org/news/how-to-block-content-from-web-pages-using-ublock-origin/"&gt;uBlock Origin can block elements on pages&lt;/a&gt; - I started using it on Linkedin to block  the feed + notifications, and on Amazon to make the homepage just a search bar. You can &lt;a href="https://danielmiessler.com/blog/rss-feed-youtube-channel/"&gt;subscribe to Youtube channels via RSS&lt;/a&gt; - allowing you to &lt;a href="https://zapier.com/apps/email/integrations/rss/1441/send-new-rss-feed-entries-via-email"&gt;set up email notifications&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffees I'm drinking: &lt;/strong&gt;Comparing geisha varietals from &lt;a href="https://www.prologcoffee.com/"&gt;Prolog&lt;/a&gt;, &lt;a href="https://www.aprilcoffeeroasters.com/products/guatemala-el-socorro-washed-gesha-limited"&gt;April&lt;/a&gt;, and &lt;a href="https://getprodigal.com/products/finca-betel-geisha-colombia"&gt;Prodigal&lt;/a&gt;. &lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;📫 &lt;strong&gt;What I'm up to this month&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;I'm in the process of building 1,000 websites for people on &lt;a href="https://postcard.page"&gt;Postcard&lt;/a&gt;. I'm also working on a post-writing experience I hope to launch soon.&lt;strong&gt;&lt;br /&gt;&lt;br /&gt;📍 Where I'll be &lt;/strong&gt;&lt;em&gt;(Let me know if we overlap!)&lt;/em&gt;
&lt;/div&gt;&lt;ul&gt;&lt;li&gt;March 8-24: 🇫🇷 Paris&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;figure class="attachment attachment--preview attachment--jpg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBakk3IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--593be90b3504eb1f1852952ca7c0dcf5a1353242/IMG_6599%20(1).jpg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;How I do comparative tastings of coffee&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Thu, 02 Mar 2023 22:40:41 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-march-2023</guid></item><item><title>Writing a simple Python async message queue server - Part II</title><link>https://bytepawn.com/writing-a-simple-python-async-message-queue-server-part-ii.html</link><description>&lt;p&gt;I write a somewhat more complicated, but still relatively simple async message queue server in Python.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/async_echo.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Thu, 02 Mar 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-a-simple-python-async-message-queue-server-part-ii.html</guid></item><item><title>Python tip 24: modifying list using insert and slice</title><link>https://learnbyexample.github.io/tips/python-tip-24/</link><description>&lt;p&gt;The &lt;code&gt;insert()&lt;/code&gt; list method helps to insert an object before the given index. Negative indexing is also supported.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;books &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sourdough'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sherlock Holmes'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: books.insert(-1, 'The Martian')
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;books.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;insert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Martian'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;books
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sourdough'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sherlock Holmes'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Martian'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# index &amp;gt;= list-length will append the object at the end
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;books.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;insert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Legends &amp;amp; Lattes'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;books
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sourdough'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Sherlock Holmes'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'The Martian'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Cradle'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Legends &amp;amp; Lattes'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use slicing notation to modify one or more list elements. The list will automatically shrink or expand as needed. Here are some examples:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;22&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# modify a single element
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;22&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# modify the last three elements
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;:] &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# elements at index 1, 2 and 3 are replaced with a single object
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2000&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# element at index 1 is replaced with multiple elements
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3.14&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4.13&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6.78&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3.14&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4.13&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6.78&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; RHS must be an iterable when you use slicing notation with &lt;code&gt;:&lt;/code&gt;, even when LHS refers to a single element. For example, &lt;code&gt;nums[1:2] = 100&lt;/code&gt; is not valid.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 28 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-24/</guid></item><item><title>Writing a simple Python async message queue server - Part I</title><link>https://bytepawn.com/writing-a-simple-python-async-message-queue-server.html</link><description>&lt;p&gt;I write a simple, bi-directional async message queue server in Python.&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/async_echo.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 27 Feb 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/writing-a-simple-python-async-message-queue-server.html</guid></item><item><title>What I learned from contributing to Rust's linter</title><link>https://nindalf.com/posts/clippy/</link><description>I share my experience contributing to Rust's linter Clippy and the welcoming environment for new contributors.</description><author>Krishna's blog</author><pubDate>Sun, 26 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nindalf.com/posts/clippy/</guid></item><item><title>Marginalia Search: 2 years, big news</title><link>https://www.marginalia.nu/log/74-marginalia-2-years/</link><description>No time like the project&amp;rsquo;s two year anniversary to drop this particular bomb&amp;hellip;
Marginalia&amp;rsquo;s gotten an NLNet grant. This means I&amp;rsquo;ll be able to work full time on this project at least a year.
https://nlnet.nl/project/Marginalia/ This grant is essentially the best-case scenario for funding this project. It&amp;rsquo;ll be able to remain independent, open-source, and non-profit.
I won&amp;rsquo;t start in earnest for a few months as I&amp;rsquo;ve got loose ends to tie up before I can devote that sort of time.</description><author>Weblog on marginalia.nu</author><pubDate>Sun, 26 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/74-marginalia-2-years/</guid></item><item><title>How to transcribe podcast audio (WhisperX with speaker diarization)</title><link>https://www.swyx.io/transcribe-podcasts-with-whisper</link><description>&lt;blockquote&gt;
&lt;p&gt;Note: sometimes WhisperX is WAAYYYY too slow so I often end up using https://github.com/ggerganov/whisper.cpp which somehow runs much faster.&lt;/p&gt;
&lt;/blockquote&gt;</description><author>swyx's site RSS Feed</author><pubDate>Fri, 24 Feb 2023 17:33:15 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/transcribe-podcasts-with-whisper</guid></item><item><title>How Microsoft Became Phishing's Biggest Enabler</title><link>https://www.brightball.com/articles/how-microsoft-became-phishings-biggest-enabler</link><description>It might sound strange to hear that Microsoft, a company who goes to great lengths to protect computers and networks, is one of the biggest contributors to phishing and fraud on the planet. It's true unfortunately. 
They aren't actually committing the acts themselves of course, but they are enabling the problem by withdrawing support for standards designed to help stop it. Here's why this is such a big deal.
UPDATE 4/12/2023: After years, Microsoft is finally fixing this by honoring p=reject. This is a huge improvement and deserves to be applauded. The work isn't done though. We need aggregate reports to avoid blind spots during our implementation. Offering the reports for enterprises is a great step though.</description><author>Brightball Articles</author><pubDate>Thu, 23 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.brightball.com/articles/how-microsoft-became-phishings-biggest-enabler</guid></item><item><title>Notes from Neal Gabler's Walt Disney</title><link>http://notes.eatonphil.com/2023-02-18-neal-gabler-walt-disney-notes.html</link><description>&lt;p&gt;Disney was a celebrity by his mid-30s, Disney the company was famous
by 1930s.&lt;/p&gt;
&lt;p&gt;Even though politically the 1930s was considered the decade of
Roosevelt (elected President in 1933), culturally the 1930s was
considered the decade of Mickey Mouse.&lt;/p&gt;
&lt;p&gt;Almost every new animation/filmmaking technique they tried, they would
experiment with it in shorts (Silly Symphonies) before applying to big
films like Snow White. Examples of this include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple layers of animation moving independently to create depth in
&lt;a href="https://www.youtube.com/watch?v=MYEmL0d0lZE"&gt;The Old Mill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The first Disney animations with humans (not flora/fauna) like &lt;a href="https://www.youtube.com/watch?v=SRB2YlQOSBI"&gt;The
Cookie Carnival&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nobody took animation seriously, didn't think there was much
possibility for it in film. Disney kept pushing the envelope. Some
examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not including the hand inside the drawing (&lt;a href="https://www.youtube.com/watch?v=ERokauUI6TA"&gt;though early Disney ones
did&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Eventually focusing on actual stories, not just gags/jokes&lt;/li&gt;
&lt;li&gt;Sound (the famous Mickey &lt;a href="https://www.youtube.com/watch?v=BBgghnQF6E4"&gt;Steamboat Willie
animation&lt;/a&gt;, &lt;a href="https://www.loc.gov/static/programs/national-film-preservation-board/documents/steamboat_willie.pdf"&gt;read
more&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Merchandise, not just the art&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rarenewspapers.com/view/557744"&gt;Feature films (i.e. Snow White in 1937, the first animated feature
film), not just shorts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Brought Hollywood to Television&lt;ul&gt;
&lt;li&gt;"Walt Disney signed an exclusive long-term contract today with
the American Broadcasting Company to become the first leading
Hollywood producer to enter into formal alliance with
television. &lt;a href="https://timesmachine.nytimes.com/timesmachine/1954/04/03/84611681.html?pageNumber=19"&gt;NY
Times&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Added &lt;a href="https://d23.com/the-wonderful-things-about-walt-disneys-wonderful-world-of-color/"&gt;color to TV
shows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="snow-white"&gt;Snow White&lt;/h3&gt;&lt;p&gt;Disney hired fine arts teachers to come and teach employees. From time
to time he forced the artists to take night classes.&lt;/p&gt;
&lt;p&gt;They trained for years(?) before &lt;em&gt;starting&lt;/em&gt; the animation of Snow
White and did almost all the animation in the last 10 months or so
before the release in December 1937.&lt;/p&gt;
&lt;p&gt;They had to do 24-hour animation in 8 hour shifts to get up to
speed. They had to hire 100s of animators to do fill in work so the
“master” animators could focus on “drawing the extremes”.&lt;/p&gt;
&lt;p&gt;The average age at Disney was 25. These days of the 1930s really felt
quite similar to what a Silicon Valley startup is thought to be.&lt;/p&gt;
&lt;p&gt;Disney preferred to hire recent art school students so they could
train them in the Disney style.&lt;/p&gt;
&lt;p&gt;They could not animate humans during Snow White well enough so they
ended up just tracing them, called
&lt;a href="https://imgur.com/gallery/IZkSR"&gt;rotoscoping&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Snow White voice cast were quite famous at the time. We wouldn't
know it now but it was basically an ensemble cast.&lt;/p&gt;
&lt;h3 id="world-war-2"&gt;World War 2&lt;/h3&gt;&lt;p&gt;Ran low on money so they produced films for the &lt;a href="https://en.wikipedia.org/wiki/List_of_Walt_Disney%27s_World_War_II_productions_for_Armed_Forces"&gt;US
Government&lt;/a&gt;. &lt;a href="https://www.smithsonianmag.com/history/how-disney-propaganda-shaped-life-on-the-home-front-during-wwii-180979057/"&gt;Propaganda&lt;/a&gt;,
basically. But also &lt;a href="https://www.youtube.com/watch?v=kRVFQs2XYy4"&gt;instructional
videos&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://animationguild.org/about-the-guild/disney-strike-1941/"&gt;Disney workers began striking
(1941)&lt;/a&gt;
and established unions. If Disney was a dick before this, he became a
much bigger dick after this.&lt;/p&gt;
&lt;h3 id="post-war"&gt;Post War&lt;/h3&gt;&lt;p&gt;Got into television with ABC initially. First Hollywood company to do
so. Arrangement with ABC was in part to finance Disneyland. (Not
covered in the book but Disney &lt;a href="https://www.nytimes.com/1995/08/01/business/media-business-merger-walt-disney-acquire-abc-19-billion-deal-build-giant-for.html"&gt;eventually took over
ABC&lt;/a&gt;,
not before eventually splitting ABC and working with NBC though.)&lt;/p&gt;
&lt;p&gt;Disney stopped caring about films and moved to mostly thinking about
Disneyland, this under WED (what is now Walt Disney Imagineering).&lt;/p&gt;
&lt;p&gt;After Disneyland launched he moved on to world fairs and eventually
Disneyworld. He died of lung cancer before completing Disneyworld.&lt;/p&gt;
&lt;h3 id="tidbits"&gt;Tidbits&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.disneyplus.com/video/aa400cf1-a54d-4187-997d-573711c88697"&gt;The Reluctant
Dragon&lt;/a&gt;,
a throwaway film because they needed money when they went public. It
is the story of a children's book author trying to get Disney to
make a film out of his book. He stumbles around the new Disney
Burbank Studio through art classes and musicians practicing,
uncovering how Disney films are made in the process.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="questions"&gt;Questions&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;What were the other major animation studies? Even if Snow White was
the first animated feature film, surely others must have rushed to
copy the success. Who were they?&lt;ul&gt;
&lt;li&gt;UPA (Mr Magoo) was one. Also Warner Brothers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Basically after every turn he'd get tired of the stuff he had already
done (and killed at doing) to do something new. From animated shorts
to feature films to television to Disneyland to Disneyworld and EPCOT.&lt;/p&gt;
&lt;p&gt;To his employees he was a huge dick. They'd be in constant fear of
upsetting him and getting fired. And he admitted that he would
basically fire people randomly. He'd fire anyone important enough to
get their name on a door (i.e. establish their own fiefdom within the
company). But it seems more like Disney the company worked in spite of
this rather than because of this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After Mary Poppins (1964, two years before he died): "I'm on the
spot. I have to keep trying to keep up to that same level. And the way
to do it is not to worry, not to get tense. Not to think, 'I got to
beat Mary Poppins', 'I got to beat Mary Poppins'. The way to do it is
just to go off and get interested in some little thing, some little
idea that interests me. Some little idea that looks like fun."&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Finished Neal Gabler's Walt Disney (5/5) and here are my raw notes. (If I had to polish the notes I wouldn't have the will to publish.) Hopefully a few interesting bits and links in there though.&lt;br /&gt;&lt;br /&gt;In particularly this quote (2nd pic) really struck me.&lt;a href="https://t.co/P9astFZ6Ts"&gt;https://t.co/P9astFZ6Ts&lt;/a&gt; &lt;a href="https://t.co/wKPd6zjLau"&gt;pic.twitter.com/wKPd6zjLau&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1627025676162281472?ref_src=twsrc%5Etfw"&gt;February 18, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sat, 18 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-02-18-neal-gabler-walt-disney-notes.html</guid></item><item><title>If I had a million dollars (to do physics)</title><link>https://mikewarot.blogspot.com/2023/02/if-i-had-million-dollars-to-do-physics.html</link><description>&lt;p&gt;&amp;nbsp;If I had a million $USD to do physics, I'd do some experiments.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Berry Phase/Electromagnetic 4 Potential experiments&lt;/h3&gt;&lt;br /&gt;First purchase/rental - a &lt;a href="https://en.wikipedia.org/wiki/SQUID" target="_blank"&gt;SQUID&lt;/a&gt;&amp;nbsp;- Superconducting QUantum Interference Device.&lt;br /&gt;&lt;br /&gt;My first hypothesis to check is regarding the A field, also known as the electromagnetic vector potential. I hypothesize that if you were to take a bifilar wound coil of quite a few turns, the flux when the windings were connected to oppose each other would correctly generate no B field (detectable with a compass, magnetometer, etc), but I believe there WOULD be an A field generated, perhaps much weaker, than that of the field when the windings aided each other, but non-zero.&lt;br /&gt;&lt;br /&gt;I'd then investigate the polarity and form of this field in 3d around the coils, etc.&lt;br /&gt;&lt;br /&gt;I'd also perform the same experiment with a set of toroidally wound coils, a pair, and see what type of A fields they generate, and see if everything matches simulations.&lt;br /&gt;&lt;br /&gt;--&lt;p&gt;&lt;/p&gt;&lt;p&gt;A refinement of this experiment would be to use a pair of atomic clocks, one outside a "null set of coils", and the other inside them (with actual B magnetic fields canceled), to see if there was any drift with power applied.&lt;/p&gt;&lt;p&gt;---&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Gravimetric / Inertial measurements&lt;/h3&gt;&lt;p&gt;Purchase - Vacuum Pump&lt;/p&gt;&lt;p&gt;Next, a torsion bridge, in the manner of Cavendish... a long (as long as I could get in the garage) rod inside a copper/aluminum tube, pumped to as good a vacuum as I could reasonably get. Using electromagnetic forces on the ends, test out various materials to see measure their gravimetric effects on the bridge.&lt;br /&gt;&lt;br /&gt;I'm particularly interested to see if some materials have a different G than others&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Also, it would be interesting to see if a large capacitor had a different gravimetric effect if it were charged/not charged (whilst inside a faraday cage, of course)&lt;/p&gt;</description><author>--Mike--</author><pubDate>Fri, 17 Feb 2023 22:54:47 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2023/02/if-i-had-million-dollars-to-do-physics.html</guid></item><item><title>Simple jumphost ssh-agent config</title><link>https://j11g.com/2023/02/17/simple-jumphost-ssh-agent-config/</link><description>You can find many tutorials online on how to use ssh-agent or ssh-ident correctly. This is a short and simple two line fix aimed at a specific use i.e. a single connection to a jumphost. Add this to your .bashrc So now when you type jumphost: And from the jumphost you can ssh connect to [&amp;#8230;]</description><author>Jan van den Berg</author><pubDate>Fri, 17 Feb 2023 22:28:47 GMT</pubDate><guid isPermaLink="true">https://j11g.com/2023/02/17/simple-jumphost-ssh-agent-config/</guid></item><item><title>2023–02–17: Pinephone keyboard firmware update (1.3)</title><link>https://xnux.eu/log/#078</link><author>megi's PinePhone Development Log</author><pubDate>Fri, 17 Feb 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#078</guid></item><item><title>Muv-Luv Unlimited: THE DAY AFTER 00 review</title><link>https://burakku.com/blog/muv-luv-unlimited-the-day-after-00-review/</link><description>&lt;p&gt;&lt;img alt="Muv-Luv Unlimited: THE DAY AFTER 00" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Taking place after the events of Unlimited, the story has quite a different pace than Unlimited or Alternative with the BETA threat gone. I wasn’t completely sold on the story at first, but the characters are thankfully mostly likeable and interesting. However, I did find myself enjoying the read towards the tail end of the game. The tail end that won’t take too long to reach – it was around a four-and-a-half hour read for me. Obviously, as an “episode zero”, it is meant as a prologue to the rest of THE DAY AFTER story, and that’s probably why it’s not a very long and extremely captivating story.&lt;/p&gt;
&lt;p&gt;The art is mostly satisfactory. The characters have some animation, and a good amount of expressiveness and perspectives. The biggest issue with the art is that some graphical assets are not high enough of a resolution for the amount of zoom levels they push, so you have occasional mosaics right in your face. Audio-wise all characters are fully voiced and you get some hits of nostalgia with some familiar background songs.&lt;/p&gt;
&lt;p&gt;The game also plays nice on the Steam Deck, but you’re going to need a community layout. I don’t know about what goes into getting a game “Steam Deck Verified” as episode 00 is, but there’s no actual good Steam Deck layout. The community layout that I used allowed pretty comfortable UX but still required a bit of joystick mouse movement, so there’s definitely room for improvement. I did also notice some crackling issues with audio after waking the device from sleep without quitting the game, but that might be a SteamOS issue, is not really a major one and can be resolved by just restarting the game.&lt;/p&gt;
&lt;p&gt;All in all, I would recommend THE DAY AFTER 00 for a fan of Muv-Luv – just temper your expectations, and maybe don’t pay full price. After all, if you’re interested in the THE DAY AFTER series, you’re probably not going to skip the prologue, right?&lt;/p&gt;</description><author>ブラック</author><pubDate>Wed, 15 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/muv-luv-unlimited-the-day-after-00-review/</guid></item><item><title>The Capabilities of High Altitude Balloons</title><link>https://miscdotgeek.com/the-capabilities-of-high-altitude-balloons/</link><description>&lt;p&gt;In this MiscDotGeek article, we&amp;#8217;re trying something&amp;#8230; different. This is an experiment. You know my writing, you know my style. Read the article. Poke holes in it. Why? See at the end (don&amp;#8217;t cheat!) High Altitude Balloons: Unlocking the Potential of Near Space Exploration Have you ever wondered what the view from the edge of &amp;#8230; &lt;/p&gt;
&lt;p&gt;&lt;a class="more-link btn" href="https://miscdotgeek.com/the-capabilities-of-high-altitude-balloons/" rel="noopener noreferrer" target="_self"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://miscdotgeek.com/the-capabilities-of-high-altitude-balloons/" rel="noopener noreferrer" target="_self"&gt;The Capabilities of High Altitude Balloons&lt;/a&gt; appeared first on &lt;a href="https://miscdotgeek.com" rel="noopener noreferrer" target="_self"&gt;MiscDotGeek&lt;/a&gt;.&lt;/p&gt;</description><author>MiscDotGeek</author><pubDate>Sun, 12 Feb 2023 23:42:35 GMT</pubDate><guid isPermaLink="true">https://miscdotgeek.com/the-capabilities-of-high-altitude-balloons/</guid></item><item><title>The Time I Accidentally Ended Up Combatting Fraud for a Year</title><link>https://www.brightball.com/articles/the-time-i-accidentally-ended-up-combating-fraud-for-a-year</link><description>Lately, I’ve been spending a lot of time enjoying the Darknet Diaries podcast and it’s compelled me to finally share the entire story of the most intense year of my 20 year professional career. I was the sole developer hired by a company going through a circus-like ownership transition while criminals actively worked to defraud the 300,000 users of this 14 year old, high end marketplace.
We experienced late nights, numerous technical challenges, worked with abuse response teams, learned a lot of lessons about phishing and fraud, high emotions, death threats and at least one person lost a business that depended on the site. Here’s the story from start to finish, including how to prevent many of these problems on your own site. Buckle up.</description><author>Brightball Articles</author><pubDate>Fri, 10 Feb 2023 22:36:00 GMT</pubDate><guid isPermaLink="true">https://www.brightball.com/articles/the-time-i-accidentally-ended-up-combating-fraud-for-a-year</guid></item><item><title>CLI tip 23: recursive filename matching with globstar</title><link>https://learnbyexample.github.io/tips/cli-tip-23/</link><description>&lt;p&gt;Enable the &lt;code&gt;globstar&lt;/code&gt; option to recursively match filenames within a specified path. You can use &lt;code&gt;shopt -s globstar&lt;/code&gt; and &lt;code&gt;shopt -u globstar&lt;/code&gt; to set and unset this option respectively.&lt;/p&gt;
&lt;p&gt;First, create some sample files:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ mkdir test_globstar &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; cd &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$_
&lt;/span&gt;&lt;span&gt;$ mkdir &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;p&lt;/span&gt;&lt;span&gt; todos projects/{tictactoe,calculator}
&lt;/span&gt;&lt;span&gt;$ touch ip.txt .hidden.txt report.log hello.py
&lt;/span&gt;&lt;span&gt;$ touch todos/{books,outing}.txt
&lt;/span&gt;&lt;span&gt;$ touch projects&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;tictactoe&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;game.py projects&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;calculator/{calc.sh,notes.txt}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are some examples:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ shopt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s globstar
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ ls &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;**&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt;.txt
&lt;/span&gt;&lt;span&gt;ip.txt
&lt;/span&gt;&lt;span&gt;projects&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;calculator&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;notes.txt
&lt;/span&gt;&lt;span&gt;todos&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;books.txt
&lt;/span&gt;&lt;span&gt;todos&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;outing.txt
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ ls &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;**&lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/*/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt;.txt
&lt;/span&gt;&lt;span&gt;projects&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;calculator&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;notes.txt
&lt;/span&gt;&lt;span&gt;todos&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;books.txt
&lt;/span&gt;&lt;span&gt;todos&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;outing.txt
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# assumes extglob is enabled
&lt;/span&gt;&lt;span&gt;$ ls &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;**&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt;.@(py&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt;sh)
&lt;/span&gt;&lt;span&gt;hello.py
&lt;/span&gt;&lt;span&gt;projects&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;calculator&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;calc.sh
&lt;/span&gt;&lt;span&gt;projects&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;tictactoe&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;game.py
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ ls &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;1d &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;**/
&lt;/span&gt;&lt;span&gt;projects&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/
&lt;/span&gt;&lt;span&gt;projects&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;calculator&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/
&lt;/span&gt;&lt;span&gt;projects&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;tictactoe&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/
&lt;/span&gt;&lt;span&gt;todos&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; If you need to match hidden files as well, enable the &lt;code&gt;dotglob&lt;/code&gt; option:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ shopt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s dotglob
&lt;/span&gt;&lt;span&gt;$ ls &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;**&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt;.txt
&lt;/span&gt;&lt;span&gt;.hidden.txt
&lt;/span&gt;&lt;span&gt;ip.txt
&lt;/span&gt;&lt;span&gt;projects&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;calculator&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;notes.txt
&lt;/span&gt;&lt;span&gt;todos&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;books.txt
&lt;/span&gt;&lt;span&gt;todos&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span&gt;outing.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/cli-computing"&gt;Linux Command Line Computing&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Fri, 10 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-23/</guid></item><item><title>Vim tip 22: word and WORD motions</title><link>https://learnbyexample.github.io/tips/vim-tip-22/</link><description>&lt;p&gt;Definitions from &lt;a href="https://vimhelp.org/motion.txt.html#word"&gt;:h word&lt;/a&gt; and &lt;a href="https://vimhelp.org/motion.txt.html#WORD"&gt;:h WORD&lt;/a&gt; are quoted below to explain the difference between &lt;strong&gt;word&lt;/strong&gt; and &lt;strong&gt;WORD&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;word&lt;/strong&gt; A word consists of a sequence of letters, digits and underscores, or a sequence of other non-blank characters, separated with white space (spaces, tabs, &lt;code&gt;&amp;lt;EOL&amp;gt;&lt;/code&gt;). This can be changed with the &lt;code&gt;iskeyword&lt;/code&gt; option. An empty line is also considered to be a word.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WORD&lt;/strong&gt; A WORD consists of a sequence of non-blank characters, separated with white space. An empty line is also considered to be a WORD.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;word based motions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;w&lt;/kbd&gt; move to the start of the next word&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;b&lt;/kbd&gt; move to the beginning of the current word if the cursor is &lt;em&gt;not&lt;/em&gt; at the start of word. Otherwise, move to the beginning of the previous word&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;e&lt;/kbd&gt; move to the end of the current word if cursor is &lt;em&gt;not&lt;/em&gt; at the end of word. Otherwise, move to the end of next word&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;ge&lt;/kbd&gt; move to the end of the previous word&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;3w&lt;/kbd&gt; move 3 words forward
&lt;ul&gt;
&lt;li&gt;Similarly, a number can be prefixed for all the other commands discussed here&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WORD based motions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;W&lt;/kbd&gt; move to the start of the next WORD
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;192.1.168.43;hello&lt;/code&gt; is considered as a single WORD, but has multiple words&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;B&lt;/kbd&gt; move to the beginning of the current WORD if the cursor is &lt;em&gt;not&lt;/em&gt; at the start of WORD. Otherwise, move to the beginning of the previous WORD&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;E&lt;/kbd&gt; move to the end of the current WORD if cursor is &lt;em&gt;not&lt;/em&gt; at the end of WORD. Otherwise, move to the end of next WORD&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;gE&lt;/kbd&gt; move to the end of the previous WORD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; All of these motions will work across lines. For example, if the cursor is on the last word of a line, pressing &lt;kbd&gt;w&lt;/kbd&gt; will move to the start of the first word in the next line.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Fri, 10 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-22/</guid></item><item><title>The Sawtooth Career</title><link>https://jodavaho.io/posts/sawtooth-career.html</link><description>&lt;h2 id="on-the-optimality-of-sawtooth-careers"&gt;On the optimality of sawtooth careers&lt;/h2&gt;
&lt;p&gt;I was having lunch at JPL with their Chief Engineer, Rob Manning. Normally,
you&amp;rsquo;d expect the Chief Eng. to reserve their time for more important people,
but that&amp;rsquo;s not how JPL works.&lt;/p&gt;
&lt;p&gt;I had posted in the New Researcher Support Group slack channel, asking for
stories from folks who regret some part of their career. Gloomy, I know. He was
curious, and so we we got lunch.&lt;/p&gt;
&lt;p&gt;Being early career is fraught with uncertainty. What should I specialize in?
How can I make an impact? etc.&lt;/p&gt;
&lt;p&gt;He cut through it pretty quickly with his model for an optimal career: The
sawtooth.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Behold, the sawtooth graph" src="https://jodavaho.io/img/sawtooth.png" /&gt;&lt;figcaption&gt;
      &lt;p&gt;Behold, the sawtooth graph&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Rob said that he had a horrible character flaw, in that when he got bored of
something he&amp;rsquo;d usually leave to do something else&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;. This led to him
perpetually entering new fields as a beginner, asking dumb questions and
holding himself accountable to learning quickly. He said this breadth of
perspective allowed him to become a far better engineer over time.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I respect that. Tying it back, it helped me see that just doing &lt;em&gt;something&lt;/em&gt;,
and doing it fully, is better than not. If you hate it, at least you did it,
and now you can go dedicate yourself to something else.&lt;/p&gt;
&lt;p&gt;For that reason I was perfectly happy not applying for management positions
when I left Amazon. Even though this is kind of a step down. Or, when I was a
manager, I could get over it and do small little contributions to small little
projects because I could learn something.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;As a meta-cognitive skill, I&amp;rsquo;d like to become better at walking into new areas
as a beginner, and less reluctant to do so. Less pearl clutching, more
exploration.&lt;/p&gt;
&lt;p&gt;In the last 3 years, my saw-teeth have been:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Learning to optimize with SIMD, CUDA, and what you might call &amp;ldquo;esoteric problem reformulations&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Beginning projects in new languages. Zig, Rust, or (since I rarely use it anymore) C.&lt;/li&gt;
&lt;li&gt;Diving into backend development with both feet, especially on AWS and &lt;a href="https://vercel.com"&gt;vercel&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These should be evident in &lt;a href="https://jodavaho.io/categories/projects.html"&gt;projects&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And, going forward, I&amp;rsquo;d really love to do more:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;High performance or low-latency computing&lt;/li&gt;
&lt;li&gt;Image processing, especially for tracking&lt;/li&gt;
&lt;li&gt;Work/research on parallelization for search, esp in game theory or discrete
optimization&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now I just need to figure out &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Clearly the chief eng of JPL knows how to finish projects, so I suspect he meant on the scale of years, not days.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&amp;ldquo;May you have breadth over years, and depth over days&amp;rdquo; &amp;ndash; maybe this will &lt;a href="https://jodavaho.io/posts/technologists-prayerbook.html"&gt;catch on&lt;/a&gt;?&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>jodavaho.io</author><pubDate>Thu, 09 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/sawtooth-career.html</guid></item><item><title>Search and replace tricks with ripgrep</title><link>https://learnbyexample.github.io/substitution-with-ripgrep/</link><description>&lt;p&gt;&lt;a href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt; (command name &lt;code&gt;rg&lt;/code&gt;) is a &lt;code&gt;grep&lt;/code&gt; tool, but supports search and replace as well. &lt;code&gt;rg&lt;/code&gt; is far from a like-for-like alternate for &lt;code&gt;sed&lt;/code&gt;, but it has nifty features like multiline replacement, fixed string matching, &lt;code&gt;PCRE2&lt;/code&gt; support, etc. This post gives an overview of syntax for substitution and highlights some of the cases where &lt;code&gt;rg&lt;/code&gt; is a handy replacement for &lt;code&gt;sed&lt;/code&gt;.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="global-search-and-replace"&gt;Global search and replace&lt;a class="zola-anchor" href="#global-search-and-replace"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat ip.txt
&lt;/span&gt;&lt;span&gt;dark blue, light blue
&lt;/span&gt;&lt;span&gt;light orange
&lt;/span&gt;&lt;span&gt;blue sky
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# by default, line number is displayed if output destination is stdout
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# by default, only lines that matched the given pattern is displayed
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 'blue' is search pattern and -r 'red' is replacement string
&lt;/span&gt;&lt;span&gt;$ rg &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'blue' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'red'&lt;/span&gt;&lt;span&gt; ip.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1:dark&lt;/span&gt;&lt;span&gt; red, light red
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;3:red&lt;/span&gt;&lt;span&gt; sky
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# --passthru option is useful to print all lines, whether or not it matched
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# -N will disable line number prefix
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this command is similar to: sed 's/blue/red/g' ip.txt
&lt;/span&gt;&lt;span&gt;$ rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;N &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'blue' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'red'&lt;/span&gt;&lt;span&gt; ip.txt
&lt;/span&gt;&lt;span&gt;dark red, light red
&lt;/span&gt;&lt;span&gt;light orange
&lt;/span&gt;&lt;span&gt;red sky
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="matching-nth-occurrence"&gt;Matching Nth occurrence&lt;a class="zola-anchor" href="#matching-nth-occurrence"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As seen in previous example, &lt;code&gt;rg&lt;/code&gt; will search and replace all occurrences. So, you'll have to be creative with regexp to replace only a specific occurrence per input line.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'see bat hot at but at go gate at sat at but at'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace first occurrence only
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: sed 's/\bat\b/[xyz]/'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;N &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\bat\b(.*)' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[xyz]$1'
&lt;/span&gt;&lt;span&gt;see bat hot [xyz] but at go gate at sat at but at
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: sed 's/\bat\b/[xyz]/3'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# the number within {} is N-1 to replace Nth occurrence, for N&amp;gt;1
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;N &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'^((.*?\bat\b){2}.*?)\bat\b' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$1[xyz]'
&lt;/span&gt;&lt;span&gt;see bat hot at but at go gate [xyz] sat at but at
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace last but Nth occurrence, for N&amp;gt;=0
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;N &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'^(.*)\bat\b((.*\bat\b){3})' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$1[xyz]$2'
&lt;/span&gt;&lt;span&gt;see bat hot at but [xyz] go gate at sat at but at
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="in-place-workaround"&gt;In-place workaround&lt;a class="zola-anchor" href="#in-place-workaround"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;rg&lt;/code&gt; doesn't support in-place option, so you'll have to do it yourself.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# -N isn't needed here as output destination is a file
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# same as: sed -i 's/blue/red/g' ip.txt
&lt;/span&gt;&lt;span&gt;$ rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'blue' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'red'&lt;/span&gt;&lt;span&gt; ip.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; tmp.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; mv tmp.txt ip.txt
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ cat ip.txt
&lt;/span&gt;&lt;span&gt;dark red, light red
&lt;/span&gt;&lt;span&gt;light orange
&lt;/span&gt;&lt;span&gt;red sky
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have &lt;a href="https://joeyh.name/code/moreutils/"&gt;moreutils installed&lt;/a&gt;, then you could use &lt;code&gt;sponge&lt;/code&gt; as well.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'blue' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'red'&lt;/span&gt;&lt;span&gt; ip.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sponge ip.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="rust-regex-and-pcre2"&gt;Rust regex and PCRE2&lt;a class="zola-anchor" href="#rust-regex-and-pcre2"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default, &lt;code&gt;rg&lt;/code&gt; uses Rust regular expressions, which is much more featured compared to &lt;code&gt;GNU sed&lt;/code&gt;. The main feature not supported is backreference within regexp definition (for performance reasons). See &lt;a href="https://docs.rs/regex/1.3.9/regex/index.html"&gt;Rust regex documentation&lt;/a&gt; for regular expression syntax and features. &lt;code&gt;rg&lt;/code&gt; supports Unicode by default.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# non-greedy quantifier is supported
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'food land bark sand band cue combat'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'foo.*?ba' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[xyz]'
&lt;/span&gt;&lt;span&gt;[xyz]rk sand band cue combat
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# unicode support
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fox:αλεπού,eagle:αετός' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\p{L}+' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'($0)'
&lt;/span&gt;&lt;span&gt;(fox)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span&gt;(αλεπού),(eagle)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:&lt;/span&gt;&lt;span&gt;(αετός)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# set operator example, remove all punctuation characters except . ! and ?
&lt;/span&gt;&lt;span&gt;$ para=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&amp;quot;hi&amp;quot;, there! how *are* you? all fine here.'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$para&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[[:punct:]--[.!?]]+' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''
&lt;/span&gt;&lt;span&gt;hi there! how are you? all fine here.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-P&lt;/code&gt; switch will enable &lt;a href="https://www.pcre.org/current/doc/html/index.html"&gt;PCRE2&lt;/a&gt; flavor, which has even more tricks. You can also use &lt;code&gt;--engine=auto&lt;/code&gt; to allow &lt;code&gt;rg&lt;/code&gt; to automatically use &lt;code&gt;PCRE2&lt;/code&gt; when needed (for example: useful as an alias for &lt;code&gt;rg&lt;/code&gt; command so that it gives performance of Rust engine by default and use &lt;code&gt;PCRE2&lt;/code&gt; only when needed).&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# backreference within regexp definition
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cocoa appleseed tool speechless'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;wP &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'([a-z]*([a-z])\2[a-z]*){2}' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$0}'
&lt;/span&gt;&lt;span&gt;cocoa {appleseed} tool {speechless}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replace all whole words except 'imp' and 'ant'
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'tiger imp goat eagle ant important'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;P &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\b(imp|ant)\b(*SKIP)(*F)|\w+' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[$0]'
&lt;/span&gt;&lt;span&gt;[tiger] imp [goat] [eagle] ant [important]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# recursively match parentheses
&lt;/span&gt;&lt;span&gt;$ eqn=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(3+a)x * y((r-2)*(t+2)/6) + z(a(b(c(d(e)))))'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$eqn&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;P &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\((?:[^()]++|(?0))++\)' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''
&lt;/span&gt;&lt;span&gt;x &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span&gt; y &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span&gt; z
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# all lowercase letters and optional hyphen combo from start of string
&lt;/span&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple-fig-mango guava grape'
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;P &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\G([a-z]+)(-)?' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'($1)$2'
&lt;/span&gt;&lt;span&gt;(apple)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;(fig)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;(mango) guava grape
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="extract-and-modify"&gt;Extract and modify&lt;a class="zola-anchor" href="#extract-and-modify"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;-r&lt;/code&gt; option can be used when &lt;code&gt;-o&lt;/code&gt; option is active too. The example shown below is not easy to do with &lt;code&gt;sed&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'0501 035 154 12 26 98234'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# numbers &amp;gt;= 100 and ignore leading zeros
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;woP &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'0*+(\d{3,})' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&amp;quot;$1&amp;quot;' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; paste &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;sd,
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;501&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;154&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;98234&amp;quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="fixed-string-matching"&gt;Fixed string matching&lt;a class="zola-anchor" href="#fixed-string-matching"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like &lt;code&gt;grep&lt;/code&gt;, the &lt;code&gt;-F&lt;/code&gt; option will allow fixed strings to be matched, a handy option that I feel every search and replace tool should provide.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2.3/[4]*6\nfoo\n5.3-[4]*9\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;F &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'[4]*' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'2'
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2.3&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;/&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;26
&lt;/span&gt;&lt;span&gt;foo
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5.3&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-F&lt;/code&gt; doesn't extend to replacement section though, so you need &lt;code&gt;$$&lt;/code&gt; instead of &lt;code&gt;$&lt;/code&gt; character to represent it literally.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a.*{2}-b' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;F &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'.*{2}' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'+$x\tc'
&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span&gt;\tc&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;b
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a.*{2}-b' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;F &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'.*{2}' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'+$$x\tc'
&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$x&lt;/span&gt;&lt;span&gt;\tc&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;b
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="multiline-matching"&gt;Multiline matching&lt;a class="zola-anchor" href="#multiline-matching"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another handy option is &lt;code&gt;-U&lt;/code&gt; which enables multiline matching.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ s=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hi there\nhave a nice day\nbye'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# (?s) flag will allow . to match newline characters as well
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;%b&lt;/span&gt;&lt;span style="color: #d07711;"&gt;' &amp;quot;$s&amp;quot; &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;U &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(?s)the.*ice' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''
&lt;/span&gt;&lt;span&gt;hi  day
&lt;/span&gt;&lt;span&gt;bye
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://learnbyexample.github.io/multiline-search-and-replace/"&gt;my blog post&lt;/a&gt; for a detailed discussion on multiline fixed string search and replace operations from the command line.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="handling-dos-style-input"&gt;Handling dos-style input&lt;a class="zola-anchor" href="#handling-dos-style-input"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;rg&lt;/code&gt; provides support for dos-style files with &lt;code&gt;--crlf&lt;/code&gt; option.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# same as: sed -E 's/\w+(\r?)$/xyz\1/'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# note that output will retain CR+LF as line ending
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# similar to the sed solution, this will work for unix-style input too
&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;printf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hi there\r\ngood day\r\n' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;crlf &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\w+$' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'xyz'
&lt;/span&gt;&lt;span&gt;hi xyz
&lt;/span&gt;&lt;span&gt;good xyz
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="speed-comparison-with-gnu-sed"&gt;Speed comparison with GNU sed&lt;a class="zola-anchor" href="#speed-comparison-with-gnu-sed"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another advantage of &lt;code&gt;rg&lt;/code&gt; is that it is likely to be faster than &lt;code&gt;sed&lt;/code&gt;. See &lt;a href="https://blog.burntsushi.net/ripgrep/"&gt;ripgrep benchmark with other grep implementations&lt;/a&gt; by the author for a methodological detailed analysis and insights.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# for small files, initial processing time of rg is a large component
&lt;/span&gt;&lt;span&gt;$ time echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aba' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/a/b/g' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; f1
&lt;/span&gt;&lt;span&gt;real	0m0.002s
&lt;/span&gt;&lt;span&gt;$ time echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'aba' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span&gt; rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'b' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; f2
&lt;/span&gt;&lt;span&gt;real	0m0.007s
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# for larger files, rg is likely to be faster
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 6.2M sample ASCII file
&lt;/span&gt;&lt;span&gt;$ wget &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'https://norvig.com/big.txt'
&lt;/span&gt;&lt;span&gt;$ time &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;LC_ALL&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;C&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\bcat\b/dog/g'&lt;/span&gt;&lt;span&gt; big.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; f1
&lt;/span&gt;&lt;span&gt;real	0m0.060s
&lt;/span&gt;&lt;span&gt;$ time rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\bcat\b' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'dog'&lt;/span&gt;&lt;span&gt; big.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; f2
&lt;/span&gt;&lt;span&gt;real	0m0.048s
&lt;/span&gt;&lt;span&gt;$ diff &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s f1 f2
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Files&lt;/span&gt;&lt;span&gt; f1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;and&lt;/span&gt;&lt;span&gt; f2 are identical
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# nearly 8 times faster!!
&lt;/span&gt;&lt;span&gt;$ time &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;LC_ALL&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;C&lt;/span&gt;&lt;span&gt; sed &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;E &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'s/\b(\w+)(\s+\1)+\b/\1/g'&lt;/span&gt;&lt;span&gt; big.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; f1
&lt;/span&gt;&lt;span&gt;real	0m0.725s
&lt;/span&gt;&lt;span&gt;$ time rg &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;no&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;unicode &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;--&lt;/span&gt;&lt;span&gt;passthru &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;wP &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(\w+)(\s+\1)+' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'$1'&lt;/span&gt;&lt;span&gt; big.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; f2
&lt;/span&gt;&lt;span&gt;real	0m0.093s
&lt;/span&gt;&lt;span&gt;$ diff &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s f1 f2
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Files&lt;/span&gt;&lt;span&gt; f1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;and&lt;/span&gt;&lt;span&gt; f2 are identical
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="other-alternatives-for-sed"&gt;Other alternatives for sed&lt;a class="zola-anchor" href="#other-alternatives-for-sed"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://unix.stackexchange.com/questions/112023/how-can-i-replace-a-string-in-a-files/251742#251742"&gt;rpl&lt;/a&gt; — search and replace tool, has interesting options like interactive mode and recursive mode&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/chmln/sd"&gt;sd&lt;/a&gt; — simple search and replace, implemented in Rust&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.perl.org/"&gt;perl&lt;/a&gt; and &lt;a href="https://www.ruby-lang.org/en/"&gt;ruby&lt;/a&gt; — programming languages with excellent command line support&lt;/li&gt;
&lt;/ul&gt;</description><author>learnbyexample</author><pubDate>Thu, 09 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/substitution-with-ripgrep/</guid></item><item><title>Creating GUI Applications with wxPython - book review</title><link>https://learnbyexample.github.io/python-gui-book-review/</link><description>&lt;p align="center"&gt;&lt;img alt="GUI example" src="/images/python_gui/GUI_example.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo Credit: &lt;a href="https://www.pexels.com/photo/apple-computer-desk-devices-326501/"&gt;Tranmautritam&lt;/a&gt; on &lt;a href="https://www.pexels.com/"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;p&gt;I've always wanted to create nice looking, useful GUI applications over the years. And I've given up most of the time as the programming seemed too difficult for me and GUI requires at least some level of design skills. I only managed to grit through one Android app for over a year as it was a dream game from school days and I had loads of free time having quit my job. At the end of it though, I had a spaghetti mess of several 1000+ lines programs and a strong aversion to Java and object oriented programming. Part of the reason is that I didn't try to learn in a formal way, just started from a tutorial closest to the game I wanted to do.&lt;/p&gt;
&lt;p&gt;Several years later, here I am, trying my hand with GUI again. I have several small to medium scale apps in mind to implement and hopefully I'll avoid previous mistakes, especially feature creep. When I saw &lt;a href="https://twitter.com/driscollis/status/1109106540160733184"&gt;this tweet from Mike Driscoll&lt;/a&gt;, I took up the offer. I got a free book in exchange for reviewing &lt;a href="https://www.blog.pythonlibrary.org/2019/05/08/creating-gui-applications-with-wxpython-now-available/"&gt;Creating GUI Applications with wxPython&lt;/a&gt;. The book is currently on sale till May 15. Having to review has served as an extra incentive to read the book regularly, and so far I'm quite satisfied to have done so.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="book cover" src="/images/python_gui/wxPython_book_cover.png" /&gt;&lt;/p&gt;
&lt;p&gt;I hadn't heard of &lt;a href="https://wxpython.org"&gt;wxPython&lt;/a&gt; before this book. When it comes to GUI in Python, I knew about &lt;code&gt;tkinter&lt;/code&gt; which comes by default with standard libary, &lt;a href="https://kivy.org"&gt;Kivy&lt;/a&gt;, &lt;a href="https://www.pygame.org"&gt;Pygame&lt;/a&gt; and &lt;a href="https://pypi.org/project/PyQt5/"&gt;PyQt5&lt;/a&gt;. This book starts with an introduction to &lt;code&gt;wxPython&lt;/code&gt; and then dives into project-based approach. I've finished half the chapters so far, covering four project concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Image viewer&lt;/li&gt;
&lt;li&gt;Database viewer and editor&lt;/li&gt;
&lt;li&gt;Calculator&lt;/li&gt;
&lt;li&gt;Archiver&lt;/li&gt;
&lt;/ul&gt;
&lt;p align="center"&gt;&lt;img alt="calculator" src="/images/python_gui/calculator.png" /&gt;&lt;/p&gt;
&lt;p&gt;Rest of the chapters cover these topics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MP3 tag editor&lt;/li&gt;
&lt;li&gt;Image application using NASA's API&lt;/li&gt;
&lt;li&gt;PDF merger/splitter&lt;/li&gt;
&lt;li&gt;File search&lt;/li&gt;
&lt;li&gt;FTP application&lt;/li&gt;
&lt;li&gt;XML editor&lt;/li&gt;
&lt;li&gt;Distributing your application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are also a couple of appendix chapters.&lt;/p&gt;
&lt;p&gt;As mentioned in book's introduction, you definitely need to be comfortable with Python classes before you start this book. The code used in the book is also available from &lt;a href="https://github.com/driscollis/applications_with_wxpython"&gt;GitHub repo&lt;/a&gt;, but I highly recommend to type them manually.&lt;/p&gt;
&lt;p&gt;The project nature also means that after chapter 3, you could probably skip chapters you are not interested in. For example, I didn't pay too much attention to database chapters as I don't have much experience with databases. Each project is described and shown step by step. The projects could be run at different stages as well - playing around with the GUI at those points helps in mapping code-to-output, as well as to experiment different settings.&lt;/p&gt;
&lt;p&gt;All in all, I would highly recommend this book for those wanting to start coding GUI applications in Python. And please do contact the author to let him know your feedback or if you have any clarifications. Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 09 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/python-gui-book-review/</guid></item><item><title>Python for maths</title><link>https://learnbyexample.github.io/python-for-maths/</link><description>&lt;p align="center"&gt;&lt;img alt="sample plot" src="/images/python_for_maths/gravitational_plot.png" /&gt;&lt;/p&gt;
&lt;p&gt;The above image was generated using &lt;code&gt;matplotlib&lt;/code&gt; courtesy code provided by &lt;a href="https://github.com/doingmathwithpython/code/blob/master/chapter2/Chapter2.ipynb"&gt;Doing Math with Python&lt;/a&gt; book.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;
&lt;p&gt;Last month, I had an opportunity to conduct beginner Python workshop for maths department students in an arts and science college. It was a great experience and I had my first taste of how Python could be applied for mathematical problems. Presented here are bunch of useful links that I gathered as resources for the students. &lt;/p&gt;
&lt;h1 id="documentation-links"&gt;Documentation links&lt;a class="zola-anchor" href="#documentation-links"&gt;🔗&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/"&gt;docs.python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.scipy.org/doc/"&gt;numpy and scipy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://matplotlib.org/stable/api/index.html"&gt;matplotlib&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="books-and-courses"&gt;Books and courses&lt;a class="zola-anchor" href="#books-and-courses"&gt;🔗&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://doingmathwithpython.github.io/"&gt;Doing Math with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://maths-with-python.readthedocs.io/en/latest/"&gt;Maths with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/drvinceknight/Python-Mathematics-Handbook"&gt;Doing mathematics with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jrjohansson/scientific-python-lectures"&gt;Lectures on scientific computing with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://greenteapress.com/thinkdsp/html/index.html"&gt;Digital Signal Processing in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.coursera.org/learn/audio-signal-processing"&gt;Audio Signal Processing for Music Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.coursera.org/learn/what-is-a-proof"&gt;Mathematical Thinking in Computer Science&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="python-for-beginners"&gt;Python for beginners&lt;a class="zola-anchor" href="#python-for-beginners"&gt;🔗&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://automatetheboringstuff.com/"&gt;Automate the Boring Stuff with Python&lt;/a&gt; — teaches you programming concepts and then shows how to automate everyday problems&lt;/li&gt;
&lt;li&gt;&lt;a href="https://runestone.academy/ns/books/published/thinkcspy/index.html"&gt;How to Think Like a Computer Scientist: Interactive Edition&lt;/a&gt; — inspired by Think Python&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thepythoncodingbook.com/"&gt;The Python Coding Book&lt;/a&gt; — friendly, relaxed programming book for beginners&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gto76.github.io/python-cheatsheet/"&gt;Comprehensive Python cheatsheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythontutor.com/visualize.html#mode=edit"&gt;Pythontutor: Visualize code execution&lt;/a&gt; — also has example codes and ability to share sessions&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2019/06/23/a-few-debugging-resources/"&gt;What does debugging a program look like?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ryanstutorials.net/problem-solving-skills/"&gt;Problem solving skills&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my &lt;a href="https://learnbyexample.github.io/py_resources/"&gt;comprehensive Python learning resources&lt;/a&gt; for more.&lt;/p&gt;
&lt;br /&gt;
&lt;h1 id="numpy-scipy-matplotlib"&gt;numpy, scipy, matplotlib&lt;a class="zola-anchor" href="#numpy-scipy-matplotlib"&gt;🔗&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.labri.fr/perso/nrougier/from-python-to-numpy/"&gt;From Python to Numpy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nbviewer.org/github/vlad17/np-learn/blob/master/presentation.ipynb"&gt;Advanced Numpy Techniques&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/donnemartin/data-science-ipython-notebooks"&gt;List of data science Python notebooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scipy-lectures.org/"&gt;Scipy Lecture Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rougier/scientific-visualization-book"&gt;Scientific Visualization: Python + Matplotlib&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.python-graph-gallery.com/"&gt;Collection of charts with Matplotlib, Seaborn, Plotly, etc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ipgp.github.io/scientific_python_cheat_sheet/"&gt;Scientific Python Cheatsheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://animatplot.readthedocs.io/en/stable/tutorial/getting_started.html"&gt;animatplot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://alimanfoo.github.io/2017/01/23/go-faster-python.html"&gt;benchmarking, profiling and optimising Python code&lt;/a&gt; - includes discussion on numpy&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h1 id="more-resources"&gt;More resources&lt;a class="zola-anchor" href="#more-resources"&gt;🔗&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mathoverflow.net/questions/308797/what-programming-language-should-a-professional-mathematician-know"&gt;What programming language should a professional mathematician know?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.python.org/moin/BeginnersGuide/Mathematics"&gt;Python Beginners Guide for Mathematics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jupyter/jupyter/wiki#a-gallery-of-interesting-jupyter-notebooks"&gt;Interesting Jupyter Notebooks on mathematics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/learnbyexample/curated_resources/blob/master/Education.md#maths"&gt;Maths curated resource links&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sagemath.org/"&gt;Sagemath&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://octave.org/"&gt;GNU Octave&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>learnbyexample</author><pubDate>Thu, 09 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/python-for-maths/</guid></item><item><title>A short and satisfying bug hunt</title><link>https://learnbyexample.github.io/a-short-and-satisfying-bug-hunt/</link><description>&lt;h2 id="the-surprise"&gt;The surprise&lt;a class="zola-anchor" href="#the-surprise"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So, a pleasant surprise awaited me last Sunday. As is my usual habit, I opened my &lt;a href="https://github.com/learnbyexample"&gt;github&lt;/a&gt; account after breakfast to see if I've got any sudden spurt in traffic. And as usual, things were normal. Except for the blue notification, which was rare. I hoped it wasn't a silly pull request and thankfully it was a &lt;a href="https://github.com/learnbyexample/Command-line-text-processing/issues/24"&gt;new issue&lt;/a&gt; that was opened.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;
&lt;p&gt;I gave the issue a cursory glance and wrongly guessed it was probably some line ending issue (user was on Windows OS). As someone who has seen plenty of bugs in previous job, I wasn't ruling out anything though. I first cloned the repo so as to try to recreate the working environment without possible interference from my local working copy. As the user had provided detailed information while opening the issue, I was able to quickly replicate it. Sure enough, I was seeing the same problem. I only wondered why it wasn't brought to my attention before. Either past users chose not to or things weren't interesting enough to reach that far in the exercises.&lt;/p&gt;
&lt;h2 id="creating-minimal-failing-case"&gt;Creating minimal failing case&lt;a class="zola-anchor" href="#creating-minimal-failing-case"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As I had written the &lt;a href="https://github.com/learnbyexample/Command-line-text-processing/blob/master/exercises/GNU_grep/solve"&gt;solution checker script&lt;/a&gt; about 2 years back, the script looked alien. Right from cloning the repo, I had to fight the urge to improve things. By the time I spotted the issue, all such fantasies were thrown out. Replaced by a todo note to &lt;em&gt;someday&lt;/em&gt; write automated testing script to check that my script is indeed working properly for all the exercises.&lt;/p&gt;
&lt;p&gt;To put it simply, the role of &lt;code&gt;solve&lt;/code&gt; script is to check if the previous command executed by the user solves the current exercise question. To do so, the script gets the previous command from history and compares the output of that command and a reference solution present in the exercise directory. Sounds simple right? Yeah, I thought so too. I do remember testing few cases before I first published it and no one had submitted an issue so far. So, why was it failing now?&lt;/p&gt;
&lt;p&gt;As mentioned before, I thought it could be some weird line ending issue. But that was effectively ruled out as it was failing for me as well on Linux. Still, I did check for funny characters with &lt;code&gt;cat -A&lt;/code&gt;. Nope, no issues there.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; grep&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -o &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'^[^=]*'&lt;/span&gt;&lt;span&gt; sample.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;a[2]
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;foo_bar
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;appx_pi
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;greeting
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;food[4]
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;b[0][1]
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; source ../solve&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -s
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;---------------------------------------------
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Mismatch&lt;/span&gt;&lt;span&gt; for question 1:
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Expected&lt;/span&gt;&lt;span&gt; output is:
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;a[2]
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;foo_bar
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;appx_pi
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;greeting
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;food[4]
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;b[0][1]
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;---------------------------------------------
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expected output was same as output for submitted solution. So, why is the script failing? I remember passing the script through &lt;a href="https://www.shellcheck.net/"&gt;shellcheck&lt;/a&gt; but still checked it again. No progress. So, then I started by trying to debug the most likely culprit from terminal before trying to debug the whole script. Luckily, that turned out well.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cat sample.txt 
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;a[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'sample string'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;foo_bar&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;4232
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;appx_pi&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;3.14
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;greeting&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;Hi  there		have a nice   day&amp;quot;
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;food[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;dosa&amp;quot;
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;b[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;][&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;42
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$ &lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# say what??
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$ &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;[&lt;/span&gt;&lt;span&gt;[ $(&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;eval &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;command grep -o '^[^=]*' sample.txt&amp;quot;&lt;/span&gt;&lt;span&gt;) == \
&lt;/span&gt;&lt;span&gt;&amp;gt;    $(&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;eval &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;command grep -o '^[^=]*' sample.txt&amp;quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;]&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|| &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Not fine'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Not&lt;/span&gt;&lt;span&gt; fine
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$ &lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# after some attempts, I tried a command that won't have
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$ &lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# any [] characters in the output
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$ &lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# Eureka!
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; [[ $(&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;eval &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;command grep 'bar' sample.txt&amp;quot;&lt;/span&gt;&lt;span&gt;) == \
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;    &lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;eval &lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;quot;command grep 'bar' sample.txt&amp;quot;&lt;/span&gt;&lt;span&gt;) ]] &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|| &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Not fine'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$ &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;[&lt;/span&gt;&lt;span&gt;[ foo == foo &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;]&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;amp;&amp;amp; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fine'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;fine
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$ &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;[&lt;/span&gt;&lt;span&gt;[ &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a[5]'&lt;/span&gt;&lt;span&gt; == a[5&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;]&lt;/span&gt;&lt;span&gt; ]] &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|| &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Not fine'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Not&lt;/span&gt;&lt;span&gt; fine
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$ &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;[&lt;/span&gt;&lt;span&gt;[ &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a[5]'&lt;/span&gt;&lt;span&gt; == &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a[5]' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;]&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;amp;&amp;amp; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'fine'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;fine
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Having a minimal failing case from terminal was a relief. I tried &lt;code&gt;set -x&lt;/code&gt; but that didn't light a bulb either. Finally, somehow I thought perhaps characters in the output was causing the issue and when &lt;code&gt;[]&lt;/code&gt; characters were not present, the comparison worked as expected.&lt;/p&gt;
&lt;p&gt;I did think quoting could be the issue, but dismissed it at first as both sides of comparison had the same command. Then my recent experience from reviewing &lt;a href="https://www.packtpub.com/application-development/command-line-fundamentals"&gt;Command Line Fundamentals&lt;/a&gt; book came in handy. I remembered that if quotes aren't used on RHS of comparison operator, it is treated as &lt;code&gt;glob&lt;/code&gt; matching instead of string matching. Phew.&lt;/p&gt;
&lt;h2 id="tl-dr"&gt;TL;DR&lt;a class="zola-anchor" href="#tl-dr"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Always &lt;a href="https://unix.stackexchange.com/questions/131766/why-does-my-shell-script-choke-on-whitespace-or-other-special-characters"&gt;quote strings in bash&lt;/a&gt; unless you have a very good reason for not using them.&lt;/p&gt;
&lt;p&gt;After adding double quotes around the command substitution commands, the script worked as expected. I thanked the user for opening the issue. And then informed the author for cli fundamentals book as well.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 09 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/a-short-and-satisfying-bug-hunt/</guid></item><item><title>Python tip 23: map, filter and reduce</title><link>https://learnbyexample.github.io/tips/python-tip-23/</link><description>&lt;p&gt;Many operations on container objects can be defined in terms of these three concepts. For example, if you want to sum the square of all even numbers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;separating out even numbers is &lt;strong&gt;Filter&lt;/strong&gt; (i.e. only elements that satisfy a condition are retained)&lt;/li&gt;
&lt;li&gt;square of such numbers is &lt;strong&gt;Map&lt;/strong&gt; (i.e. each element is transformed by a mapping function)&lt;/li&gt;
&lt;li&gt;final sum is &lt;strong&gt;Reduce&lt;/strong&gt; (i.e. you get one value out of multiple values)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One or more of these operations may be absent depending on the problem statement. Each of these steps will be first illustrated using straightforward code and then the equivalent list comprehensions (and generator expressions) are also shown.&lt;/p&gt;
&lt;p&gt;The first of these steps could look like:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="background-color: #562d56bf; color: #f8f8f8;"&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;get_evens&lt;/span&gt;&lt;span&gt;(iterable):
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span&gt;op &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[]
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;iterable:
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...         &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;% &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;== &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...             &lt;/span&gt;&lt;span&gt;op.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;append&lt;/span&gt;&lt;span&gt;(n)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span&gt;op
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;nums &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;53&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;32&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;11&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;get_evens&lt;/span&gt;&lt;span&gt;(nums)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;32&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;nums &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;% &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;== &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;32&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second step could be:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="background-color: #562d56bf; color: #f8f8f8;"&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sqr_evens&lt;/span&gt;&lt;span&gt;(iterable):
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span&gt;op &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;[]
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;iterable:
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...         &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;% &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;== &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...             &lt;/span&gt;&lt;span&gt;op.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;append&lt;/span&gt;&lt;span&gt;(n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span&gt;n)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span&gt;op
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sqr_evens&lt;/span&gt;&lt;span&gt;(nums)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;10000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1024&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;[n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;nums &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;% &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;== &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;10000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1024&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, the third step could be:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="background-color: #562d56bf; color: #f8f8f8;"&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sum_sqr_evens&lt;/span&gt;&lt;span&gt;(iterable):
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;iterable:
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...         &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;% &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;== &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...             &lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+= &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span&gt;n
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;...     &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span&gt;total
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;... 
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sum_sqr_evens&lt;/span&gt;&lt;span&gt;(nums)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;11028
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;sum&lt;/span&gt;&lt;span&gt;(n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;* &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;nums &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;% &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;== &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;11028
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; You can also use &lt;a href="https://docs.python.org/3/library/functions.html#map"&gt;map()&lt;/a&gt;, &lt;a href="https://docs.python.org/3/library/functions.html#filter"&gt;filter()&lt;/a&gt; and &lt;a href="https://docs.python.org/3/library/functools.html#functools.reduce"&gt;functools.reduce()&lt;/a&gt; for such problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 07 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/python-tip-23/</guid></item><item><title>A new approach to domain ranking</title><link>https://www.marginalia.nu/log/73-new-approach-to-ranking/</link><description>This is a very brief post announcing a fascinating discovery.
It appears to be possible to use the cosine similarity approach powering explore2.marginalia.nu as a substitute for the link graph in an eigenvector-based ranking algorithm (i.e. PageRank).
The original PageRank algorithm can be conceptualized as a simulation of where a random visitor would end up if they randomly clicked links on websites. With this model in mind, the modification replaces the link-clicking with using explore2 for navigation.</description><author>Weblog on marginalia.nu</author><pubDate>Mon, 06 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/73-new-approach-to-ranking/</guid></item><item><title>Lifelong purchases</title><link>https://bytepawn.com/lifelong-purchases.html</link><description>&lt;p&gt;What are some of my buy-it-for-life purchases?&lt;br /&gt;&lt;br /&gt; &lt;img alt="." src="/images/tm-notebook.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 05 Feb 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/lifelong-purchases.html</guid></item><item><title>How to Find Podcasts That Have Been Deleted</title><link>https://www.swyx.io/how-to-find-podcasts-that-have-been-deleted</link><description>&lt;p&gt;TLDR, https://megaphone.spotify.com/ rehosts all mp3s&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Sat, 04 Feb 2023 21:13:40 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/how-to-find-podcasts-that-have-been-deleted</guid></item><item><title>What I'm up to - February 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-february-2023</link><description>&lt;div class="prose"&gt;
  &lt;div&gt;
&lt;strong&gt;✨ What I've been up to&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;Hello from Cozumel, Mexico! It's 80° F here - a welcome break from the Chicago Winter. &lt;br /&gt;&lt;br /&gt;Since last Summer, I've been taking a "solo week" trip every quarter as a creative recharge. This trip to Cozumel is one of these solo weeks, and I've been spending a lot of time in cafes exploring new &lt;a href="https://contraption.co"&gt;Contraption Company&lt;/a&gt; projects. It's a ritual I plan to continue - I've already scheduled solo weeks over the next year to Copenhagen, Mexico City, And Tokyo.&lt;br /&gt;&lt;br /&gt;Postcard continues to do well. I &lt;a href="https://updates.postcard.page/posts/new-on-postcard-featured-posts-email-list-imports-and-more"&gt;built new features&lt;/a&gt; and did a &lt;a href="https://gosolo.subkit.com/postcard"&gt;short interview with GoSolo&lt;/a&gt;.  &lt;br /&gt;&lt;br /&gt;&lt;a href="https://www.robinsloan.com/lab/attention-router/"&gt;Robin Sloan mentioned Postcard in his newsletter&lt;/a&gt;. I enjoyed Robin's book &lt;a href="https://en.wikipedia.org/wiki/Mr._Penumbra%27s_24-Hour_Bookstore"&gt;Mr. Penumbra's 24-Hour Bookstore&lt;/a&gt; while living in the San Francisco neighborhood where the story is set.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;🤔 Things to share&lt;/strong&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Articles: &lt;/strong&gt;&lt;a href="https://www.lesswrong.com/posts/yJfBzcDL9fBHJfZ6P/nash-equilibria-and-schelling-points"&gt;Nash Equilibria and Schelling Points&lt;/a&gt;. &lt;a href="https://kwokchain.com/2020/01/23/underutilized-fixed-assets/?ref=bits-about-money"&gt;Underutilized fixed assets&lt;/a&gt; and how marketplaces inevitably shift from retail to professional sellers. &lt;a href="https://www.nytimes.com/interactive/2023/01/23/magazine/cal-newport-interview.html"&gt;The Digital Workplace Is Designed to Bring You Down&lt;/a&gt;. &lt;a href="https://www.thedrive.com/the-war-zone/navy-pilot-who-secretly-killed-four-migs-on-one-mission-finally-recognized"&gt;The uncovering of a real Tom Cruise-style dogfight between US and Soviet fighters during the Korean War&lt;/a&gt;. All of &lt;a href="https://www.bitsaboutmoney.com/"&gt;Bits about Money&lt;/a&gt; (like &lt;a href="https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine"&gt;Money Stuff&lt;/a&gt; for financial systems). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books: &lt;/strong&gt;Intrigued recently by the Nordics, I read every book by &lt;a href="https://www.amazon.com/stores/Kay-Xander-Mellish/author/B00LUNKXPS?ref=ap_rdr&amp;amp;store_ref=ap_rdr&amp;amp;isDramIntegrated=true&amp;amp;shoppingPortalEnabled=true"&gt;Kay Xander Mellish&lt;/a&gt; - even a guide to American culture for Danes (!). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trends: &lt;/strong&gt;&lt;a href="https://www.starlink.com/"&gt;Starlink&lt;/a&gt; is popping up on Airbnb listings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Listening: &lt;/strong&gt;&lt;a href="https://www.amazon.com/gp/product/B0899G43R5/ref=ppx_yo_dt_b_d_asin_title_o09aud_?ie=UTF8&amp;amp;psc=1"&gt;Creativity by John Cleese&lt;/a&gt;. &lt;a href="https://www.youtube.com/watch?v=2BdBfsXbST8"&gt;A 2019 interview between Lex Friedman and Donald Knuth&lt;/a&gt; (personal takeaway: seek simplicity in tools I build, but embrace complexity in tools I use).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fun fact: &lt;/strong&gt;&lt;a href="https://www.thedrive.com/the-war-zone/rare-look-at-nuclear-reactor-inside-russian-ballistic-missile-submarine?utm_term=The%20War%20Zone_Wire_01.26.23&amp;amp;utm_campaign=The%20War%20Zone_Wire_Actives_Dynamic&amp;amp;utm_source=Sailthru&amp;amp;utm_medium=email"&gt;Many Russian submarines have a sauna&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps&lt;/strong&gt;: &lt;a href="https://obsidian.md/"&gt;Obsidian&lt;/a&gt; replaced Notion as my personal organization tool. &lt;a href="https://marco.org/apps#quitter"&gt;Quitter&lt;/a&gt; closes my calendar, messages, and email after 5 minutes of inactivity. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cool infrastructure&lt;/strong&gt;: &lt;a href="https://www.youtube.com/watch?v=2klS1diYMWU"&gt;Chicago Deep Tunnel project&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffee I'm drinking:&lt;/strong&gt; &lt;a href="https://metriccoffee.com/products/modicum-costa-rica-la-bandera?variant=40080454221911"&gt;Metric Costa Rica La Bandera Geisha&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;📫 &lt;strong&gt;What I'm up to this month&lt;/strong&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Writing a post about the AI hype cycle&lt;/li&gt;
&lt;li&gt;Launching some new Postcard features&lt;/li&gt;
&lt;li&gt;Researching online communities&lt;/li&gt;
&lt;li&gt;Prototyping a new project&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;
&lt;strong&gt;📍 Where I'll be &lt;/strong&gt;&lt;em&gt;(Let me know if we overlap!)&lt;/em&gt;
&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Feb 18-24: 🗽 NYC&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;figure class="attachment attachment--preview attachment--jpeg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBc1kyIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c3210e075a040dc791b46d647e12217284e5423e/5D1904B5-C476-4A67-8E54-966BAF8DA90B_1_201_a.jpeg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Current office&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Sat, 04 Feb 2023 02:26:42 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-february-2023</guid></item><item><title>Python Regex Surprises</title><link>https://learnbyexample.github.io/python-regex-surprises/</link><description>&lt;p&gt;In this post, you'll find a few regular expression examples that might surprise you. Some are Python specific and some are applicable to other regex flavors as well. To make it more interesting, these are framed as questions for you to ponder upon. Answers are hidden by default.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Python Regex Surprises" src="/images/python_regex_surprises.png" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;i&gt;Poster created using &lt;a href="https://www.canva.com/"&gt;Canva&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; If you are not familiar with regular expressions, check out my &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebook.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="vs-z"&gt;$ vs \Z&lt;a class="zola-anchor" href="#vs-z"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Are the &lt;code&gt;$&lt;/code&gt; and &lt;code&gt;\Z&lt;/code&gt; anchors equivalent?&lt;/p&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;&lt;code&gt;$&lt;/code&gt; can match both the end of string and just before &lt;code&gt;\n&lt;/code&gt; if it is the last character. &lt;code&gt;\Z&lt;/code&gt; will only match the end of string.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;greeting &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'hi there&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;have a nice day&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;day&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;$&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, greeting))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;day&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;$&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, greeting))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;day&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\Z&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, greeting))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;False
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;day&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\Z&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, greeting))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="slicing-vs-start-and-end-arguments"&gt;Slicing vs start and end arguments&lt;a class="zola-anchor" href="#slicing-vs-start-and-end-arguments"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Did you know that you can specify &lt;em&gt;start&lt;/em&gt; and &lt;em&gt;end&lt;/em&gt; index arguments for compiled methods?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Pattern.search(string[, pos[, endpos]])&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, here's a conundrum:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;word_pat &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;compile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\A&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;at&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(word_pat.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cater'&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;:]))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# what will be the output?
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(word_pat.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cater'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
Specifying a greater than &lt;code&gt;0&lt;/code&gt; start index when using &lt;code&gt;\A&lt;/code&gt; is always going to return &lt;code&gt;False&lt;/code&gt;. This is because, as far as the &lt;code&gt;search()&lt;/code&gt; method is concerned, only the search space has been narrowed — the anchor positions haven't changed. When slicing is used, you are creating an entirely new string object with new anchor positions.
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="do-and-match-after-the-last-newline"&gt;Do ^ and $ match after the last newline?&lt;a class="zola-anchor" href="#do-and-match-after-the-last-newline"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you use the &lt;code&gt;re.MULTILINE&lt;/code&gt; flag, the &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; anchors will match at the start and end of every input line. Question is, will they also match after a newline character at the end of the input?&lt;/p&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;Yes, they will both match after the last newline character.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;(?m)^&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple '&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;2&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span&gt;apple &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;apple &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span&gt;apple 
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;(?m)$&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;' banana'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;2&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\n&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1 &lt;/span&gt;&lt;span&gt;banana
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2 &lt;/span&gt;&lt;span&gt;banana
&lt;/span&gt;&lt;span&gt; banana
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="word-boundary-vs-lookarounds"&gt;Word boundary vs lookarounds&lt;a class="zola-anchor" href="#word-boundary-vs-lookarounds"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;\b..\b&lt;/code&gt; is same as &lt;code&gt;(?&amp;lt;!\w)..(?!\w)&lt;/code&gt; — True or False?&lt;/p&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;False! &lt;code&gt;\b&lt;/code&gt; matches both the start and end of word locations. In the below example, &lt;code&gt;\b..\b&lt;/code&gt; doesn't necessarily mean that the first &lt;code&gt;\b&lt;/code&gt; will match only the start of word location and the second &lt;code&gt;\b&lt;/code&gt; will match only the end of word location. They can be any combination! For example, &lt;code&gt;I&lt;/code&gt; followed by space in the input string here is using the start of word location for both the conditions. Similarly, space followed by &lt;code&gt;2&lt;/code&gt; is using the end of word location for both the conditions.&lt;/p&gt;
&lt;p&gt;In contrast, the negative lookarounds version ensures that there are no word characters around any two characters. Also, such assertions will always be satisfied at the start of string and the end of string respectively. But &lt;code&gt;\b&lt;/code&gt; depends on the presence of word characters. For example, &lt;code&gt;!&lt;/code&gt; at the end of the input string here matches the lookaround assertion but not word boundary.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;ip &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'I have 12, he has 2!'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;..&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{&lt;/span&gt;&lt;span style="text-decoration: underline; font-style: italic; color: #d2a8a1;"&gt;\g&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;lt;0&amp;gt;}'&lt;/span&gt;&lt;span&gt;, ip)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{I }have &lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{12}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;{, }&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{he}&lt;/span&gt;&lt;span style="color: #d07711;"&gt; has{ 2}!'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;!\w&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;..&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?!\w&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{&lt;/span&gt;&lt;span style="text-decoration: underline; font-style: italic; color: #d2a8a1;"&gt;\g&lt;/span&gt;&lt;span style="color: #d07711;"&gt;&amp;lt;0&amp;gt;}'&lt;/span&gt;&lt;span&gt;, ip)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'I have &lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{12}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;, &lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{he}&lt;/span&gt;&lt;span style="color: #d07711;"&gt; has {2!}'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="undefined-escape-sequences"&gt;Undefined escape sequences&lt;a class="zola-anchor" href="#undefined-escape-sequences"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you use undefined escape sequences like &lt;code&gt;\e&lt;/code&gt;, will you get an error or will it match the unescaped character (&lt;code&gt;e&lt;/code&gt; for this example`)?&lt;/p&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;Python raises an exception for escape sequences that are not defined. Apart from sequences defined for character sets (for example &lt;code&gt;\d&lt;/code&gt;, &lt;code&gt;\w&lt;/code&gt;, &lt;code&gt;\s&lt;/code&gt;, etc), these are allowed: &lt;code&gt;\a \b \f \n \N \r \t \u \U \v \x \\&lt;/code&gt; where &lt;code&gt;\b&lt;/code&gt; means backspace only in character classes. Also, &lt;code&gt;\u&lt;/code&gt; and &lt;code&gt;\U&lt;/code&gt; are valid only in Unicode patterns.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;dog'&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;bool&lt;/span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\c&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'cat&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\t&lt;/span&gt;&lt;span style="color: #d07711;"&gt;dog'&lt;/span&gt;&lt;span&gt;))
&lt;/span&gt;&lt;span&gt;re.error: bad escape \&lt;/span&gt;&lt;span style="background-color: #562d56bf; color: #f8f8f8;"&gt;c at position 0&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="using-octal-and-hexadecimal-escapes-in-the-replacement-section"&gt;Using octal and hexadecimal escapes in the replacement section&lt;a class="zola-anchor" href="#using-octal-and-hexadecimal-escapes-in-the-replacement-section"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In string literals, you can use octal, hexadecimal and unicode escapes to represent a character. For example, &lt;code&gt;'\174'&lt;/code&gt; is same as using &lt;code&gt;'|'&lt;/code&gt;. Do you know which of these escapes you can use inside raw strings in the replacement section of the &lt;code&gt;sub()&lt;/code&gt; function?&lt;/p&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;Only octal escapes are allowed inside raw strings in the replacement section. If you are otherwise not using the &lt;code&gt;\&lt;/code&gt; character, then using normal strings in the replacement section is preferred as it will also allow hexadecimal and unicode escapes.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;,&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\x&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;7c&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;re.error: bad escape \&lt;/span&gt;&lt;span style="background-color: #562d56bf; color: #f8f8f8;"&gt;x at position 0&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;,&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\17&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;4&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1|2'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;,&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\x7c&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1|2'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I feel like it would have been rather better if octal escapes were also not allowed. That would have allowed us to use &lt;code&gt;\0&lt;/code&gt; instead of &lt;code&gt;\g&amp;lt;0&amp;gt;&lt;/code&gt; for backreferencing the entire matched portion in the replacement section.&lt;/p&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="using-escape-sequences-for-metacharacters"&gt;Using escape sequences for metacharacters&lt;a class="zola-anchor" href="#using-escape-sequences-for-metacharacters"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the search section, if you use an escape (for example, &lt;code&gt;\x7c&lt;/code&gt; to represent the &lt;code&gt;|&lt;/code&gt; character), will it behave as the alternation metacharacter or match it literally?&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;2&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;3&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'5'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'12|30'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'15|50'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# what will be the output?
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;2&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\x&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;7c3&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'5'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'12|30'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;The output will be &lt;code&gt;'150'&lt;/code&gt; since escapes will be treated literally.&lt;/p&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="empty-matches"&gt;Empty matches&lt;a class="zola-anchor" href="#empty-matches"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You are likely to have come across this before:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# what will be the output?
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;{&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\g&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;lt;0&amp;gt;}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;',cat,tiger'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;See also &lt;a href="https://www.regular-expressions.info/zerolength.html"&gt;Zero-Length Matches&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# there is an extra empty string match at the end of matches
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;{&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\g&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;lt;0&amp;gt;}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;',cat,tiger'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;,&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{cat}{}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;,&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{tiger}{}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*+&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;{&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\g&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;lt;0&amp;gt;}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;',cat,tiger'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;,&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{cat}{}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;,&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{tiger}{}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# use lookarounds as a workaround
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;?&amp;lt;![&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;{&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\g&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;lt;0&amp;gt;}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;',cat,tiger'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;,&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{cat}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;,&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{tiger}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="can-quantifiers-be-grouped-out"&gt;Can quantifiers be grouped out?&lt;a class="zola-anchor" href="#can-quantifiers-be-grouped-out"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Similar to &lt;code&gt;a(b+c)d = abd+acd&lt;/code&gt; in maths, you get &lt;code&gt;a(b|c)d = abd|acd&lt;/code&gt; in regular expressions. &lt;code&gt;(a*|b*)&lt;/code&gt; is same as &lt;code&gt;(a|b)*&lt;/code&gt; — True or False?&lt;/p&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p align="center"&gt;&lt;img alt="Regexp grouping with quantifiers gotcha" src="/images/mini/regexp_gotcha_1.png" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;Railroad diagram created using &lt;a href="https://www.debuggex.com/"&gt;debuggex.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;False. Because &lt;code&gt;(a*|b*)&lt;/code&gt; will match only sequences like &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;aaa&lt;/code&gt;, &lt;code&gt;bb&lt;/code&gt;, &lt;code&gt;bbbbbbbb&lt;/code&gt;. But &lt;code&gt;(a|b)*&lt;/code&gt; can match mixed sequences like &lt;code&gt;ababbba&lt;/code&gt; too.&lt;/p&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="portion-captured-by-a-quantified-group"&gt;Portion captured by a quantified group&lt;a class="zola-anchor" href="#portion-captured-by-a-quantified-group"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This should be another familiar regex gotcha:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #7f8989;"&gt;# what will be the output?
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\A&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;,)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3}&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\1&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\2&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,4,5,6,7'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;Referring to the text matched by a capture group with a quantifier will give only the last match, not the entire match. You'll need an outer capture group to get the entire matched portion.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\A&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;,)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3}&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\1&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\2&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,4,5,6,7'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'3,(4),5,6,7'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\A&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;((?:&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;,)&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3}&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;,]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\1&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\2&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,4,5,6,7'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1,2,3,(4),5,6,7'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="character-combinations"&gt;Character combinations&lt;a class="zola-anchor" href="#character-combinations"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;\b[a-z](on|no)[a-z]\b&lt;/code&gt; is same as &lt;code&gt;\b[a-z][on]{2}[a-z]\b&lt;/code&gt; — True or False?&lt;/p&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;False. &lt;code&gt;[on]{2}&lt;/code&gt; will also match &lt;code&gt;oo&lt;/code&gt; and &lt;code&gt;nn&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;words &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'known mood know pony inns'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;a-z&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(?:on&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;|&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;no)&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;a-z&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, words)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'know'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'pony'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;a-z&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;][on]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{2}&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;a-z&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;\b&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, words)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'mood'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'know'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'pony'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'inns'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="greedy-vs-possessive"&gt;Greedy vs Possessive&lt;a class="zola-anchor" href="#greedy-vs-possessive"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Suppose you want to match integer numbers greater than or equal to &lt;code&gt;100&lt;/code&gt; where these numbers can optionally have leading zeros. Will the below code work? If not, what would you use instead?&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;numbers &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 314 001 12 00984'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# will this work?
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;No. You can either modify the pattern such that &lt;code&gt;0*&lt;/code&gt; won't interfere or use possessive quantifiers to prevent backtracking.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;numbers &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'42 314 001 12 00984'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# this solution fails because 0* and \d{3,} can both match leading zeros
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# and greedy quantifiers will give up characters to help overall RE succeed
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'001'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 0*+ is possessive, will never give back leading zeros
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*+&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{3,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# workaround if possessive isn't supported
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;0&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1-9&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;]\d&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;{2,}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, numbers)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'314'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'00984'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my blog post on &lt;a href="https://learnbyexample.github.io/python-regex-possessive-quantifier/"&gt;possessive quantifiers and atomic grouping&lt;/a&gt; for more examples, details about catastrophic backtracking and so on.&lt;/p&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="optional-flags-argument"&gt;Optional flags argument&lt;a class="zola-anchor" href="#optional-flags-argument"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Will the &lt;code&gt;sub()&lt;/code&gt; function in the code sample below match case insensitively or not?&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;key&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'KEY portkey oKey Keyed'&lt;/span&gt;&lt;span&gt;, re.I)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'KEY'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'key'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Key'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Key'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# what will be the output?
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;key&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\g&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;lt;0&amp;gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'KEY portkey oKey Keyed'&lt;/span&gt;&lt;span&gt;, re.I)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;You should always pass flags as a keyword argument. Using it as positional argument leads to a common mistake between &lt;code&gt;re.findall()&lt;/code&gt; and &lt;code&gt;re.sub()&lt;/code&gt; functions due to difference in their placement.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;re.findall(pattern, string, flags=0)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;re.sub(pattern, repl, string, count=0, flags=0)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; +&lt;/span&gt;&lt;span&gt;re.I
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# works because flags is the only optional argument for findall
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;key&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'KEY portkey oKey Keyed'&lt;/span&gt;&lt;span&gt;, re.I)
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'KEY'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'key'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Key'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Key'&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# wrong usage, but no error because re.I has a value of 2
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# so, this is same as specifying count=2
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;key&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\g&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;lt;0&amp;gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'KEY portkey oKey Keyed'&lt;/span&gt;&lt;span&gt;, re.I)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'KEY port(key) oKey Keyed'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# correct use of keyword argument
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;key&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\g&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;lt;0&amp;gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'KEY portkey oKey Keyed'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;flags&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span&gt;re.I)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(KEY) port(key) o(Key) (Key)ed'
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# alternatively, you can use inline flags to avoid this problem altogether
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;(?i)&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;key&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;(&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\g&lt;/span&gt;&lt;span style="color: #7c8f4c;"&gt;&amp;lt;0&amp;gt;)&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'KEY portkey oKey Keyed'&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(KEY) port(key) o(Key) (Key)ed'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="re-vs-regex-module-flags"&gt;re vs regex module flags&lt;a class="zola-anchor" href="#re-vs-regex-module-flags"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The third-party &lt;code&gt;regex&lt;/code&gt; module is handy for advanced features like subexpression calls, skipping matches and so on. Can you use &lt;code&gt;re&lt;/code&gt; module flag constants with the &lt;code&gt;regex&lt;/code&gt; module?&lt;/p&gt;
&lt;details&gt;&lt;i style="color: gray;"&gt;Click to view answer&lt;/i&gt;
&lt;p&gt;When using the flags argument with the &lt;code&gt;regex&lt;/code&gt; module, the constants should also be used from the &lt;code&gt;regex&lt;/code&gt; module.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; +&lt;/span&gt;&lt;span&gt;re.A
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;256
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt;&amp;gt;&amp;gt; +&lt;/span&gt;&lt;span&gt;regex.A
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;128
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, you can use inline flags to avoid such issues.&lt;/p&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id="understanding-python-re-gex-book"&gt;Understanding Python re(gex)? book&lt;a class="zola-anchor" href="#understanding-python-re-gex-book"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit my GitHub repo &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; for details about the book I wrote on Python regular expressions. The ebook uses plenty of examples to explain the concepts from the very beginning and step by step introduces more advanced concepts. The book also covers the &lt;a href="https://pypi.org/project/regex/"&gt;third-party module regex&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Sat, 04 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/python-regex-surprises/</guid></item><item><title>Rob’s Awesome Python Template</title><link>https://blog.tedivm.com/open-source/2023/02/robs-awesome-python-template/</link><description>Rob's Awesome Python Template is a highly configurable CookieCutter template that's packed full of best practices for Python Projects.</description><author>tedious ramblings</author><pubDate>Fri, 03 Feb 2023 18:59:44 GMT</pubDate><guid isPermaLink="true">https://blog.tedivm.com/open-source/2023/02/robs-awesome-python-template/</guid></item><item><title>How to Reverse Interview Investors</title><link>https://www.swyx.io/investor-questions</link><description>&lt;p&gt;I honestly never expected this to be a topic that was common enough to write up, however, it suddenly hit me today that it is the &lt;em&gt;ultra niche topics&lt;/em&gt; that deserve writing up since it is the stuff that is outside the usual SERP riffraff.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Thu, 02 Feb 2023 07:55:43 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/investor-questions</guid></item><item><title>Vim tip 21: working with tabs</title><link>https://learnbyexample.github.io/tips/vim-tip-21/</link><description>&lt;p&gt;Multiple files can be opened in Vim within the same tab page and/or in different tabs. From &lt;a href="https://vimhelp.org/windows.txt.html#windows-intro"&gt;:h windows-intro&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;A buffer is the in-memory text of a file.&lt;/li&gt;
&lt;li&gt;A window is a viewport on a buffer.&lt;/li&gt;
&lt;li&gt;A tab page is a collection of windows.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;:tabe filename&lt;/kbd&gt; open the given file in a new tab (&lt;code&gt;:tabe&lt;/code&gt; is short for &lt;code&gt;:tabedit&lt;/code&gt;)
&lt;ul&gt;
&lt;li&gt;if &lt;code&gt;filename&lt;/code&gt; isn't specified, you'll get an unnamed empty window&lt;/li&gt;
&lt;li&gt;by default, the new tab is opened to the right of the current tab&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:0tabe&lt;/kbd&gt; open as the first tab&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:$tabe&lt;/kbd&gt; open as the last tab&lt;/li&gt;
&lt;li&gt;see &lt;a href="https://vimhelp.org/tabpage.txt.html#%3Atabnew"&gt;:h :tabe&lt;/a&gt; for more details and features&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Switching between tabs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;:tabn&lt;/kbd&gt; switch to the next tab (&lt;code&gt;:tabn&lt;/code&gt; is short for &lt;code&gt;:tabnext&lt;/code&gt;)
&lt;ul&gt;
&lt;li&gt;if tabs to the right are exhausted, switch to the first tab&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;gt&lt;/kbd&gt; and &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Page Down&lt;/kbd&gt; can also be used&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;2gt&lt;/kbd&gt; switch to the second tab (the number specified is absolute, not relative)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:tabp&lt;/kbd&gt; switch to the previous tab (&lt;code&gt;:tabp&lt;/code&gt; is short for &lt;code&gt;:tabprevious&lt;/code&gt;)
&lt;ul&gt;
&lt;li&gt;if tabs to the left are exhausted, switch to the last tab&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;gT&lt;/kbd&gt; and &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Page Up&lt;/kbd&gt;  can also be used&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:tabr&lt;/kbd&gt; switch to the first tab (&lt;code&gt;:tabr&lt;/code&gt; is short for &lt;code&gt;:tabrewind&lt;/code&gt;)
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;:tabfirst&lt;/kbd&gt; can also be used&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:tabl&lt;/kbd&gt; switch to the last tab (&lt;code&gt;:tabl&lt;/code&gt; is short for &lt;code&gt;:tablast&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Moving tabs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;:tabm N&lt;/kbd&gt; move the current tab to after &lt;code&gt;N&lt;/code&gt; tabs from the start (&lt;code&gt;:tabm&lt;/code&gt; is short for &lt;code&gt;:tabmove&lt;/code&gt;)
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;:tabm 0&lt;/kbd&gt; move the current tab to the beginning&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:tabm&lt;/kbd&gt; move the current tab to the end&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:tabm +N&lt;/kbd&gt; move the current tab &lt;code&gt;N&lt;/code&gt; positions to the right&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;:tabm -N&lt;/kbd&gt; move the current tab &lt;code&gt;N&lt;/code&gt; positions to the left&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; Buffer list includes all the files opened in all the tabs. You can also use the mouse to switch/move tabs in GVim.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 01 Feb 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-21/</guid></item><item><title>The Swyx.io 2023 rewrite</title><link>https://www.swyx.io/rewrite-2023</link><description>&lt;p&gt;Discussing the 2023 migration of swyx.io to the SvelteKit 1.0 and the swyxkit template&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Tue, 31 Jan 2023 10:43:17 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/rewrite-2023</guid></item><item><title>Lessons learned streaming building a Scheme-like interpreter in Go</title><link>http://notes.eatonphil.com/2023-01-30-livescheme.html</link><description>&lt;p&gt;I wanted to practice making coding videos so I did a &lt;a href="https://www.youtube.com/watch?v=lZNhZI-dN9k&amp;amp;list=PLjJMyANAIVHEgUOK2cU0hrvSwFPNHT2a7"&gt;four-part
series&lt;/a&gt;
on writing a basic Scheme-like language (minus macros and arrays and
tons of stuff).&lt;/p&gt;
&lt;p&gt;I picked this simple topic because I wanted a low-stakes way to learn
what I did not know about making videos.&lt;/p&gt;
&lt;p&gt;Here was the end result (nothing crazy):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;build&lt;/span&gt;
&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;examples/fib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;scm&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;fib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;/livescheme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;examples/fib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;scm&lt;/span&gt;
&lt;span class="mi"&gt;89&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The code for the project is
&lt;a href="https://github.com/eatonphil/livescheme"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="video-archives"&gt;Video archives&lt;/h3&gt;&lt;p&gt;Here are the four episodes! Each about an hour long. One per week for
four weeks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=lZNhZI-dN9k"&gt;Part 1: A lexer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=5ttFEPQopXc"&gt;Part 2: Parsing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=YwmGcverSHI"&gt;Part 3: AST walking interpreter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=skDhTWILH8I"&gt;Part 4: Cleanup and Fibonacci&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="live-live"&gt;Live live&lt;/h3&gt;&lt;p&gt;The videos were &lt;a href="https://twitch.tv/eatonphil"&gt;streamed to Twitch&lt;/a&gt;
live.&lt;/p&gt;
&lt;p&gt;I didn't prep for them because I wanted to show warts and all. The
thought process.&lt;/p&gt;
&lt;p&gt;But some things turned out to be tricky to explain without preparation
(function calling conventions, mostly).&lt;/p&gt;
&lt;p&gt;Overall hopefully the series was somewhat useful.&lt;/p&gt;
&lt;h3 id="full-screen-windows"&gt;Full screen windows&lt;/h3&gt;&lt;p&gt;The first episode I did I didn't make sure that the terminal window
was captured full screen. So some of my code went off the bottom of
the video. That was dumb.&lt;/p&gt;
&lt;p&gt;I even have a tmux mode-line at the bottom of the terminal app that I
could have looked for to notice it didn't exist in the OBS view.&lt;/p&gt;
&lt;p&gt;So I made sure to have the full window in view after the first
episode.&lt;/p&gt;
&lt;h3 id="twitch-moderation"&gt;Twitch moderation&lt;/h3&gt;&lt;p&gt;&lt;a href="https://safety.twitch.tv/s/article/Protect-your-channel-with-Shield-Mode"&gt;Twitch Shield
Mode&lt;/a&gt;
is great. But the default setting prevents folks from commenting live
until they've followed you for 2 weeks or something.&lt;/p&gt;
&lt;p&gt;For someone starting a channel that doesn't make much sense. So in my
first video I disabled it so folks could chat. And then some crypto
scammer came in. Go figure.&lt;/p&gt;
&lt;p&gt;After the first video I turned Shield Mode back on but set the minimum
follow time to 10 minutes I think.&lt;/p&gt;
&lt;h3 id="obs-studio"&gt;OBS Studio&lt;/h3&gt;&lt;p&gt;I used &lt;a href="https://obsproject.com/"&gt;OBS Studio&lt;/a&gt; to record. I was
frustrated with it for a while because the video would lag so much
when I tested out streaming. After playing around with Twitch Studio
and giving up on it for being too simple, I messed with OBS video
settings enough to get my video to not lag. Unfortunately I can't
remember what settings I used.&lt;/p&gt;
&lt;h3 id="noise-gate-/-pop-filter"&gt;Noise Gate / pop filter&lt;/h3&gt;&lt;p&gt;The &lt;a href="https://obsproject.com/kb/noise-gate-filter"&gt;Noise Gate Filter&lt;/a&gt;
is awesome. My mechanical keyboard sounded obnoxious before I turned
it on. I was considering getting a pop filter but then discovered that
the Noise Gate Filter is built in, you just have to turn it on.&lt;/p&gt;
&lt;h3 id="scenes"&gt;Scenes&lt;/h3&gt;&lt;p&gt;It also took me a while to understand OBS Scenes but then I realized I
can use them to have an intro graphic (without the mic on!), a main
coding scene (focused on my terminal and with my webcam overlayed),
and a "back soon" graphic if I needed it.&lt;/p&gt;
&lt;p&gt;To get the mic off you have to &lt;a href="https://obsproject.com/forum/threads/mute-one-specific-scene.43661/"&gt;disable the mic
globally&lt;/a&gt;
(it's on globally by default) and then add it as an input only to the
scenes you want.&lt;/p&gt;
&lt;h3 id="storage-and-export-to-youtube"&gt;Storage and export to YouTube&lt;/h3&gt;&lt;p&gt;Twitch doesn't store streams by default. You have to turn on &lt;a href="https://help.twitch.tv/s/article/video-on-demand?language=en_US"&gt;Video on
Demand&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Even when it's turned on the videos only seem to be stored for 1 week. Maybe that's configurable but I didn't see it.&lt;/p&gt;
&lt;p&gt;In any case it's not a problem because you can set up a YouTube
connection. Then after a stream is complete you find the stream video
and click Export. It takes about a minute to upload the hour long
videos I did. Though YouTube post-processing took a while longer after
that.&lt;/p&gt;
&lt;h3 id="next?"&gt;Next?&lt;/h3&gt;&lt;p&gt;I'm forced to take a break from recording these videos for the next
two weeks since I'll be &lt;a href="https://systemsdistributed.com/"&gt;in Cape
Town&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I haven't decided yet if I'll continue this series (not something I'm
extremely excited about since everyone builds a Scheme-like language).&lt;/p&gt;
&lt;p&gt;I'd like to have a project that I can keep contributing to over time
but I don't see very much value in doing that based on a Scheme or any
lisp-like.&lt;/p&gt;
&lt;p&gt;Maybe I'll do a basic JavaScript implementation next. Or another basic
SQL database. Dunno.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Now that I'm done that series on the Scheme-like interpreter in Go (at least for a few weeks), I wrote down a few thoughts about the experience and the Twitch and OBS Studio setup.&lt;br /&gt;&lt;br /&gt;Up next after Cape Town? Not totally sure yet!&lt;a href="https://t.co/bgdO1ZI5Ow"&gt;https://t.co/bgdO1ZI5Ow&lt;/a&gt; &lt;a href="https://t.co/E1kwMRcCWY"&gt;pic.twitter.com/E1kwMRcCWY&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1620239367037157376?ref_src=twsrc%5Etfw"&gt;January 31, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 30 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-01-30-livescheme.html</guid></item><item><title>Compact WSL partition and reclaim storage space</title><link>https://j11g.com/2023/01/28/compact-wsl-partition-and-reclaim-storage-space/</link><description>Start PowerShell Find where your WSL vhdx file is located. Usually under: C:\Users\yourname\AppData\Local\Package\Linuxdistroflavour\LocalState\ext4.vhdx Start diskpart (from PowerShell or CMD): diskpart.exe Run: select vdisk file="C:\Users\Jan van den Berg\AppData\Local\Packages\TheDebianProject.DebianGNULinux_76v4gfsz19hv4\LocalState\ext4.vhdx" and next: compact vdisk</description><author>Jan van den Berg</author><pubDate>Sun, 29 Jan 2023 00:37:53 GMT</pubDate><guid isPermaLink="true">https://j11g.com/2023/01/28/compact-wsl-partition-and-reclaim-storage-space/</guid></item><item><title>January Gears emulator update</title><link>https://anisse.astier.eu/gears-update-2023-02.html</link><description>&lt;p&gt;&lt;a href="talks-emulation.html"&gt;Previously&lt;/a&gt;, I had a &lt;a href="gears-update-2023-01.html"&gt;bug on my emulator&lt;/a&gt; with the way the map is rendered.&lt;/p&gt;
&lt;table&gt;
    &lt;tr&gt;
        &lt;td&gt;
            &lt;img alt="Sonic 1 map screen. Sky/sea background and clouds are scrambled." src="images/gears-update-1/Sonic The Hedgehog (World) (Rev 1).3-S-3-s-60-S-320-actual.png" /&gt;&lt;br /&gt;
            With the bug
        &lt;/td&gt;
        &lt;td&gt;
            &lt;img alt="Sonic 1 map screen how it should look like.

The first level is showed with a line, and Sonic has 3 lives." src="images/gears-update-1/Sonic The Hedgehog (World) (Rev 1).3-S-3-s-60-S-320-target.png" /&gt;&lt;br /&gt;
            How it should look like
        &lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;After I wrote the previous article, I posted it on the &lt;a href="https://www.smspower.org/"&gt;SMS power&lt;/a&gt; discord, asking for ideas on this corruption bug. Many great people chimed in.&lt;/p&gt;
&lt;p&gt;A …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Sat, 28 Jan 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/gears-update-2023-02.html</guid></item><item><title>Are you ok?</title><link>https://www.marginalia.nu/log/72-are-you-ok/</link><description>I don&amp;rsquo;t know if I&amp;rsquo;m just imagining it, but has the Internet gone progressively more crazy the last decade or so?
It&amp;rsquo;s like everyone is so damn angry all the time. If they aren&amp;rsquo;t angry they&amp;rsquo;re bitter and resentful. And when they aren&amp;rsquo;t angry or bitter, they&amp;rsquo;re so depressed they&amp;rsquo;re barely able to crawl out of bed. And if they aren&amp;rsquo;t angry, bitter, or depressed, they have crippling anxiety. Every other week there&amp;rsquo;s some public blow-out where some person or another just loses their shit.</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 27 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/72-are-you-ok/</guid></item><item><title>Crypto V: On NFTs</title><link>https://bytepawn.com/my-thoughts-on-nfts.html</link><description>&lt;p&gt;I argue that virtual goods such as in-game skins make more sense in a centralized walled-garden approach, and NFTs tracked and traded on blockchains are legally problematic.&lt;br /&gt;&lt;br /&gt; &lt;img alt="BTC transaction" src="/images/bayc6.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 27 Jan 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/my-thoughts-on-nfts.html</guid></item><item><title>Introduction to AI and Large Language Models (LLMs)</title><link>https://smcleod.net/2023/01/introduction-to-ai-and-large-language-models-llms/</link><description>A high level intro to LLMs that I'm writing for a few friends that are new to the concept. It is far from complete, definitely contains some errors and is a work in progress.</description><author>smcleod.net</author><pubDate>Thu, 26 Jan 2023 03:00:00 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/01/introduction-to-ai-and-large-language-models-llms/</guid></item><item><title>Jet Set Radio review</title><link>https://burakku.com/blog/jet-set-radio-review/</link><description>&lt;p&gt;&lt;img alt="Muv-Luv Unlimited: THE DAY AFTER 01" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Short but sweet (soul brother)&lt;/p&gt;
&lt;p&gt;Two decades later and Jet Set Radio is still a great game. Luckily the visual style keeps the game looking quite fresh despite 23 years worth of patina. The movement and camera controls do show some age, but not to a degree that you can't get used to them. Once you do, the movement does actually become rather nice and ever-so stylish. And the soundtrack is still one of the all-time greats.&lt;/p&gt;
&lt;p&gt;My second playthrough of the main story only took around 4.5 hours after having beaten it for the first time in 2014, so it's definitely not the longest. I imagine first-time players will progress through the game a bit slower than I did though. However, there is still more content and collectibles to grind out if the main story isn't enough skating action.&lt;/p&gt;
&lt;p&gt;I would definitely recommend this piece of gaming history. It is, if nothing else, a stylish and fun little snack.&lt;/p&gt;
&lt;p&gt;As an added bonus, played basically perfectly on my Steam Deck and with good battery life.&lt;/p&gt;</description><author>ブラック</author><pubDate>Thu, 26 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/jet-set-radio-review/</guid></item><item><title>CLI tip 22: grep options to suppress stdout and stderr</title><link>https://learnbyexample.github.io/tips/cli-tip-22/</link><description>&lt;p&gt;While writing scripts, sometimes you just need to know if a file contains the pattern and act based on the exit status of the command. Instead of redirecting the output to &lt;code&gt;/dev/null&lt;/code&gt; you can use the &lt;code&gt;-q&lt;/code&gt; option. This will avoid printing anything on &lt;code&gt;stdout&lt;/code&gt; and also provides speed benefit as processing would be stopped as soon as the given condition is satisfied.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ cat find.txt
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;The&lt;/span&gt;&lt;span&gt; find command is more versatile than recursive options &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;and
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;and &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;extended&lt;/span&gt;&lt;span&gt; globs. &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Apart&lt;/span&gt;&lt;span&gt; from searching based on filename, it
&lt;/span&gt;&lt;span&gt;has provisions to &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt; based on the the file characteristics
&lt;/span&gt;&lt;span&gt;like size &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;and&lt;/span&gt;&lt;span&gt; time.
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;wE &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(\w+) \1'&lt;/span&gt;&lt;span&gt; find.txt
&lt;/span&gt;&lt;span&gt;has provisions to &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;match&lt;/span&gt;&lt;span&gt; based on the the file characteristics
&lt;/span&gt;&lt;span&gt;$ grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;qwE &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(\w+) \1'&lt;/span&gt;&lt;span&gt; find.txt
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$?
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;q &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'xyz'&lt;/span&gt;&lt;span&gt; find.txt
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$?
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;qwE &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'(\w+) \1'&lt;/span&gt;&lt;span&gt; find.txt &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'Repeated words found!'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Repeated&lt;/span&gt;&lt;span&gt; words found!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-s&lt;/code&gt; option will suppress the error messages that are intended for the &lt;code&gt;stderr&lt;/code&gt; stream.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span style="color: #7f8989;"&gt;# when the input file doesn't exist
&lt;/span&gt;&lt;span&gt;$ grep &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'in'&lt;/span&gt;&lt;span&gt; xyz.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;grep:&lt;/span&gt;&lt;span&gt; xyz.txt&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;: &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;No&lt;/span&gt;&lt;span&gt; such file &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;or&lt;/span&gt;&lt;span&gt; directory
&lt;/span&gt;&lt;span&gt;$ grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'in'&lt;/span&gt;&lt;span&gt; xyz.txt
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$?
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# when sufficient permission is not available
&lt;/span&gt;&lt;span&gt;$ touch &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;new&lt;/span&gt;&lt;span&gt;.txt
&lt;/span&gt;&lt;span&gt;$ chmod &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;new&lt;/span&gt;&lt;span&gt;.txt
&lt;/span&gt;&lt;span&gt;$ grep &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'rose' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;new&lt;/span&gt;&lt;span&gt;.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;grep: &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;new&lt;/span&gt;&lt;span&gt;.txt&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;: &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Permission&lt;/span&gt;&lt;span&gt; denied
&lt;/span&gt;&lt;span&gt;$ grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;s &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'rose' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;new&lt;/span&gt;&lt;span&gt;.txt
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$?
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Errors regarding regular expressions and invalid options will be on the &lt;code&gt;stderr&lt;/code&gt; stream even when the &lt;code&gt;-s&lt;/code&gt; option is used.&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;$ grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;sE &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a('&lt;/span&gt;&lt;span&gt; find.txt
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;grep: &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;Unmatched &lt;/span&gt;&lt;span&gt;( &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;or&lt;/span&gt;&lt;span&gt; \(
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ grep &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span&gt;sE &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a('&lt;/span&gt;&lt;span&gt; find.txt &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #c49a39;"&gt;/dev/&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;nu&lt;/span&gt;&lt;span&gt;ll
&lt;/span&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$?
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; Check out my &lt;a href="https://github.com/learnbyexample/command_help/blob/master/ch"&gt;&lt;code&gt;ch&lt;/code&gt; command line tool&lt;/a&gt; for a practical example of using the &lt;code&gt;-q&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my &lt;a href="https://github.com/learnbyexample/learn_gnugrep_ripgrep"&gt;CLI text processing with GNU grep and ripgrep&lt;/a&gt; ebook if you are interested in learning about &lt;code&gt;GNU grep&lt;/code&gt; and &lt;code&gt;ripgrep&lt;/code&gt; commands in more detail.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Wed, 25 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/cli-tip-22/</guid></item><item><title>3D Printer Nozzle Camera</title><link>https://sschueller.github.io/posts/3d-printer-nozzle-camera/</link><description>&lt;div class="featured-image"&gt;
                &lt;img src="/posts/3d-printer-nozzle-camera/nozzle-camera-close-up.jpg" /&gt;
            &lt;/div&gt;&lt;p&gt;(please ignore the horrible layer lines, this is just a prototype)&lt;/p&gt;</description><author>Stefan Schüller</author><pubDate>Mon, 23 Jan 2023 17:37:16 GMT</pubDate><guid isPermaLink="true">https://sschueller.github.io/posts/3d-printer-nozzle-camera/</guid></item><item><title>ActivityPub -&amp;gt; Announce -&amp;gt; Post</title><link>https://boyter.org/posts/activitypub-announce-post/</link><description>&lt;p&gt;I have been investigating how ActivityPub is meant to work. What follows is how a boost/announce/retweet works in Mastodon and any other system that implements against ActivityPub.&lt;/p&gt;
&lt;p&gt;You can find the full collection on github &lt;a href="https://github.com/boyter/activitypub"&gt;https://github.com/boyter/activitypub&lt;/a&gt; and contribute your own details.&lt;/p&gt;
&lt;h1 id="activitypub---announce---post"&gt;ActivityPub -&amp;gt; Announce -&amp;gt; Post&lt;/h1&gt;
&lt;p&gt;When a user clicks on bonk/boost/retweet the following sequence happens.&lt;/p&gt;
&lt;p&gt;The users intent is converted to an announce. This announce is sent to every follower they have as well as the owner
of the post that the user is boosting.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 23 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/activitypub-announce-post/</guid></item><item><title>An effective product manager</title><link>http://notes.eatonphil.com/effective-product-manager.html</link><description>&lt;p&gt;There are three specific activities I have loved in some product
managers I've worked with (and missed in others).&lt;/p&gt;
&lt;p&gt;tldr;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Talk with customers and prospects&lt;/li&gt;
&lt;li&gt;Develop and share a vision&lt;/li&gt;
&lt;li&gt;Evangelize&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="talk-with-customers-and-prospects"&gt;Talk with customers and prospects&lt;/h3&gt;&lt;p&gt;As a product manager, your superpower over engineering is to have
spent time with customers and prospects. You should have (or develop)
a good understanding of the market and your product's potential.&lt;/p&gt;
&lt;p&gt;The only way you can do this is by spending time, over time, with
customers and prospects. Understanding their workflows and their
issues.&lt;/p&gt;
&lt;h3 id="develop-and-share-a-vision"&gt;Develop and share a vision&lt;/h3&gt;&lt;p&gt;Cynical folks will cringe at the word "vision" but it is a serious and
necessary part of a successful organization.&lt;/p&gt;
&lt;p&gt;As a product manager, you should establish and share a path for
engineering to follow based on your understanding of customers,
prospects, the market, and the company.&lt;/p&gt;
&lt;p&gt;This is the "roadmap" and "prioritization". But prioritization is
useless without a long-term vision.&lt;/p&gt;
&lt;p&gt;The roadmap should represent (and broadly demonstrate) a concrete and
meaningful goal. A goal that you can and should adjust over time as
the company and market changes.&lt;/p&gt;
&lt;h3 id="evangelize"&gt;Evangelize&lt;/h3&gt;&lt;p&gt;In bigger organizations there might be dedicated evangelism teams. But
product managers must drive this work.&lt;/p&gt;
&lt;p&gt;Evangelism should fit the vision you've developed.&lt;/p&gt;
&lt;p&gt;And in the absense of dedicated evangelism teams, product managers
should be creating demos, writing blog posts, and testing the solution
with customers and prospects.&lt;/p&gt;
&lt;p&gt;Again, it's fine for dedicated teams outside of product management to
do bits of that work. But it must be driven and led by the product
manager.&lt;/p&gt;
&lt;h3 id="it's-hard"&gt;It's hard&lt;/h3&gt;&lt;p&gt;Observed as I have from outside, being an effective product
manager feels like a massively challenging task.&lt;/p&gt;
&lt;p&gt;It's so easy to go without talking to customers, to get sucked into
day-to-day issues and not create a vision, and to allow evangelism to
happen ad-hoc.&lt;/p&gt;
&lt;p&gt;Then there's the fact you don't live in a vacuum. You may have a boss
in product management. Your engineering peers may have competing
priorities. You may have a hard time understanding the founders or
CEO. In a large company, you may not even have a CEO.&lt;/p&gt;
&lt;h3 id="my-ideas,-your-ideas"&gt;My ideas, your ideas&lt;/h3&gt;&lt;p&gt;These are my ideas based on my
&lt;a href="https://eatonphil.com/"&gt;experience&lt;/a&gt;. You may have your own ideas. If
mine help you, great! If they don't, great!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I've been considering recently what makes an effective product manager. So I wrote down a few of my thoughts.&lt;br /&gt;&lt;br /&gt;What I've loved the most in some PMs and missed the most in others.&lt;br /&gt;&lt;br /&gt;I'd likewise love to hear what you think!&lt;a href="https://t.co/5vTWTNhs68"&gt;https://t.co/5vTWTNhs68&lt;/a&gt; &lt;a href="https://t.co/vXjPY9fiVT"&gt;pic.twitter.com/vXjPY9fiVT&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1617661616593723394?ref_src=twsrc%5Etfw"&gt;January 23, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 23 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/effective-product-manager.html</guid></item><item><title>Crypto IV: On stock investing vs. crypto investing</title><link>https://bytepawn.com/my-thoughts-on-crypto-vs-stock-investing.html</link><description>&lt;p&gt;I argue that a cryptocurrency like Bitcoin has a weird property: the more you buy of it, the less valuable it becomes.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Elon Musk Twitter" src="https://e3.365dm.com/22/10/2048x1152/skynews-twitter-elon-musk_5944670.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 21 Jan 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/my-thoughts-on-crypto-vs-stock-investing.html</guid></item><item><title>Crypto III: On perceivd crypto advantages such as anonymity and irreversibility</title><link>https://bytepawn.com/my-thoughts-on-crypto-advantages-such-as-anonymity-and-irreversibility.html</link><description>&lt;p&gt;I argue that anonymity, irreversibility and decentralization, while interesting technical features that implemented in a fascinating way using cryptographic primites, are not practical for real-world use.&lt;br /&gt;&lt;br /&gt; &lt;img alt="BTC transaction" src="/images/btc-transaction.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 20 Jan 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/my-thoughts-on-crypto-advantages-such-as-anonymity-and-irreversibility.html</guid></item><item><title>Paying my bills with 'free' ebooks</title><link>https://learnbyexample.github.io/my-book-writing-experience/</link><description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Small victories are more precious when you have nothing. Instead of burning through my savings, I'm now adding to it. The relief is priceless.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="it-is-worth-it-for-me"&gt;It is worth it (for me)&lt;a class="zola-anchor" href="#it-is-worth-it-for-me"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The section title is my response to this article &lt;a href="https://martin.kleppmann.com/2020/09/29/is-book-writing-worth-it.html"&gt;Writing a book: is it worth it?&lt;/a&gt; that I saw on &lt;a href="https://news.ycombinator.com/item?id=24628549"&gt;Hacker News&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For my unique circumstances, the decision to write ebooks has brought me financial stability, improved my mental health and gives me a sense of satisfaction. This could've come from any of my previous attempts to earn money, but ebooks is what worked out for me.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Book writing" src="/images/books/book_writing.jpg" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;i&gt;Photo by &lt;a href="https://unsplash.com/@bramnaus"&gt;Bram Naus&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/n8Qb1ZAkK88"&gt;Unsplash&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="how-it-all-started"&gt;How it all started?&lt;a class="zola-anchor" href="#how-it-all-started"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I left my job in 2014 for various reasons. I didn't have any plans for the future, just knew that I couldn't work as an employee any more.&lt;/p&gt;
&lt;p&gt;After enjoying my break, I had to try something to start earning again. I wrote an android gaming app, fantasized earning loads of money with an awesome work planner/communicator software that never left my imaginations, tried a small stint with &lt;a href="https://krishworks.com/"&gt;a team making an educational app&lt;/a&gt;, etc. I failed due to various reasons — didn't try hard enough, quit early, didn't fit my skills, wasn't good at design/marketing and so on. The educational app for example went on to become a success. Or perhaps, having saved enough to live out a few years without working meant I wasn't under enough pressure to earn.&lt;/p&gt;
&lt;p&gt;Among these failures, college workshops was the sole bread giver (and long way from supporting my modest living costs). My bachelor's degree was in electronics and communications and I had worked in a semiconductor company. So I knew enough to teach students pursuing similar courses the basics for Linux command line, Vim, Perl, Bash scripting, etc. As reference materials, I used to provide ppt slides (when I still had a job). Now that I had loads of free time, I started expanding my knowledge. Came to know about sites like Stackoverflow/Stackexchange/Reddit/etc. With newer and better materials to learn from, I created PDFs (using LibreOffice, which was pretty much the only option I knew about).&lt;/p&gt;
&lt;p&gt;Another loss maker was getting a domain/host to share these learning materials. Web development was too much for me and the (ugly) site didn't get any love. In hindsight, one of the better turning points was learning about GitHub in 2016. I loved markdown's nice output with syntax highlighting (and realized I was using it poorly in Reddit) and GitHub's social aspect (stars, issues, etc) — plus I can use Vim! I manually converted my materials from LibreOffice to markdown (again, I didn't know that tools like &lt;code&gt;pandoc&lt;/code&gt; could've helped me). Just like any other skill, I was learning and getting better with every iteration. That was the year I learned Python (thanks to &lt;a href="https://inventwithpython.com/"&gt;&lt;strong&gt;Al Sweigart&lt;/strong&gt;&lt;/a&gt;'s free coupon for &amp;quot;Automate the Boring Stuff with Python&amp;quot; video course) and started conducting workshops for Python instead of Perl.&lt;/p&gt;
&lt;p&gt;Being active on Stackoverflow and Reddit, I finally became proficient at CLI one-liners (late by 8 years, since it would have significantly helped in my role as a design and test engineer). I came across articles/books on regular expressions and one-liners. I thought — I can do that too, plus I was really liking them. Thus began my epic &lt;a href="https://github.com/learnbyexample/Command-line-text-processing"&gt;Command Line Text Processing&lt;/a&gt; repo, another big turning point in my journey as an author.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="encouraging-signs"&gt;Encouraging signs&lt;a class="zola-anchor" href="#encouraging-signs"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Over the course of ten months, I managed to complete the holy trinity of &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt; and &lt;code&gt;awk&lt;/code&gt; one-liners. I promoted these tutorials on Reddit, Google+, LinkedIn and other social sites I knew at that time. The repo got hundreds of stars and more importantly, I got critical feedback. I was ecstatic, even if I was continuing to burn through my savings.&lt;/p&gt;
&lt;p&gt;Then, I got to know about Hacker News (I think it was someone bragging about reaching front page). It took me a while to get used to Reddit, and HN was similarly alien to me. I posted a few links as a test and then I was &lt;a href="https://news.ycombinator.com/item?id=15549318"&gt;brave enough to submit&lt;/a&gt; my &lt;code&gt;awk&lt;/code&gt; one-liners post. I was refreshing HN anxiously for about half an hour or so. It got one vote and then other submissions pushed it away from new posts tab. Disappointed, I moved on. After sometime, I was checking traffic on my GitHub repo as usual, a habit I had picked up (all kinds of points, karma, likes, etc were so enticing). I noticed a HUGE spike in traffic and star count, the likes of which I had never seen before/since. The last time I had felt that proud of my work was during my job. This comment made a big impression on me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;These are the best stories on HN and why i subscribed here in the first place. I have often seen awk used so many times on SO but I've always put it up for something later to learn. Finally today I have some basic understanding of awk and this is really great stuff! I did get by with Perl but this is definitely more handy and the example approach to teaching it makes is super easy to understand!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After the euphoria had died down (about a week I guess), I was thinking about all the various kinds of posts I could make. And I was thinking how to use the repo popularity to bring in money. Long story short, I ended up adding donate buttons to my repos. This was before GitHub sponsors was announced. I wanted my materials to be freely available, so I wasn't even thinking about creating paid only options. Despite adding more tutorials, getting featured in &lt;a href="https://rubyweekly.com/issues/389"&gt;rubyweekly&lt;/a&gt; and other newsletters, social sites, etc, all I got was a single recurring donation (which ended prematurely when that platform switched payment set up).&lt;/p&gt;
&lt;p&gt;Another turning point came when a friend of mine was authoring a book and referred me for the reviewer role. Around that time, I had been converting &lt;a href="https://github.com/AllenDowney/ThinkPython2"&gt;Think Python&lt;/a&gt; to &lt;a href="https://github.com/learnbyexample/ThinkRubyBuild"&gt;Think Ruby&lt;/a&gt; and simultaneously working on a separate Ruby tutorial. During the book review process, I was given a list of topics and asked if I was interested in writing a book (they were impressed by my existing repos). The topics were either beyond my knowledge or out of scope, and they weren't interested in the repos I had already put up.&lt;/p&gt;
&lt;p&gt;My friends were always suggesting me to write a book and my reply consistently had been that I wasn't good enough to write one (the imposter syndrome hasn't left me even now). The book review experience, existing repos, my tryst with Think Ruby, dwindling savings, etc changed my mindset enough to try. By then I was already familiar with Leanpub, so I knew self-publishing was an option. I picked a niche topic (&lt;a href="https://learnbyexample.github.io/Ruby_Regexp/"&gt;Ruby Regexp&lt;/a&gt;), &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;learned enough &lt;code&gt;pandoc&lt;/code&gt; to produce a PDF&lt;/a&gt; and published it even before the book review ended. It helped that I already had material as part of the Ruby tutorial I was working on. I still had to work a lot, since tutorial description was all bullet points.&lt;/p&gt;
&lt;p&gt;I got only a few sales, but I had landed another review (video course for the same book) and was getting paid. So, I converted 'Ruby Regexp' to &lt;a href="https://learnbyexample.github.io/py_regular_expressions/"&gt;Python re(gex)?&lt;/a&gt;. I made it free for a few days and posted on Reddit, HN and other social sites. HN submission didn't get any traction, but fortunately &lt;a href="https://www.reddit.com/r/Python/comments/aeusdu/i_wrote_a_book_on_python_regular_expressions_it/"&gt;Reddit submission on /r/Python/&lt;/a&gt; was a big hit — thousands of free downloads and a few paid ones enough to cover 2 months of my expenses. I should mention now that I live alone, in outskirts of an Indian city, and my modest lifestyle costs about $150 per month. What works for me won't necessarily suit others.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="a-dip-followed-by-sustainable-momentum"&gt;A dip followed by sustainable momentum&lt;a class="zola-anchor" href="#a-dip-followed-by-sustainable-momentum"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Encouraged by the second release, I changed my focus from updating my GitHub repos to writing books. All those repos were now a fodder for book conversion. I picked up &lt;code&gt;grep&lt;/code&gt; first and included &lt;code&gt;ripgrep&lt;/code&gt; as well to keep it inline with the trend. Got decent sales from &lt;em&gt;free&lt;/em&gt; promotions. HN submission tanked at first, but got good attention when I posted again after a revision. Then I published a new version of 'Python re(gex)?' with significant changes and this HN submission got good views too. But note that these HN hits weren't anywhere close to what my &lt;code&gt;awk&lt;/code&gt; one-liners post had received.&lt;/p&gt;
&lt;p&gt;Writing &lt;code&gt;sed&lt;/code&gt; took a lot out of me. Probably I was getting jaded again, juggling between workshops and ebooks. Then I had a medical issue. I didn't even try promoting the &lt;code&gt;sed&lt;/code&gt; book on HN. I managed to learn enough JS to write &lt;a href="https://github.com/learnbyexample/learn_js_regexp"&gt;JavaScript regexp&lt;/a&gt;. Wasn't anywhere close to what I got from the Python book.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Roller Coaster" src="/images/books/roller_coaster.jpg" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;i&gt;Photo by &lt;a href="https://unsplash.com/@davidtrana"&gt;David Traña&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/mmdchg5UPtQ"&gt;Unsplash&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;So, despite reasonable reception during free promotions, my ebooks weren't still good enough to consistently pay my bills. Combined with workshops I was just about making my ends meet. I was losing interest and the medical issue was continuing. Still, without anything else to do, I finally started a book on &lt;code&gt;awk&lt;/code&gt; one-liners. Things started getting better for a few months and then the pandemic hit.&lt;/p&gt;
&lt;p&gt;Given the recent medical scare, pandemic fears and the trend of giveaways, I decided to open source my book contents. And, I made all my ebooks free to download indefinitely. Made a single bundle of all the 5 books I had published until then to make it easier to download in one shot. The reception was better than expected. Shortly after (last week of March), I published the &lt;code&gt;awk&lt;/code&gt; book early by cutting corners like excluding exercises. All books bundle now had 6 entries. Again, the reception was much better than expected. I hadn't made so many paid sales during a month ever before.&lt;/p&gt;
&lt;p&gt;Encouraged by the success, I made another important decision. Instead of starting another book, I took up the task of updating all my books. I alloted a month or two for this task, but it took me more than 4 months in the end. It wasn't that I had lot of new features to add. The feedback I had received over the past year and my own improving writing skills meant that I just couldn't help updating the books to the best of my abilities. Somehow, lockdown and fear of the pandemic ended up improving my workflow.&lt;/p&gt;
&lt;p&gt;Workshops weren't going to come my way anytime soon, but ebook sales for about 6 months averaged $200+ per month. For the first time since leaving my job, I was saving money!!! During this period all my books were free to download, in addition to the markdown source being available from GitHub repos. I even managed to create EPUB and web versions for my ebooks. The web version generated using &lt;a href="https://github.com/rust-lang/mdBook"&gt;mdBook&lt;/a&gt; was much better than my attempts with wordpress all those years ago, but to be fair I hadn't known enough about formatting for coding books.&lt;/p&gt;
&lt;p&gt;After finishing this marathon revision task, I reverted PDF/EPUB versions to be a paid option again. Since then, I've managed to write three more books. I did Perl and Ruby one-liners (as part of the ongoing conversion of the CLI text processing repo) despite knowing sales won't be good enough to keep up the momentum. Then I wrote a &lt;a href="https://learnbyexample.github.io/100_page_python_intro/"&gt;Python intro&lt;/a&gt; book for those already familiar with programming basics. Published last month, sales are much lesser than I expected. Given Python is now 30 years old and there's no shortage of Python books for beginners, I shouldn't be surprised though. I'm probably grumpy because it took a month more than expected even though I already had decent material from my workshops. Anyway, my main motivation was to improve my Python knowledge and it did serve that purpose. As a bonus, I just got started with workshops again, conducted online (a first for me). The book is already proving useful as a handy reference for me as well as the students.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-criticism"&gt;Feedback and Criticism&lt;a class="zola-anchor" href="#feedback-and-criticism"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here's some of the feedback I've received over the past two years.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Grammatical mistakes. Missing &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;an&lt;/code&gt; and &lt;code&gt;the&lt;/code&gt; articles were particularly jarring for the readers. If you couldn't tell from reading &lt;em&gt;this&lt;/em&gt; article (heh) that English isn't my native language, I'll consider that I've improved a lot.&lt;/li&gt;
&lt;li&gt;Some readers stated that they didn't bother checking out my books because the covers are so bad. I finally got a cover done by an artist for the &lt;a href="https://learnbyexample.github.io/100_page_python_intro/"&gt;Python intro book&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For the regexp books, a few readers said my introductions were light on content. So during the marathon book updates I did last year, I managed to add more details. I feel there's still plenty of room for improvement.&lt;/li&gt;
&lt;li&gt;My comprehension is kinda average and it works better whenever I manage to create code snippets to prove or disprove my understanding. So, my books are heavily example oriented. I've received feedback that there are too many examples, explanations aren't sufficient, etc. I'm trying to improve on this count, but I doubt I can change my natural writing style.&lt;/li&gt;
&lt;li&gt;A few readers wanted more exercises, which I was happy to oblige. It took me a while to accept that I should provide solutions as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I did get a few negative feedback (ones I consider weren't constructive in nature). One such feedback affected me a lot, despite the encouraging sales for the second book. Over time, I've adapted but I'm still afraid of seeing one whenever I promote my books.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="self-publishing-experience"&gt;Self publishing experience&lt;a class="zola-anchor" href="#self-publishing-experience"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I don't have a personal experience with traditional publishing (other than the two review opportunities). After the initial success of 'Python re(gex)?' book, I was happy to stay being self published. When there was a dip, I did consider it would be nice to have the backing of a traditional publisher and a chance to improve the contents of my books.&lt;/p&gt;
&lt;p&gt;What I like about self published:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can give away free copies whenever I want, change pricing, share the source code, put up free web version of the books, etc.
&lt;ul&gt;
&lt;li&gt;I'm aware of a few publishers allowing authors to put up free web copies, but it isn't universal.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;I can push updates easily and inform the readers as well.&lt;/li&gt;
&lt;li&gt;No deadlines, other than self imposed ones. This is both good and bad. The good thing is that I can take my time. The bad thing is that the reduced pressure leads to longer schedule. I spend a lot of time on social media, reading fiction, watching entertainment, etc. The lockdown marathon did improve my average working hours, but there's still a lot of room for improvement.&lt;/li&gt;
&lt;li&gt;I am not restricted by guidelines set by a publisher regarding chapter structure, images, exercises, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I feel would improve with traditional publishing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cover image&lt;/li&gt;
&lt;li&gt;PDF/EPUB quality&lt;/li&gt;
&lt;li&gt;Content quality, especially grammar&lt;/li&gt;
&lt;li&gt;Audience reach&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not sure how my earnings would be affected. On the one hand, I get minimum 80% on book sales. On the other hand, I'd probably reach a wider audience with traditional publishing. I did receive a few offers when my promotional posts were trending. One of the offer (for 'Python re(gex)?' ebook) had a joining bonus and initial advance — both combined was less than what I had already earned. But if they had extended the offer for other books as well, it would've been a much more tempting deal.&lt;/p&gt;
&lt;p&gt;Currently, I'm happy with status quo. Always free web versions and free PDF/EPUB promotional sales kinda solves my donation problem before I started selling ebooks — I get paid and readers have a way to get the materials for free. I'm also inspired by FOSS products I use and authors like Al Sweigart and Allen B. Downey who give away quality learning resources for free.&lt;/p&gt;
&lt;p&gt;That said, I wish I could improve my marketing skills. Or, somehow someone likes my books so much that their review attracts significant attention and my sales increase as a result. I've also considered trying out affiliates, but haven't even created a list of people to contact yet. I don't have analytics set up on Leanpub, my blog, web versions of my books, etc. Based on analytics that is available by default on GitHub and Gumroad, I do see a few links from schools and universities. I wish they would contact me, so that I can help if needed and improve my book contents based on their experiences.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="leanpub-vs-gumroad"&gt;Leanpub vs Gumroad&lt;a class="zola-anchor" href="#leanpub-vs-gumroad"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started with Leanpub since I had seen a few posts from self published authors using this platform. By the time I had published the second book I got to know about Gumroad and was attracted by the pricing/payout structure. From then on, I have published on both platforms.&lt;/p&gt;
&lt;p&gt;Here's what I like most about both these platforms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can change pricing (including free option) and book contents any number of times&lt;/li&gt;
&lt;li&gt;I can allow users to pay &lt;em&gt;more&lt;/em&gt; than the product price, which is how I get paid during &lt;em&gt;free&lt;/em&gt; promotional sales&lt;/li&gt;
&lt;li&gt;I can inform readers whenever I update my books&lt;/li&gt;
&lt;li&gt;I can create bundles&lt;/li&gt;
&lt;li&gt;They handle collection of VAT (and other such fees)&lt;/li&gt;
&lt;li&gt;Their payout options work for me in India&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's some differences and &lt;em&gt;my&lt;/em&gt; opinions on some of their features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gumroad's pricing structure is better. If you have a following like &lt;a href="https://twitter.com/b0rk"&gt;Julia Evans&lt;/a&gt;, pricing would make a huge difference&lt;/li&gt;
&lt;li&gt;Gumroad gives analytics for free&lt;/li&gt;
&lt;li&gt;Gumroad's email notification is opt-out compared to opt-in for Leanpub. Opt-in is better for readers, but in my experience less than 10% sign up and thus miss out when I want to send them book updates&lt;/li&gt;
&lt;li&gt;Leanpub payout delay is 45-75 days, Gumroad is 7-14 days (or instant in some cases)&lt;/li&gt;
&lt;li&gt;Leanpub's bundle feature is better since it doesn't require a new cover and files are automatically picked based on the links provided. In Gumroad, it is essentially a new product, but it does allow to manually pick files from existing ones. Also, Leanpub allows bundling with another author (which I have used and given me decent sales)&lt;/li&gt;
&lt;li&gt;Leanpub's product page and UI is better. The sliding scale (along with information on author's share) to pick a price is clearer than Gumroad's manual price entry. And I don't like that Gumroad places the minimum price information away from the box where the user enters a price. On Leanpub, all of these are shown together and reduces confusion&lt;/li&gt;
&lt;li&gt;Leanpub's product page has always ranked higher in search results in my experience&lt;/li&gt;
&lt;li&gt;Leanpub's 45-day Guarantee and Sample chapters as part of the product page makes it easier for readers to take a risk&lt;/li&gt;
&lt;li&gt;Leanpub has &lt;a href="https://leanpub.com/newsletters"&gt;weekly/monthly sale newsletters&lt;/a&gt; in which you could get featured. This has brought me significant earnings in the past few months. If you enable an option, Gumroad would promote your product too (for 10% extra fee) but this has given me very few sales compared to Leanpub's newsletter&lt;/li&gt;
&lt;/ul&gt;
&lt;p align="center"&gt;&lt;img alt="Pricing UI on Leanpub" src="/images/books/leanpub_price_ui.png" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;i&gt;Pricing UI on Leanpub&lt;/i&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Pricing UI on Gumroad" src="/images/books/gumroad_price_ui.png" /&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;i&gt;Pricing UI on Gumroad&lt;/i&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="pandoc-and-mdbook"&gt;pandoc and mdbook&lt;a class="zola-anchor" href="#pandoc-and-mdbook"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I picked &lt;a href="https://github.com/jgm/pandoc/"&gt;pandoc&lt;/a&gt; to generate PDF from GitHub style markdown, as it seemed the most popular tool for this purpose. The default output is good enough, but I wanted to customize a lot of things. With help from documentation and various Stackoverflow/Stackexchange threads, I was able to generate an output to my liking. I didn't know about templates though, otherwise I could have researched about them and re-used solutions from others. I wrote a blog post about my learnings, visit &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;Customizing pandoc to generate beautiful pdf and epub from markdown&lt;/a&gt; if you are interested.&lt;/p&gt;
&lt;p&gt;Some readers wanted EPUB versions too. I thought it made sense for reading from mobile, but my own experience with this format on desktop was quite disappointing. Only later did I learn that I wasn't using a proper EPUB reader for technical books. Which is why I didn't realize that the default output from &lt;code&gt;pandoc&lt;/code&gt; for EPUB was also good enough. During the revision marathon, I finally created EPUB versions too. I'd say I am still a beginner, but I did learn enough CSS and LaTeX to customize EPUB and PDF generation with &lt;code&gt;pandoc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pandoc&lt;/code&gt; has its own enhanced version of markdown, which has a lot of nifty features for ebooks. But I chose to stick with GitHub style markdown. And it came in handy when I wanted to re-use book material for blog posts, generating web versions of the book with &lt;a href="https://github.com/rust-lang/mdBook"&gt;mdbook&lt;/a&gt; and so on. After I had decided to open source my books, I also wanted to make a web version that feels like a book instead of just the single page markdown source from the GitHub repos. I would've probably used Gitbook if they hadn't moved away from the legacy version. I came across &lt;code&gt;mdbook&lt;/code&gt; as an alternate for Gitbook and I'm glad I did.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="future-plans"&gt;Future plans&lt;a class="zola-anchor" href="#future-plans"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have certainly improved a lot as a writer since I first published my book in late 2018. But after 9 books, I'm finding it a lot more difficult to motivate myself to keep writing. See also &lt;a href="https://news.ycombinator.com/item?id=20212090"&gt;HN discussion: Writing a book, still the same pain 15 years later&lt;/a&gt; for another example.&lt;/p&gt;
&lt;p&gt;I have plans to publish at least one more book in 2021 and revise my existing books (not comprehensive, but a few items have cropped up). I hope the current momentum can extend enough to cover my expenses for this year at least. Beyond that, I think I will write more books, but I'll have to mix it up with other things (such as video courses, interactive courses, freelancing, etc) to keep myself motivated. I just hope that this time I will be able to pick an alternative quickly.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="resources"&gt;Resources&lt;a class="zola-anchor" href="#resources"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I've been asked a few times regarding my experiences as an author (especially self publishing) and resources I've used. That was my primary intention in writing this blog post. I thought I'd add a bit of background as well, not the multi-section essay I ended up with. Anyway, here's some links that I've bookmarked related to book writing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Authors sharing their experiences&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.jeffgeerling.com/blog/2020/self-publishing-and-2nd-edition-ansible-devops"&gt;Jeff Geerling's self-publishing experience&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/#on-writing-comics---zines"&gt;Julia Evans's articles on writing comics/zines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2020/10/28/a-few-things-i-ve-learned-about-email-marketing/"&gt;Julia Evans's email marketing experience&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.swyx.io/marketing-yourself/"&gt;Shawn Wang's article: How to Market Yourself&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://andregarzia.com/2021/04/writing-a-technical-book.html"&gt;Andre Alves Garzia's article: Writing a Technical Book&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=23818859"&gt;HN discussion: Writing a software book and making over $100k&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Writing skills&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=20070558"&gt;HN discussion: Tips for Writing a Technical Book&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=22283919"&gt;HN discussion: Learning technical writing using the engineering method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=23281568"&gt;HN discussion: How to write a programming book&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jenniferlynparsons/awesome-writing"&gt;awesome-writing: list of information to help developers write better, kinder, more helpful documentation and learning materials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/joshuacc/prose-for-programmers"&gt;A book to help software developers write better prose (WIP)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tools and Miscellaneous&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;Customizing pandoc to generate beautiful pdf and epub from markdown&lt;/a&gt; — my own blog post, includes resource links for similar articles and tools other than &lt;code&gt;pandoc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LisaDziuba/Awesome-Design-Tools"&gt;List of awesome design tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sw-yx/launch-cheatsheet/"&gt;launch-cheatsheet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="a-parting-advice"&gt;A parting advice&lt;a class="zola-anchor" href="#a-parting-advice"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Don't quit easily!&lt;/p&gt;
&lt;br /&gt;</description><author>learnbyexample</author><pubDate>Thu, 19 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/my-book-writing-experience/</guid></item><item><title>1000 Subscriber Live Stream 1/18/23 @ 5PM PST</title><link>https://miscdotgeek.com/1000-subscriber-live-stream-1-18-23-5pm-pst/</link><description>&lt;p&gt;Yep, you read that right! Thanks to all of you, the MiscDotGeek YouTube channel hit 1000 subscribers this month. This might be small beans for YouTube, but to me it&amp;#8217;s a huge deal and a big milestone! To mark the occasion, MiscDotGeek will be doing its first ever Live Stream&amp;#8230; okay well Second, if you &amp;#8230; &lt;/p&gt;
&lt;p&gt;&lt;a class="more-link btn" href="https://miscdotgeek.com/1000-subscriber-live-stream-1-18-23-5pm-pst/" rel="noopener noreferrer" target="_self"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://miscdotgeek.com/1000-subscriber-live-stream-1-18-23-5pm-pst/" rel="noopener noreferrer" target="_self"&gt;1000 Subscriber Live Stream 1/18/23 @ 5PM PST&lt;/a&gt; appeared first on &lt;a href="https://miscdotgeek.com" rel="noopener noreferrer" target="_self"&gt;MiscDotGeek&lt;/a&gt;.&lt;/p&gt;</description><author>MiscDotGeek</author><pubDate>Mon, 16 Jan 2023 22:10:17 GMT</pubDate><guid isPermaLink="true">https://miscdotgeek.com/1000-subscriber-live-stream-1-18-23-5pm-pst/</guid></item><item><title>A Journey in E-commerce Search</title><link>https://danpalmer.me/2023-01-15-ecommerce-search-journey/</link><description>At Thread we went through several iterations of Search, evolving the technology as we evolved the business and our understanding of what our customers wanted. Later stages went beyond my naive understanding of search at the time, and may prove useful inspiration to others.
Before we dive in, some clarification of terms. For us, search meant free text entry that generated product results, whereas filtering referred to distinct options that could be chosen by the user, such as filtering to next-day delivery or a particular brand.</description><author>Dan Palmer</author><pubDate>Sun, 15 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://danpalmer.me/2023-01-15-ecommerce-search-journey/</guid></item><item><title>I don’t understand terminals, shells and SSH</title><link>https://j11g.com/2023/01/14/i-dont-understand-terminals-shells-and-ssh/</link><description>Confession time: I don&amp;#8217;t fully understand how terminals, shells and SSH really work (and my guess is you don&amp;#8217;t either). And I don&amp;#8217;t mean the cryptography behind SSH. I mean how SSH and the terminal &amp;#8212; and the shell for that matter &amp;#8212; interact with one another. I recently realized that even though I&amp;#8217;ve been [&amp;#8230;]</description><author>Jan van den Berg</author><pubDate>Sat, 14 Jan 2023 21:03:25 GMT</pubDate><guid isPermaLink="true">https://j11g.com/2023/01/14/i-dont-understand-terminals-shells-and-ssh/</guid></item><item><title>Setting the Bozo Bit on Apple</title><link>https://blog.metaobject.com/2023/01/setting-bozo-bit-on-apple.html</link><description>The other day I was fighting once again with Apple Music.  Not the service, the app.  What I wanted
to do was simple:  I have some practice recordings for my choir and voice lessons that I want on
my iPhone and Apple Watch.  How hard could it be?&lt;p&gt;

Apple:  hold my beer.&lt;p&gt;

These are sent via WhatsApp so the audio recordings are mp4 files, which for some bizarre reason 
won't open in Music and instead open in QuickTime Player, despite definitely being audio files.&lt;p&gt;

OK, not a biggie, so export to m4a from QT Player.  Transfer to the machine that has my audio library.   Create
a new playlist, transfer some previous songs over, then try to drop the new m4a's onto the
open playlist.  No go.  Play around for a while, figure out that the entity that accepts the
drops is the TableView, not the surrounding view.  So you can't drop the new files into the
empty space below the songs, you have to drop them onto the existing songs.&lt;p&gt;

Who programmed this?  Who didn't pay attention to this when doing QA?  Who approved it for 
release?  iTunes used to be if not the, then certainly a flagship app for Apple.&lt;p&gt;

OK, plug in the iPhone, as for some reason wireless transfers don't seem to be overly reliable.&lt;p&gt;

No Finder, I don't want to back...too late.  Ok, do your backup.   Waiting.  Spinner.  Waiting.  Repeat.  
After a while it says it's finished.   Unplug and ... the songs are not there.&lt;p&gt;

I quit Music.app, relaunch it, and lo-and-behold, the songs are now no longer in the playlist in
Music.app either.  Re-add them, carefully aiming for the table, sync again (hey, it remembered we 
just did a backup and doesn't try again, kudos!), and now they show up.&lt;p&gt;

Whew!  Only took 15 minutes or so, the last time I was futzing with it for over an hour and
the songs never synced.  Or one did and two did not, which is obviously Much Better.&lt;p&gt;

How can such basic functionality be this incredibly broken?  And of course this is just one
tiny example, there are legions others, as many others have reported.&lt;p&gt;

With this, I noticed that I hadn't actually expected better.  I knew it &lt;em&gt;should&lt;/em&gt;
be better but I hadn't expected Apple to actually make it work.&lt;p&gt;

In other words, I had set the &lt;a href="https://en.wikipedia.org/wiki/Bozo_bit"&gt;Bozo Bit&lt;/a&gt; on
Apple.  By default, when Apple does something new these days, I fully and quietly expect it  
to be broken.  And I am surprised when they actually get something right, like Apple Silicon.
And it wasn't an angry reaction to anything, in fact, it wasn't even much
of conscious decision, more a gradual erosion of expectations.&lt;p&gt;

It Just Doesn't Work™.&lt;p&gt;

And that's sad.&lt;p&gt;</description><author>metablog</author><pubDate>Fri, 13 Jan 2023 20:02:24 GMT</pubDate><guid isPermaLink="true">https://blog.metaobject.com/2023/01/setting-bozo-bit-on-apple.html</guid></item><item><title>Memex Design</title><link>https://www.marginalia.nu/log/71-memex-design/</link><description>For clarification, this is discussing no other thing called Memex than memex.marginalia.nu, the website you&amp;rsquo;re probably visiting right now. That, or you&amp;rsquo;re reading this over gemini at marginalia.nu, which is serving the same content over a different protocol.
I wanted to build a cross-protocol static site generator designed in a way that is equally understandable by both humans and machines. This groundedness is an appealing property I really admire about the gemini protocol and gemtext format.</description><author>Weblog on marginalia.nu</author><pubDate>Fri, 13 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/71-memex-design/</guid></item><item><title>Which Technical Courses did I use on the Job? Part II</title><link>https://blog.nawaz.org/posts/2023/Jan/which-technical-courses-did-i-use-on-the-job-part-ii/</link><description>&lt;p&gt;In an earlier
&lt;a class="reference external" href="https://blog.nawaz.org/posts/2023/Jan/which-technical-courses-did-i-use-on-the-job/"&gt;post&lt;/a&gt;,
I listed all the technical courses I took in my undergrad, and which
ones I used on the&amp;nbsp;job.&lt;/p&gt;
&lt;p&gt;Of the 40 courses I took, I used at most 8 of them at work. &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I like technical topics. I &lt;em&gt;actively seek&lt;/em&gt; ways to utilize what …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Thu, 12 Jan 2023 11:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2023/Jan/which-technical-courses-did-i-use-on-the-job-part-ii/</guid></item><item><title>Which Technical Courses did I use on the Job?</title><link>https://blog.nawaz.org/posts/2023/Jan/which-technical-courses-did-i-use-on-the-job/</link><description>&lt;p&gt;My undergrad degree was in electrical engineering. Since then, I&amp;#8217;ve
worked in industry for over a decade - both as an electrical engineer
and as a software&amp;nbsp;engineer.&lt;/p&gt;
&lt;p&gt;In this post I&amp;#8217;ll list all the technical courses I took in undergrad,
with a remark on whether I ever used …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Thu, 12 Jan 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2023/Jan/which-technical-courses-did-i-use-on-the-job/</guid></item><item><title>The year in books: 2022</title><link>http://notes.eatonphil.com/2023-01-12-year-in-books.html</link><description>&lt;p&gt;In 2022 I &lt;a href="https://www.goodreads.com/challenges/11636-2022-reading-challenge"&gt;finished 20
books&lt;/a&gt;
spanning 15,801 pages.  3 more than I read in 2021, but about twice
the number of pages. 3 fiction and 17 non-fiction. Another ~30 started
but not finished.&lt;/p&gt;
&lt;p&gt;I had a hard time reading books while I was trying to start my own
company. But I also discovered audiobooks. I would put on a book and
listen while I did my chores. Only 5 of the 20 books I finished were
physical (or kindle) books. The other 15 were audiobooks.&lt;/p&gt;
&lt;h3 id="non-fiction:-13-to-recommend"&gt;Non-fiction: 13 to recommend&lt;/h3&gt;&lt;p&gt;After I started read Robert Caro's Master of the Senate I got hooked
on history and felt less daunted about larger books.&lt;/p&gt;
&lt;p&gt;The only non-fiction I read in 2022 was US and UK history.&lt;/p&gt;
&lt;p&gt;Here were my favorites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/86525.Master_of_the_Senate"&gt;Master of the
Senate&lt;/a&gt;
by Robert Caro: Covering more than just Lyndon B. Johnson but the
history of the Senate and the Civil Rights movements in the US. This
book is now on my &lt;a href="https://lists.eatonphil.com/book-recommendations.html"&gt;list of best
books&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/19809.The_Last_Lion"&gt;The Last Lion: Winston Spencer Churchill: Visions of Glory,
1874-1932&lt;/a&gt;
by William Manchester: First in a three-volume series about
Churchill. He's an especially interesting guy to read about because
he served in UK politics 1901 to his retirement (for the second
time) as UK Prime Minister in 1955. He was First Lord of the
Admiralty in World War 1 before he, more famously, become Prime
Minister during World War 2. This entire series is on my &lt;a href="https://lists.eatonphil.com/book-recommendations.html"&gt;list of best
books&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/42547.The_Autobiography_of_Martin_Luther_King_Jr_"&gt;The Autobiography of Martin Luther King,
Jr.&lt;/a&gt;:
Sad and revealing. Though it doesn't talk much about his legacy
since it only includes his writings.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/13049569-the-passage-of-power"&gt;Passage of
Power&lt;/a&gt;
by Robert Caro: Covering LBJ's pathetic failed attempts at the
presidency before becoming JFK's Vice President, up to JFK's
assassination. Still a very good book. I can't wait for Caro's final
book to come out.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/2279.Truman"&gt;Truman&lt;/a&gt; by David
McCullough: I always thought Truman was a lame nerd but he actually
had a very interesting life (and as I'd later discover, is far from
the lamest president. Wilson hands down takes that place.) And
unlike most other famous politicians I read about, he had a great
relationship with his wife. He was honest and respectable and was
the first US president to normalize relations with Mexico since the
Mexican-American War (that U.S. Grant and Robert E. Lee fought in
the 1840s).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/55751.The_Last_Lion"&gt;The Last Lion: Winston Spencer Churchill: Alone,
1932-40&lt;/a&gt; by
William Manchester: The second book in the series. Pretty
depressing because it's a decade of Churchill noticing Nazi German
behavior and stressing UK preparedness and the UK ignoring him and
Nazi Germany.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/746673.The_Last_Lion"&gt;The Last Lion: Winston Spencer Churchill: Defender of the Realm,
1940-1965&lt;/a&gt;
by William Manchester: The final book in the series, covering his
Prime Ministership.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/884536.Eleanor_Roosevelt_Volume_1"&gt;Eleanor Roosevelt, Volume 1: The Early Years,
1884-1933&lt;/a&gt;
by Blanche Wiesen Cook: Her background and many problems, as the
daughter of Theodore Roosevelt's brother and later husband of their
distant cousin, is pretty hard to relate to. Still it was quite
interesting to hear about her life and early activities how she
became such an outspoken progressive activist from being quite
conservative.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/17082810-abraham-lincoln"&gt;Abraham Lincoln: A Life, Volume One&lt;/a&gt; by Michael Burlingame&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/17082819-abraham-lincoln"&gt;Abraham Lincoln: A Life, Volume Two&lt;/a&gt; by Michael Burlingame&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/34237826-grant"&gt;Grant&lt;/a&gt; by Ron
Chernow: Among famous generals of the Civil War, somehow Robert
E. Lee and Stonewall Jackson came to mind to me more readily than
Grant. I'm glad I read this book because the popularity of Southern
generals today seems like revisionism. This book makes strong
arguments that while Lee was a great officer, he could only think in
terms of short-term tactics and the Virginia region. Whereas Grant
was the first (US, anyway) officer to consider and command (via
telegraph) all theaters of war at once, every day. And this book
redeems his presidency somewhat. His progressive adoption of freed
Black people and work to make them equal citizens is highly
commendable. Even with the horror of what happened in the South
after the war ended.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/40929.The_Rise_of_Theodore_Roosevelt"&gt;The Rise of Theodore
Roosevelt&lt;/a&gt;
by Edmund Morris: First in a three-volume series about the 26th
President. I read somewhere that it can feel impossible to read a
bad biography of Roosevelt because he was such an interesting
human. That may be true. This book didn't disappoint. Roosevelt
growing up in a townhouse in Manhattan, going to Harvard, buying a
farm on Long Island is all hard to relate to. His Puritanical morals
and machismo were also difficult to get past. But he was a very
interesting guy.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/40923.Theodore_Rex"&gt;Theodore
Rex&lt;/a&gt; by
Edmund Morris: Second in the series, covering the entirety of
Roosevelt's presidency. Like the first volume, a great read. I
always used to think Roosevelt was a pure war-monger. But he helped
avert war with the UK and Germany over Venezuelan debt-default. And
he later received the Nobel Peace Prize for mediating peace between
Japan and Russia in 1905.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="fiction:-1-to-recommend"&gt;Fiction: 1 to recommend&lt;/h3&gt;&lt;p&gt;Of the three I read last year, I really enjoyed one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/18950097-the-leopard"&gt;The
Leopard&lt;/a&gt;
by Giuseppe Tomasi di Lampedusa: A gentle piece of historical
fiction set during the 1860s in Sicily during and after the
unification of Italy. I learned about this book from a Rick Stein
episode in the Mediterranean Escapes series.&lt;/li&gt;
&lt;/ul&gt;</description><author>Notes on software development</author><pubDate>Thu, 12 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-01-12-year-in-books.html</guid></item><item><title>2023–01–12: Pinephone cpuidle</title><link>https://xnux.eu/log/#077</link><author>megi's PinePhone Development Log</author><pubDate>Thu, 12 Jan 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://xnux.eu/log/#077</guid></item><item><title>Signs that a Startup is Going Bad</title><link>https://www.swyx.io/startups-going-bad</link><description>&lt;p&gt;All rocketship startups are alike, but every fading startup is fading in its own way.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Wed, 11 Jan 2023 17:31:20 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/startups-going-bad</guid></item><item><title>Write down what you're working on</title><link>https://nindalf.com/posts/write-it-down/</link><description>Learn how to make performance reviews less stressful and more accurate with a simple, low-tech solution.</description><author>Krishna's blog</author><pubDate>Tue, 10 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nindalf.com/posts/write-it-down/</guid></item><item><title>Vim tip 20: character based motions within the current line</title><link>https://learnbyexample.github.io/tips/vim-tip-20/</link><description>&lt;p&gt;These commands allow you to move based on a single character search, &lt;strong&gt;within the current line only&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;kbd&gt;f(&lt;/kbd&gt; move forward to the next occurrence of character &lt;code&gt;(&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;fb&lt;/kbd&gt; move forward to the next occurrence of character &lt;code&gt;b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;3f&amp;quot;&lt;/kbd&gt; move forward to the third occurrence of character &lt;code&gt;&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;t;&lt;/kbd&gt; move forward to the character just before &lt;code&gt;;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;3tx&lt;/kbd&gt; move forward to the character just before the third occurrence of character &lt;code&gt;x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;Fa&lt;/kbd&gt; move backward to the character &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;Ta&lt;/kbd&gt; move backward to the character just after &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;;&lt;/kbd&gt; repeat previous &lt;code&gt;f&lt;/code&gt; or &lt;code&gt;F&lt;/code&gt; or &lt;code&gt;t&lt;/code&gt; or &lt;code&gt;T&lt;/code&gt; motion in the same direction&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;,&lt;/kbd&gt; repeat previous &lt;code&gt;f&lt;/code&gt; or &lt;code&gt;F&lt;/code&gt; or &lt;code&gt;t&lt;/code&gt; or &lt;code&gt;T&lt;/code&gt; motion in the opposite direction
&lt;ul&gt;
&lt;li&gt;for example, &lt;kbd&gt;tc&lt;/kbd&gt; becomes &lt;kbd&gt;Tc&lt;/kbd&gt; and vice versa&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; Note that the previously used count prefix wouldn't be repeated with &lt;kbd&gt;;&lt;/kbd&gt; or &lt;kbd&gt;,&lt;/kbd&gt; commands, but you can use a new count prefix. If you pressed a wrong motion command, use the &lt;kbd&gt;Esc&lt;/kbd&gt; key to abandon the search instead of continuing with the wrongly chosen command.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video demo&lt;/strong&gt;:&lt;/p&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also my &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;Vim Reference Guide&lt;/a&gt; and &lt;a href="https://learnbyexample.github.io/curated_resources/vim.html"&gt;curated list of resources for Vim&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 10 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/tips/vim-tip-20/</guid></item><item><title>Crypto II: On fiat currencies vs. crypto currencies</title><link>https://bytepawn.com/my-thoughts-on-fiat-and-crypto-currencies.html</link><description>&lt;p&gt;Are cryptocurrencies really better than fiat currencies? I argue that the answer is negative.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Price of a Big Mac in BTC and USD" src="/images/bigmac_btc.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 09 Jan 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/my-thoughts-on-fiat-and-crypto-currencies.html</guid></item><item><title>Activity Pub vs Web Frameworks</title><link>https://danpalmer.me/2023-01-08-activitypub-vs-web-frameworks/</link><description>In an attempt to self-host a low-cost fediverse node, I started with GoToSocial, but later decided to switch to Mastodon for better compatibility. This transition presented some challenges and got me thinking about whether existing web frameworks are well designed for linked data services.
Activity Pub, the underlying protocol for the fediverse, necessitates storing URIs to resources on other nodes in the network, and as such, even after running GoToSocial for 24 hours, there were already many links to the node.</description><author>Dan Palmer</author><pubDate>Sun, 08 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://danpalmer.me/2023-01-08-activitypub-vs-web-frameworks/</guid></item><item><title>Removing duplicates irrespective of field order</title><link>https://learnbyexample.github.io/duplicates-irrespective-field-order/</link><description>&lt;p&gt;I posted a coding challenge in the tenth issue of &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt;. I discuss the problem and various solutions in this blog post.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="problem-statement"&gt;Problem statement&lt;a class="zola-anchor" href="#problem-statement"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Retain only the first copy of duplicate lines irrespective of the order of the fields. Input order should be maintained. Assume space as the field separator with exactly two fields on each line. For example, &lt;code&gt;hehe haha&lt;/code&gt; and &lt;code&gt;haha hehe&lt;/code&gt; will be considered as duplicates.&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ cat twos.txt
&lt;/span&gt;&lt;span&gt;hehe haha
&lt;/span&gt;&lt;span&gt;door floor
&lt;/span&gt;&lt;span&gt;haha hehe
&lt;/span&gt;&lt;span&gt;6;8 3-4
&lt;/span&gt;&lt;span&gt;true blue
&lt;/span&gt;&lt;span&gt;hehe bebe
&lt;/span&gt;&lt;span&gt;floor door
&lt;/span&gt;&lt;span&gt;3-4 6;8
&lt;/span&gt;&lt;span&gt;tru eblue
&lt;/span&gt;&lt;span&gt;haha hehe
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Expected output for the above sample:&lt;/strong&gt;&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;hehe haha
&lt;/span&gt;&lt;span&gt;door floor
&lt;/span&gt;&lt;span&gt;6;8 3-4
&lt;/span&gt;&lt;span&gt;true blue
&lt;/span&gt;&lt;span&gt;hehe bebe
&lt;/span&gt;&lt;span&gt;tru eblue
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="python-solution"&gt;Python solution&lt;a class="zola-anchor" href="#python-solution"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here's one possible solution for this problem:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;filename &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'twos.txt'
&lt;/span&gt;&lt;span&gt;keys &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;set&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;with &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;open&lt;/span&gt;&lt;span&gt;(filename) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;as &lt;/span&gt;&lt;span&gt;f:
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;line &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;f:
&lt;/span&gt;&lt;span&gt;        fields &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;line.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;        key1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #668f14;"&gt;f&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;{fields[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]} {fields[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;]}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span&gt;        key2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #668f14;"&gt;f&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;{fields[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;]} {fields[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if not &lt;/span&gt;&lt;span&gt;(key1 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;keys &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;or &lt;/span&gt;&lt;span&gt;key2 &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;keys):
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(line, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;end&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;            keys.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;add&lt;/span&gt;&lt;span&gt;(key1)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main trick in the above solution is to check the input field order as well as the reversed order against elements in a set. A subtle point to note is that the &lt;code&gt;split()&lt;/code&gt; string method also removes whitespaces from the start and end of the input line. If you had to use another field delimiter (for example, comma) you'll have to remove the line ending before splitting the input.&lt;/p&gt;
&lt;p&gt;And here's a generic solution for any number of fields, which also makes the solution look simpler:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;filename &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'twos.txt'
&lt;/span&gt;&lt;span&gt;keys &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #a2a001;"&gt;set&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;with &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;open&lt;/span&gt;&lt;span&gt;(filename) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;as &lt;/span&gt;&lt;span&gt;f:
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;line &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;f:
&lt;/span&gt;&lt;span&gt;        fields &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;line.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;split&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;        sorted_key &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #d07711;"&gt;' '&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;sorted&lt;/span&gt;&lt;span&gt;(fields))
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;sorted_key &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;not in &lt;/span&gt;&lt;span&gt;keys:
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #b39f04;"&gt;print&lt;/span&gt;&lt;span&gt;(line, &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;end&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;            keys.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;add&lt;/span&gt;&lt;span&gt;(sorted_key)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In case you are wondering why space is used to join the field contents, it is necessary to avoid false matches. &lt;code&gt;tru eblue&lt;/code&gt; shouldn't be considered as a duplicate of &lt;code&gt;true blue&lt;/code&gt; or &lt;code&gt;blue true&lt;/code&gt;. Space is a safe character to use since it is the field separator.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook if you already know programming basics but new to Python.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="gnu-awk-one-liner"&gt;GNU awk one-liner&lt;a class="zola-anchor" href="#gnu-awk-one-liner"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here's a solution for CLI enthusiasts:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;$ awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'!(($1,$2) in seen || ($2,$1) in seen); {seen[$1,$2]}' &lt;/span&gt;&lt;span&gt;twos.txt
&lt;/span&gt;&lt;span&gt;hehe haha
&lt;/span&gt;&lt;span&gt;door floor
&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;6&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;8 3&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;4
&lt;/span&gt;&lt;span&gt;true blue
&lt;/span&gt;&lt;span&gt;hehe bebe
&lt;/span&gt;&lt;span&gt;tru eblue
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above solution is similar to the first Python solution with a notable difference. The fields are joined using &lt;code&gt;\034&lt;/code&gt; (a non-printing character), which is usually not present in text files.&lt;/p&gt;
&lt;p&gt;A solution using the field separator instead of &lt;code&gt;\034&lt;/code&gt; would look like:&lt;/p&gt;
&lt;pre class="language-ruby " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-ruby"&gt;&lt;span&gt;awk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'!(($1 FS $2) in seen || ($2 FS $1) in seen); {seen[$1 FS $2]}'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my &lt;a href="https://github.com/learnbyexample/learn_gnuawk"&gt;CLI text processing with GNU awk&lt;/a&gt; ebook if you are interested in such one-liners.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Sat, 07 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/duplicates-irrespective-field-order/</guid></item><item><title>Counting nested braces</title><link>https://learnbyexample.github.io/counting-nested-braces/</link><description>&lt;p&gt;I posted a coding challenge in the fifth issue of &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt;. I discuss the problem and Python/Perl solutions in this blog post.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="problem-statement"&gt;Problem statement&lt;a class="zola-anchor" href="#problem-statement"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Write a function that returns the maximum nested depth of curly braces for a given string input. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;'a*{b+c}'&lt;/code&gt; should return &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;'{{a+2}*{{b+{c*d}}+e*d}}'&lt;/code&gt; should return &lt;code&gt;4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;unbalanced or wrongly ordered braces like &lt;code&gt;'{{a}*b'&lt;/code&gt; and &lt;code&gt;'}a+b{'&lt;/code&gt; should return &lt;code&gt;-1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="python-solution"&gt;Python solution&lt;a class="zola-anchor" href="#python-solution"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here's one possible solution for this problem:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;def &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;max_nested_braces&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;expr&lt;/span&gt;&lt;span&gt;):
&lt;/span&gt;&lt;span&gt;    max_count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;for &lt;/span&gt;&lt;span&gt;char &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;in &lt;/span&gt;&lt;span&gt;expr:
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;char &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;== &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{'&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;            count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+= &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;max_count:
&lt;/span&gt;&lt;span&gt;                max_count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;count
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;elif &lt;/span&gt;&lt;span&gt;char &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;== &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'}'&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;== &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return -&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;            count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;-= &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;!= &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return -&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span&gt;max_count
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; In case you have trouble understanding the above code, you can use &lt;a href="https://www.pythontutor.com/visualize.html#mode=edit"&gt;pythontutor&lt;/a&gt; to visualize the code execution step-by-step.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's an alternate solution using regular expressions:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;import &lt;/span&gt;&lt;span&gt;re
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;def &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;max_nested_braces&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;expr&lt;/span&gt;&lt;span&gt;):
&lt;/span&gt;&lt;span&gt;    count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;while &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;True&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;        expr, no_of_subs &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;subn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\{[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{}]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, expr)
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;no_of_subs &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;== &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;break
&lt;/span&gt;&lt;span&gt;        count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+= &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[{}]&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, expr):
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return -&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span&gt;count
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you are a fan of assignment expressions:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #72ab00;"&gt;import &lt;/span&gt;&lt;span&gt;re
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;def &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;max_nested_braces&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;expr&lt;/span&gt;&lt;span&gt;):
&lt;/span&gt;&lt;span&gt;    count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;while &lt;/span&gt;&lt;span&gt;(op &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;:= &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;subn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\{[&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;^&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;{}]&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;*&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;\}&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;''&lt;/span&gt;&lt;span&gt;, expr)) &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;and &lt;/span&gt;&lt;span&gt;op[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1&lt;/span&gt;&lt;span&gt;]:
&lt;/span&gt;&lt;span&gt;        expr &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;= &lt;/span&gt;&lt;span&gt;op[&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;0&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;        count &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;+= &lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;if &lt;/span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #668f14;"&gt;r&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span style="color: #aeb52b;"&gt;[{}]&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'&lt;/span&gt;&lt;span&gt;, expr):
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return -&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;1
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;return &lt;/span&gt;&lt;span&gt;count
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; I verified these solutions using &lt;code&gt;assert&lt;/code&gt; statements. See &lt;a href="https://learnbyexample.github.io/100_page_python_intro/testing.html"&gt;Testing&lt;/a&gt; chapter from my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook for more details.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See &lt;a href="https://learnbyexample.github.io/py_regular_expressions/working-with-matched-portions.html#resubn"&gt;Working with matched portions&lt;/a&gt; chapter from my &lt;a href="https://github.com/learnbyexample/py_regular_expressions"&gt;Understanding Python re(gex)?&lt;/a&gt; ebook for more details about the &lt;code&gt;re.subn()&lt;/code&gt; function.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="perl-one-liner"&gt;Perl one-liner&lt;a class="zola-anchor" href="#perl-one-liner"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here's a solution for CLI enthusiasts:&lt;/p&gt;
&lt;pre style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code&gt;&lt;span&gt;$ cat ip.txt 
&lt;/span&gt;&lt;span&gt;{a+2}*{b+c}
&lt;/span&gt;&lt;span&gt;{{a+2}*{{b+{c*d}}+e*d}}
&lt;/span&gt;&lt;span&gt;a*b{
&lt;/span&gt;&lt;span&gt;{{a+2}*{{b}+{c*d}}+e*d}}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;$ perl -lne '$c=0; $c++ while(s/\{[^{}]*\}//g);
&lt;/span&gt;&lt;span&gt;             print /[{}]/ ? -1 : $c' ip.txt
&lt;/span&gt;&lt;span&gt;1
&lt;/span&gt;&lt;span&gt;4
&lt;/span&gt;&lt;span&gt;-1
&lt;/span&gt;&lt;span&gt;-1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See my &lt;a href="https://github.com/learnbyexample/learn_perl_oneliners"&gt;Perl One-Liners Guide&lt;/a&gt; ebook if you are interested in learning to use Perl from the command-line.&lt;/p&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; If you are interested in &lt;code&gt;awk&lt;/code&gt; and &lt;code&gt;bash&lt;/code&gt; solutions, see &lt;a href="https://unix.stackexchange.com/q/680920/109046"&gt;this unix.stackexchange thread&lt;/a&gt;.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Sat, 07 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/counting-nested-braces/</guid></item><item><title>Escaping madness to get literal field separators in awk</title><link>https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/</link><description>&lt;p&gt;I'm building a tool called &lt;a href="https://github.com/learnbyexample/regexp-cut"&gt;rcut&lt;/a&gt; that allows you to use &lt;code&gt;cut&lt;/code&gt; like syntax with features like regexp based delimiters. The solution uses &lt;code&gt;awk&lt;/code&gt; inside a &lt;code&gt;bash&lt;/code&gt; script.&lt;/p&gt;
&lt;p&gt;Latest &lt;a href="https://en.wikipedia.org/wiki/Feature_creep"&gt;feature creep&lt;/a&gt; is fixed string field splitting. I thought it would be a simple enough solution to add.&lt;/p&gt;
&lt;p&gt;I was wrong.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="how-many-escapes-for-a-single-backslash"&gt;How many escapes for a single backslash?&lt;a class="zola-anchor" href="#how-many-escapes-for-a-single-backslash"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For reference, these are the versions I have on my machine:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; gawk&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; --version
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;GNU&lt;/span&gt;&lt;span&gt; Awk 5.1.0, API: 3.0
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; mawk&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -W&lt;/span&gt;&lt;span&gt; version
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mawk&lt;/span&gt;&lt;span&gt; 1.3.4 20200120
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mawk&lt;/code&gt; and &lt;code&gt;gawk&lt;/code&gt; differ when it comes to escaping backslashes. You'll later see the rule that'll work correctly for both implementations.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple\bake\cake' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'e\' '{print $2}'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;bak
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple\bake\cake' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'e\' '{print $2}'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk:&lt;/span&gt;&lt;span&gt; fatal: invalid regexp: Trailing backslash: /e&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\/
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple\bake\cake' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'e\\' '{print $2}'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk:&lt;/span&gt;&lt;span&gt; fatal: invalid regexp: Trailing backslash: /e&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\/
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple\bake\cake' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'e\\\' '{print $2}'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;bak
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The value assigned to &lt;code&gt;FS&lt;/code&gt; is treated as a string and then converted to a regexp. &lt;code&gt;\&lt;/code&gt; is a metacharacter for string and regexp both. So, &lt;code&gt;\\&lt;/code&gt; in a string means a single backslash and &lt;code&gt;\\\\&lt;/code&gt; means double backslash. Double backslash in regexp means a single backslash.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;: For a consistent behavior across both &lt;code&gt;mawk&lt;/code&gt; and &lt;code&gt;gawk&lt;/code&gt; and irrespective of trailing backslash errors, you need to use 4 backslashes for every backslash.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# both 2 and 4 backslashes here gets treated as single backslash
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# hence the empty fields in the output
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,,2,,3
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,,2,,3
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,,2,,3
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,,2,,3
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 5-8 backslashes give expected results
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,2,3
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,2,3
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\\\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,2,3
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\\\\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,2,3
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# 5-6 backslashes give error, 7-8 backslashes give expected results
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk:&lt;/span&gt;&lt;span&gt; fatal: invalid regexp: Trailing backslash: /&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\\\/
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk:&lt;/span&gt;&lt;span&gt; fatal: invalid regexp: Trailing backslash: /&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\\\/
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\\\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,2,3
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\\\\\'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,2,3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an alternate method, you can use codepoint of the backslash character. This removes one level of escaping. See &lt;a href="https://ascii.cl/"&gt;ASCII code table&lt;/a&gt; for codepoint reference.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;: You need &lt;code&gt;\x5c\x5c&lt;/code&gt; for every backslash.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple\bake\cake' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'e\x5c\x5c' '{print $2}'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;bak
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'apple\bake\cake' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'e\x5c\x5c' '{print $2}'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;bak
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;mawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\x5c\x5c\x5c\x5c'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,2,3
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'1\\2\\3' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\x5c\x5c\x5c\x5c'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;1,2,3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="using-awk-to-generate-an-escaped-string"&gt;Using awk to generate an escaped string&lt;a class="zola-anchor" href="#using-awk-to-generate-an-escaped-string"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Suppose you want to use &lt;code&gt;\.&lt;/code&gt; literally for field splitting. Here's some ways to do it that works for both &lt;code&gt;mawk&lt;/code&gt; and &lt;code&gt;gawk&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'x\2\.y\.z' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\\\.'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;x&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\2&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;,y,z
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'x\2\.y\.z' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\\\\[.]'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;x&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\2&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;,y,z
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'x\2\.y\.z' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk -F&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'\x5c\x5c[.]'&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -v&lt;/span&gt;&lt;span&gt; OFS=, &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{$1=$1} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;x&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\2&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;,y,z
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, the task is to generate one of the above strings passed to the &lt;code&gt;-F&lt;/code&gt; option from &lt;code&gt;\.&lt;/code&gt; as input. Using &lt;code&gt;sed&lt;/code&gt; is better, but for &lt;a href="https://github.com/learnbyexample/regexp-cut"&gt;rcut&lt;/a&gt;, I didn't want to add another external tool.&lt;/p&gt;
&lt;h3 id="case-1-backslash-madness"&gt;Case 1: backslash madness&lt;a class="zola-anchor" href="#case-1-backslash-madness"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You need to convert &lt;code&gt;\&lt;/code&gt; to 4 backslashes and escape regexp metacharacters with 2 backslashes. Note that you cannot escape all characters except &lt;code&gt;\&lt;/code&gt; with 2 backslashes, for example &lt;code&gt;\\t&lt;/code&gt; will become a tab character! Also, you need to escape &lt;code&gt;\&lt;/code&gt; first and then escape the other metacharacters.&lt;/p&gt;
&lt;p&gt;Ready for the solution? I'm not even going to try explaining this, found it by experimenting.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# replacement string for the first gsub has 16 backslashes
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# replacement string for the second gsub has 8 backslashes
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a.b\c^d' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{gsub(/\\/, &amp;quot;\\\\\\\\\\\\\\\\&amp;quot;);
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;                          gsub(/[{[(^$*?+.|]/, &amp;quot;\\\\\\\\&amp;amp;&amp;quot;)} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;a&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\\&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;.b&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\\\\&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;c&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\\&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;^d
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; &lt;img alt="warning" src="/images/warning.svg" /&gt; &lt;a href="https://www.gnu.org/software/gawk/manual/gawk.html#Gory-Details"&gt;gawk manual: Gory details&lt;/a&gt; might help you understand the above solution.&lt;/p&gt;
&lt;h3 id="case-2-character-class"&gt;Case 2: character class&lt;a class="zola-anchor" href="#case-2-character-class"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the characteristic of character class is that you can enclose all characters except &lt;code&gt;\&lt;/code&gt; and &lt;code&gt;^&lt;/code&gt; to match them literally. The &lt;code&gt;\&lt;/code&gt; character is special both inside/outside of character class and &lt;code&gt;[^]&lt;/code&gt; is invalid since &lt;code&gt;^&lt;/code&gt; is special if used as the first character.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a.b\c^d' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{gsub(/\\/, &amp;quot;\\\\\\\\\\\\\\\\&amp;quot;);
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;                          gsub(/[^^\\]/, &amp;quot;[&amp;amp;]&amp;quot;);
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;                          gsub(/\^/, &amp;quot;\\\\^&amp;quot;)} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;[a][.][b]&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\\\\&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;[c]&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\\&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;^[d]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="case-3-codepoint-to-represent-backslash"&gt;Case 3: codepoint to represent backslash&lt;a class="zola-anchor" href="#case-3-codepoint-to-represent-backslash"&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Finally, my preferred solutions that uses codepoint instead of escaping backslashes.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# case 1 alternate
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a.b\c^d' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{gsub(/\\/, &amp;quot;\\x5c\\x5c&amp;quot;);
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;                          gsub(/[{[(^$*?+.|]/, &amp;quot;\\x5c&amp;amp;&amp;quot;)} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;a&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\x&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;5c.b&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\x&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;5c&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\x&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;5cc&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\x&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;5c^d
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# case 2 alternate
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'a.b\c^d' &lt;/span&gt;&lt;span style="color: #72ab00;"&gt;| &lt;/span&gt;&lt;span style="color: #5597d6;"&gt;gawk &lt;/span&gt;&lt;span style="color: #d07711;"&gt;'{gsub(/[^^\\]/, &amp;quot;[&amp;amp;]&amp;quot;);
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;                          gsub(/\\/, &amp;quot;\\x5c\\x5c&amp;quot;);
&lt;/span&gt;&lt;span style="color: #d07711;"&gt;                          gsub(/\^/, &amp;quot;\\x5c^&amp;quot;)} 1'
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;[a][.][b]&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\x&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;5c&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\x&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;5c[c]&lt;/span&gt;&lt;span style="color: #b3933a;"&gt;\x&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;5c^[d]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="sanity-check"&gt;Sanity check&lt;a class="zola-anchor" href="#sanity-check"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I probably lost my sanity trying to come up with a solution and again while writing this post. I did try a few sanity checks for the solutions presented here, but there's a chance I messed up or missed some corner case. If you spot an issue, do let me know.&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Sat, 07 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/escaping-madness-awk-literal-field-separator/</guid></item><item><title>What I'm up to - January 2023</title><link>https://www.philipithomas.com/posts/what-i-m-up-to-january-2023</link><description>&lt;div class="prose"&gt;
  &lt;div&gt;✨ &lt;strong&gt;Highlights of last month&lt;/strong&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Traveled to Hilton Head, Savannah, and Denver for the holidays&lt;/li&gt;
&lt;li&gt;Had a couple of flights canceled by Southwest&lt;/li&gt;
&lt;li&gt;Published "&lt;a href="https://www.philipithomas.com/posts/why-i-built-postcard-a-calmer-alternative-to-social-networks"&gt;Why I built Postcard: A calmer alternative to social networks&lt;/a&gt;" and "&lt;a href="https://www.philipithomas.com/posts/how-to-replace-social-media-with-a-personal-newsletter"&gt;How to replace social media with a personal newsletter&lt;/a&gt;", which were popular online &lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;strong&gt;🤔 Things and thoughts to share&lt;/strong&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Articles I've been reading: &lt;a href="https://www.nytimes.com/2022/12/15/style/teens-social-media.html"&gt;‘Luddite’ Teens Don’t Want Your Likes&lt;/a&gt;, &lt;a href="https://craigmod.com/essays/how_i_got_my_attention_back/"&gt;How I got my attention back&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Overton_window"&gt;Overton window (Wikipedia)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Been reading &lt;a href="https://www.amazon.com/Novelist-as-Vocation-Haruki-Murakami-ebook/dp/B09S37T6TM/ref=tmm_kin_swatch_0?_encoding=UTF8&amp;amp;qid=1673027309&amp;amp;sr=8-2"&gt;Novelist as a Vocation&lt;/a&gt; by Haruki Murakami. Lots of great insights - including how he avoids writer's block. &lt;/li&gt;
&lt;li&gt;Been drinking the &lt;a href="https://www.prologcoffee.com/collections/buy-our-coffee/products/roble-negro-washed-geisha-1?variant=39793244405825"&gt;Prolog Jorge Vasquez Geisha&lt;/a&gt;, which is one of the better coffees I've had. Also, I've been enjoying a Kenyan coffee from &lt;a href="https://www.kahiwacoffee.fi/"&gt;Kahiwa Coffee Roasters&lt;/a&gt; in Finland. Kahiwa caught my attention because it's co-owned by F1 driver Valtteri Bottas.  &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://prompthunt.com"&gt;PromptHunt&lt;/a&gt; aggregates the prompts that people are using with AI. "Prompt developer" is an emerging job.  &lt;/li&gt;
&lt;li&gt;Apple's new &lt;a href="https://techcrunch.com/2022/12/07/apple-launches-end-to-end-encryption-for-icloud-data/?guccounter=1"&gt;end-to-end iCloud encryption&lt;/a&gt; is a win for consumer privacy - I'm in the process of switching from Google Photos to iCloud because of this.&lt;/li&gt;
&lt;li&gt;Why are we rating everything? A former #1 restaurant in the world only has 4.6 stars on Google. Is that really useful?&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;📫 &lt;strong&gt;What I'm up to this month&lt;/strong&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Adding lots of refinements to Postcard, and helping people &lt;a href="https://postcard.page/alternative/revue"&gt;switch over from Twitters's Revue product&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Working on some interesting projects with crypto and AI &lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;
&lt;strong&gt;📍 Where I'll be &lt;/strong&gt;&lt;em&gt;(Let me know if we overlap!)&lt;/em&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Jan 28 - Feb 4: 🇲🇽 Cozumel&lt;/li&gt;
&lt;li&gt;Feb 18-24: 🏙️ NYC&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;
&lt;br /&gt;&lt;figure class="attachment attachment--preview attachment--jpg"&gt;

  &lt;img class="attachment__image" src="https://a.postcard.page/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbHN2IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ead130676d2e390cfee325aaf07bce28fb848dad/IMG_6109(1).jpg" /&gt;

  &lt;figcaption class="attachment__caption"&gt;Turns out my dog likes beaches&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;</description><author>Philip I. Thomas</author><pubDate>Fri, 06 Jan 2023 22:09:51 GMT</pubDate><guid isPermaLink="true">https://www.philipithomas.com/posts/what-i-m-up-to-january-2023</guid></item><item><title>I scanned every package on PyPi and found 57 live AWS keys</title><link>https://tomforb.es/blog/aws-keys-on-pypi/</link><description>After inadvertently finding that InfoSys leaked an AWS key on PyPi I wanted to know how many other live AWS keys may be present on Python package index. After scanning every release published to PyPi I found 57 valid access keys from organisations like: Amazon themselves 😅 Intel Stanford , Portland...</description><author>Tom Forbes</author><pubDate>Fri, 06 Jan 2023 20:31:57 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/aws-keys-on-pypi/</guid></item><item><title>Open Budgeting - Subscriptions &amp;amp; Licences</title><link>https://smcleod.net/2023/01/open-budgeting-subscriptions-licences/</link><description>Publicly documenting my subscription and license expenses</description><author>smcleod.net</author><pubDate>Thu, 05 Jan 2023 14:41:37 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2023/01/open-budgeting-subscriptions-licences/</guid></item><item><title>Favorite compiler and interpreter resources</title><link>http://notes.eatonphil.com/2023-01-04-compiler-resources.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://lists.eatonphil.com/compilers-and-interpreters.html"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 05 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-01-04-compiler-resources.html</guid></item><item><title>Mastodon</title><link>https://paulstamatiou.com/mastodon</link><description>&lt;img alt="A Mastodon app called Ivory" src="https://turbo.paulstamatiou.com/uploads/2022/12/copyright-paul-stamatiou_com-stammy-mastodon-ivory.jpg" /&gt;
&lt;small&gt;A Mastodon app called Ivory&lt;/small&gt;
&lt;p&gt;&lt;a href="https://paulstamatiou.com/odeo-launches-twttr-hellodeo/"&gt;I first wrote about Twitter&lt;/a&gt; (née twttr) just over 16 years ago. Over the years, Twitter’s place and impact in the world has become clear. It’s not just a place to hear about breaking news, keep up with celebrities or see what your politicians are saying. It’s a place where communities are formed and thrive. It is the global town square.&lt;/p&gt;
&lt;p&gt;But that’s all been changing daily ever since Elon Musk took over. This has become the tipping point for many to reconsider their reliance on big tech companies with closed platforms and to be in control of their data. This whole Twitter/Elon saga sucks, but maybe it’s just the kick we needed.&lt;/p&gt;
&lt;p&gt;And with that, I’m starting to spend more time on Mastodon, and I’ll tell you why. Follow me on Mastodon: &lt;a href="https://stammy.design/@stammy"&gt;@stammy@stammy.design&lt;/a&gt; (you’ll have to search for it on your Mastodon server).&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;h3&gt;Part 2: Hosting your own Mastodon server&lt;/h3&gt;&lt;p&gt;Want to have complete control and host your own Mastodon server?&lt;/p&gt;&lt;p&gt;Shortly after I wrote this post, I loved Mastodon so much I decided to host my own server and document the process for those that would want to do the same. In &lt;strong&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/"&gt;Hosting your own Mastodon server&lt;/a&gt;&lt;/strong&gt; I show two ways to host your own server (using a fully-managed Mastodon host, and using a VPS provider). I go into everything from scripts to backup your database and run media clean up tasks, to advanced settings to optimize your server, and much more.&lt;/p&gt;&lt;a href="https://paulstamatiou.com/hosting-your-own-mastodon-server/" title="Hosting your own Mastodon server"&gt;&lt;img alt="Hosting your own Mastodon server" src="https://turbo.paulstamatiou.com/uploads/2023/01/copyright-paulstamatiou_com-mastodon-mbp.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;Twitter quickly became a core part of how I used the internet, made friends, learned things, kept up with the world, killed time, and so much more. I eventually went to &lt;a href="https://paulstamatiou.com/joining-twitter/"&gt;work at Twitter for nine years&lt;/a&gt;. Twitter, the company, had lots of ups and downs, but the people behind it cared so much about the platform and its place in the world. It’s the critical conversational layer of the internet. Creators have active social media accounts elsewhere online, but they always relied on Twitter to truly connect and have conversations with their community. It was the vital hub.&lt;/p&gt;
&lt;p&gt;Well, it’s a different bird site now. I don’t intend to summarize the series of events and drama that have unfolded ever since Elon Musk took over Twitter, nor do I have the energy for that, but if you’ve seen even a portion of all the headlines, you get where I’m coming from.&lt;/p&gt;
&lt;p&gt;Seeing the company&amp;#x27;s culture get slaughtered so quickly was hard. It was a culture woven around caring for coworkers, caring for people that used Twitter, and caring about the good of the service.&lt;/p&gt;
&lt;p&gt;A few weeks ago was the last straw for me. Reputable accounts were randomly banned, and Twitter announced that people could no longer Tweet links to other social networks that were deemed competitive. Then more people were banned for saying they were leaving. And then, suddenly, this big policy suddenly vanished.&lt;/p&gt;
&lt;p&gt;I lost a lot of hope and confidence in Twitter and how it is being run. I still think Twitter will be around for some time, but I’m not sure how much I want to continue investing in it as much as I used to.&lt;/p&gt;
&lt;p&gt;I had seen some folks migrating away to alternate services in recent months but didn’t think much of it. Twitter was here to stay, right?&lt;/p&gt;
&lt;h3&gt;Fool me once...&lt;/h3&gt;
&lt;p&gt;The Twitter/Elon saga has a silver lining that I can appreciate. It shows us that we’ve all become too comfortable with just a few large tech companies amassing our info, controlling our data, and what we do with it.&lt;/p&gt;
&lt;p&gt;If you’re the typical user of internet services from large companies, you probably haven’t had too many issues. Sure, maybe you got locked out of your Google account once, and it took a long time for support to help you out, but for the most part, you didn’t have to think about it. The big companies were mostly staying in their lane and not doing anything too egregious (aside from miscellaneous privacy dramas). But they could have been.&lt;/p&gt;
&lt;p&gt;This Twitter drama is a perfect example of what can go wrong and how quickly it can go wrong. It’s too much. I’ve invested a lot of time into Twitter (thanks to &lt;a href="https://twitter.com/stammy"&gt;the 53,000+ of you&lt;/a&gt; that have been following me there).&lt;/p&gt;
&lt;p&gt;I no doubt expect Elon to reverse some of his decisions from the regular backlash from users and press, but there’s no trust anymore. Even if a new CEO or a new owner is announced, I don’t know if Twitter can ever be truly trusted again. Fool me once, shame on you, fool me twice, shame on me..&lt;/p&gt;
&lt;p&gt;All this to say, I think it’s time we take more control of our data. I’ve been trying to reduce my reliance on Google accounts and services already. I’m sure I end up moving on to Proton or elsewhere in 2023.&lt;/p&gt;
&lt;p&gt;This does all make me a tad nostalgic for the early 2000s. We were all just running our own little blogs in a corner of the internet and keeping up with each other with RSS readers. Social media was just our own little websites. Why can’t we just go back to that?&lt;/p&gt;
&lt;h3&gt;Mastodon&lt;/h3&gt;
&lt;h3&gt;A decentralized social network built on an open protocol&lt;/h3&gt;
&lt;img alt="The Mastodon server I use, stammy.design" src="https://turbo.paulstamatiou.com/uploads/2022/12/mastodon-site.webp" /&gt;
&lt;small&gt;The Mastodon server I use, &lt;a href="https://stammy.design/@stammy"&gt;stammy.design&lt;/a&gt;, though most Mastodon sites looks like this.&lt;/small&gt;
&lt;p&gt;I’ve started spending more time on &lt;a href="https://joinmastodon.org"&gt;Mastodon&lt;/a&gt;, open-source software for running a decentralized social networking service that’s not somewhat similar to Twitter. It’s decentralized in that there’s no single website or company running the show. It’s not a single platform.&lt;/p&gt;
&lt;p&gt;Instead, Mastodon is comprised of a network of independent servers, which you may see referred to as Mastodon instances. Each server is run by a different person that manages and moderates it. You can sign up for an account on any server you want, and you can follow people on any other server just like you would on Twitter. (There are exceptions, like if one server blocks another server—defederation).&lt;/p&gt;
&lt;p&gt;Mastodon was first released in late 2016 by &lt;a href="https://mastodon.social/@Gargron"&gt;Eugen Rochko&lt;/a&gt;, who wanted to create a platform not controlled by a single company or person. Mastodon was developed as a non-profit, and the platform is ad-free. It has made specific design choices to reduce harassment and give users more control over their experience and create a safer environment.&lt;/p&gt;
&lt;p&gt;As a long-time Twitter employee, I had heard of Mastodon many times (as well as a predecessor, &lt;a href="https://en.wikipedia.org/wiki/Diaspora_(social_network)"&gt;diaspora&lt;/a&gt;) but never took much time to dive deeper. It always felt like a rudimentary platform 10 years behind Twitter. In hindsight, it was foolish to think of it in terms of features rather than protocols. That was the real innovation with Mastodon, not whether it had certain product features.&lt;/p&gt;
&lt;p&gt;Mastodon servers communicate with each other with open-source software using a standardized, open protocol called ActivityPub for sharing updates. There are quite a few other open-source projects using ActivityPub, such as &lt;a href="https://pixelfed.org/"&gt;Pixelfed&lt;/a&gt; and &lt;a href="https://pleroma.social/"&gt;Pleroma&lt;/a&gt;, but Mastodon is the most popular. You can even run your own Mastodon instance all by yourself if you want.&lt;/p&gt;
&lt;p&gt;You may also see it referred to as a &lt;a href="https://en.wikipedia.org/wiki/Distributed_social_network"&gt;federated social network&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Fediverse"&gt;fediverse&lt;/a&gt;. There’s also a bit of nuance here when it comes to federation. It’s a form of decentralization, but not every instance needs to connect with another instance. A Mastodon instance itself can be entirely self-contained, with users on it following each other only (though not common). While a huge benefit, federation also leads to some product constraints.&lt;/p&gt;
&lt;h3&gt;Federated, like email&lt;/h3&gt;
&lt;p&gt;Federation also means that you not only have to know someone’s username but also their server. This leads to email-like usernames, such as mine: &lt;a href="https://stammy.design/@stammy"&gt;@stammy@stammy.design&lt;/a&gt;. You need both bits of information to follow someone, which makes it a tad harder to use than a centralized service. Someone can always just search for you with your username only, but it’s not reliable for various reasons related to how the federation is done.&lt;/p&gt;
&lt;p&gt;This also means your username can’t really be taken. It could be taken on a particular Mastodon server, but you can always use another server or self-host Mastodon on your own domain. It also leads to there being no such thing as verified accounts on Mastodon. There’s no central authority deeming which account is verified or not.&lt;/p&gt;
&lt;h3&gt;And it’s growing.&lt;/h3&gt;
&lt;p&gt;Mastodon is having a bit of a moment right now. The non-profit that runs Mastodon even &lt;a href="https://arstechnica.com/tech-policy/2022/12/twitter-rival-mastodon-rejects-funding-to-preserve-nonprofit-status/"&gt;turned away multiple VC investment offers&lt;/a&gt;. With all that’s happening at Twitter, people have been longing for a more stable product, one where their data could be more trusted, one that’s ad-free, supported by communities, and moderated by communities. One where terms and policies don’t change daily.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.mozilla.org/en/mozilla/mozilla-launch-fediverse-instance-social-media-alternative/"&gt;Mozilla announced&lt;/a&gt; they will be launching their own instance—&lt;code&gt;Mozilla.social&lt;/code&gt;—next year in pursuit of building a healthy alternative to today’s social media platforms. Even the &lt;a href="https://www.washingtonpost.com/technology/2022/12/17/how-to-join-mastodon/"&gt;Washington Post&lt;/a&gt; wrote about how to get started with Mastodon.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"While there is no shortage of social media platforms new and old, this is a radically different approach to social media that offers something traditional social media cannot. This may be one of the reasons why &lt;strong&gt;Mastodon has recently exploded in popularity, jumping from approx. 300K monthly active users to 2.5M&lt;/strong&gt; between the months of October and November, with more and more journalists, political figures, writers, actors and organizations moving over."&lt;/p&gt;
&lt;p&gt;—&lt;a href="https://blog.joinmastodon.org/2022/12/twitter-suspends-mastodon-account-prevents-sharing-links/"&gt;Mastodon&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Getting started with Mastodon&lt;/h3&gt;
&lt;h3&gt;It’s not easy, but it’s worth it.&lt;/h3&gt;
&lt;p&gt;Now that we’ve got a basic grasp of Mastodon, how it’s federated like email but similar in functionality to Twitter, it’s time to give it a try.&lt;/p&gt;
&lt;p&gt;But maybe a bit of a heads-up first. While Mastodon may feel comparable in look and functionality to Twitter, it’s probably best to approach Mastodon with the mindset that it is a new platform rather than trying to view it as a Twitter clone. There are key differences that will take some getting used to, and it will be a bit harder to get started.&lt;/p&gt;
&lt;p&gt;The first challenge is signing up. You can’t just go to the Mastodon website and create an account. You first need to pick &lt;em&gt;where&lt;/em&gt; you want your account by picking a Mastodon server you wish to call your home.&lt;/p&gt;
&lt;h3&gt;Pick a server, follow anyone on any server&lt;/h3&gt;
&lt;p&gt;The concept of Mastodon servers ends up being a confusing part of the Mastodon onboarding and usage experience. Even after you have an account on one server, you can’t just log into any other Mastodon server. You always have to log into yours. But you will be able to follow anyone on your own server, even if they’re on a different Mastodon server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So how do you find and pick a server, and does it matter which one you pick?&lt;/strong&gt; There are several schools of thought on this, but I’ll try to explain it as best I can.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The argument for picking any server:&lt;/strong&gt; You can easily change servers later on if you want. Just pick a server that looks interesting and go for it. If you already know other Mastodon users that you want to follow and don’t need too much assistance with discovering content and accounts, you’ll be okay.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The argument for carefully picking your server:&lt;/strong&gt;
Picking a server is like deciding where to live in a city or what subreddit you want to frequent. You’ll more easily run into the same people and content if you pick a server that’s relevant to your interests. You’ll also be more likely to find people who are interested in the same things as you.&lt;/p&gt;
&lt;p&gt;This is especially important if you are new to Mastodon and don’t know of many other people you’d want to follow that are on Mastodon.&lt;/p&gt;
&lt;p&gt;With Mastodon, in addition to your typical Home timeline comprised of people you explicitly follow, you also get two bonus timelines: a local timeline comprised of people on your specific Mastodon server and a federated timeline of posts from people your Mastodon instance knows about (not a full firehose of all Mastodon posts, just the one your instance subscribes to via relays and from people the instance follows).&lt;/p&gt;
&lt;p&gt;Having a high-quality local timeline gives you an opportunity to more easily discover great content related to your interests, helps other people discover you, and vice versa. This is important for a lot of reasons, the leading of which is that it’s harder to discover people to follow on Mastodon. There is no algorithmic timeline injecting relevant content you may like into your timeline, and searching on Mastodon is not great.&lt;/p&gt;
&lt;p&gt;So that’s one part of it. The other part is maybe more important: picking a server is also like picking your landlord. Do you trust the admin of the server to host your identity? Do you think they have the technical know-how to maintain and secure the infra as needed? Are they making backups? What if they get lazy, and you’re stuck on a 2-year-old version of Mastodon lacking all the new features? Do they have a plan for financially supporting the infra? Or are they running an overloaded server that is extremely slow?&lt;/p&gt;
&lt;p&gt;And finally, each Mastodon server has its own set of rules in place. The rules give you an idea of the type of community you would be joining. People that don’t abide by the rules can be actioned on by the server’s moderators and admins.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Finding your server&lt;/h3&gt;
&lt;p&gt;If there are already a lot of people you want to follow on a particular server, that’s probably a good place to start. Examples of Mastodon servers I come across often include ones like &lt;code&gt;mastodon.design&lt;/code&gt;, &lt;code&gt;front-end.social&lt;/code&gt;, &lt;code&gt;hachyderm.io&lt;/code&gt;, &lt;code&gt;infosec.exchange&lt;/code&gt;, &lt;code&gt;indieweb.social&lt;/code&gt; and more.&lt;/p&gt;
&lt;p&gt;An easy way to find where your friends are on Mastodon is by using &lt;a href="https://debirdify.pruvisto.org/"&gt;Debirdify&lt;/a&gt; or &lt;a href="https://fedifinder.glitch.me/"&gt;fedifinder&lt;/a&gt;. You authenticate your Twitter account, and it will tell you where people you follow on Twitter are on Mastodon if they’ve made Mastodon accounts. It’s an easy way to get an idea of Mastodon instances you may want to use.&lt;/p&gt;
&lt;img alt="Debirdify screenshot" height="3496" src="https://turbo.paulstamatiou.com/uploads/2022/12/debirdify-stammy.webp" width="2144" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="Fedifinder screenshot" height="1616" src="https://turbo.paulstamatiou.com/uploads/2022/12/fedifinder-stammy.webp" width="2144" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="Move to don screenshot" height="1616" src="https://turbo.paulstamatiou.com/uploads/2022/12/movetodon-stammy.webp" width="1924" /&gt;
&lt;small&gt;Debirdify, Fedifinder and Move to don&lt;/small&gt;
&lt;p&gt;You’ll quickly notice that &lt;strong&gt;not all servers have open, public registration&lt;/strong&gt;. Some may just be entirely closed or require an invite and are meant for a smaller community. Others require approval by the admin before you can join. And others may only be temporarily closed, so the admins have time to upgrade their servers to deal with the influx of new users, and you may just need to try again later.&lt;/p&gt;
&lt;p&gt;You can, of course, just browse yourself and see what’s out there. Tools like &lt;a href="https://instances.social/"&gt;instances.social&lt;/a&gt;, &lt;a href="https://mastodonservers.net/"&gt;MastodonServers.net&lt;/a&gt;, &lt;a href="https://fedidb.org/network"&gt;FediDB&lt;/a&gt;, and the &lt;a href="https://joinmastodon.org/servers"&gt;official Mastodon servers page&lt;/a&gt; can help with that.&lt;/p&gt;
&lt;p&gt;I’m on &lt;s&gt;macaw.social, a small private Mastodon instance created by former Twitter employees meant for former Twitter employees and friends&lt;/s&gt; &lt;a href="https://stammy.design"&gt;stammy.design&lt;/a&gt;, my self-hosted Mastodon instance.&lt;/p&gt;
&lt;p&gt;If this is all a bit confusing, you can always just pick from one of the huge Mastodon instances like mastodon.social, mas.to, and mastodon.cloud. However, since most of these are getting a ton of new users now, they are more likely to feel slower until they scale up their infrastructure.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Self-hosting your own Mastodon instance&lt;/h3&gt;
&lt;h3&gt;A bit more than meets the eye&lt;/h3&gt;
&lt;p&gt;Of course, the beauty of the Fediverse is that you can just host your own instance, own your data, and have complete control of your Mastodon identity! If you know that this is what you want to do, there are a few routes to take.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Managed hosting:&lt;/strong&gt;
There are a few hosting companies that specialize in hosting Mastodon servers, such as &lt;a href="https://masto.host/"&gt;Masto.host&lt;/a&gt; among &lt;a href="https://docs.joinmastodon.org/user/run-your-own/#so-you-want-to-run-your-own-mastodon-server"&gt;others&lt;/a&gt;, though at this time some have paused new signups due to high demand.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DIY hosting:&lt;/strong&gt; And then you can just do it all yourself following the &lt;a href="https://docs.joinmastodon.org/admin/install/"&gt;official guide&lt;/a&gt; on any server where you can set up Postgres, Redis, Ruby, and Sidekiq. You’ll need to have an email host so your Mastodon instance can send emails to you, and you’ll likely want some cloud storage, such as S3, for your media and files. You’ll also want to have a system in place for regular database backups. Here’s an article I found outlining someone’s experience &lt;a href="https://www.micahwalter.com/notes-on-running-a-mastodon-server-on-aws/"&gt;setting up their instance on AWS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Running a server yourself is a bit more involved than setting up a simple static website or WordPress install. And depending on how many relays you set up (more on that later), you may find yourself tuning the Sidekiq background job processor config so it can run faster. If you still want to DIY but prefer a bit of assistance, the hosting provider DigitalOcean has a &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-install-mastodon-with-digitalocean-marketplace-1-click"&gt;"1-click" setup for Mastodon&lt;/a&gt;. It’s still not a walk in the park, but it saves you quite a bit of work in the command line.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;But with great power comes great responsibility.&lt;/strong&gt; By hosting a Mastodon instance, you’ll be liable for the content on your server created by your users &lt;em&gt;and&lt;/em&gt; anyone you follow as well as what they boost (boosts are like ReTweets).&lt;/p&gt;
&lt;p&gt;Yes, you read that right. The way Mastodon works, &lt;strong&gt;everything you view on your Mastodon account, including media, is actually hosted on your server&lt;/strong&gt;, even if the Mastodon user you follow is on another instance. Their instance just sent that post to yours, essentially. So any text or media in their posts is now hosted by you. And if your instance has any relays setup, you’ll have a lot more content being cached and served from your instance.&lt;/p&gt;
&lt;p&gt;This goes down the path of needing to register a designated DMCA agent if you want to be careful. The EFF has a detailed article on the topic: &lt;a href="https://www.eff.org/deeplinks/2022/12/user-generated-content-and-fediverse-legal-primer"&gt;User Generated Content and the Fediverse: A Legal Primer&lt;/a&gt;. This &lt;a href="https://twitter.com/rahaeli/status/1593819064161665024"&gt;a long thread&lt;/a&gt; and &lt;a href="https://denise.dreamwidth.org/91757.html"&gt;accompanying blog post&lt;/a&gt; also outlining some of the legal ramifications of hosting your own Mastodon instance.&lt;/p&gt;
&lt;p&gt;So until you’re 100% sure you want to take on that commitment and liability, maybe start off by joining an instance hosted by a seasoned admin and donate to help them manage infra costs and things.&lt;/p&gt;
&lt;div&gt;&lt;h3&gt;A note about relays&lt;/h3&gt;&lt;p&gt;I mentioned relays a few times above, so what are they? Relays are servers running specific software that can help supply smaller instances with content from other Mastodon instances. Why would you want this? To help you discover content and accounts.&lt;/p&gt;&lt;p&gt;Remember how I said that Mastodon comes with three timelines you can follow: home (people you follow), local (accounts on your instance), and federated? The federated timeline is composed of posts from any accounts anyone on your instance follows, in addition to anything provided by relays that the instance is subscribed to.&lt;/p&gt;&lt;p&gt;Unfortunately, I’ve found it challenging to find good relays with communities and content I would want to feed into my own instance. Many instances I would want to add as a relay don’t run a relay. There’s little incentive for them to add it anyways: more work for their server and more costs associated with that.&lt;/p&gt;&lt;p&gt;At the same time, adding random relays from unknown communities isn’t a good idea either. The content could be questionable, and you’ll be hosting all that media yourself, which opens you up to more liability and more hosting costs.&lt;/p&gt;&lt;p&gt;That’s one benefit of not running a single-user instance just for yourself. It’ll be a bit more work for you to discover new content and accounts you enjoy as you won’t have much of a local or federated timeline at your disposal.&lt;/p&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;How to find people to follow?&lt;/h3&gt;
&lt;p&gt;You’ve found a Mastodon server you like, created an account, and now you want to find out who to follow or how to find people you already follow on Twitter. There are a few great tools to help with this.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://movetodon.org/"&gt;Move to don&lt;/a&gt;&lt;/strong&gt;: This is probably the easiest way to get started. Connect with your Twitter account, and it just shows you people you follow that are now on Mastodon, providing you an easy way to follow them all or individually. You’ll want to keep checking it over days/weeks/months as more people sign up for Mastodon. &lt;strong&gt;Update:&lt;/strong&gt; It seems Twitter has disabled Move to don&amp;#x27;s API access but &lt;a href="https://mastodon-flock.vercel.app/"&gt;Mastodon Flock&lt;/a&gt; seems like a good replacement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://twitodon.com/"&gt;Twitodon&lt;/a&gt;, &lt;a href="https://debirdify.pruvisto.org/"&gt;Debirdify&lt;/a&gt; and &lt;a href="https://fedifinder.glitch.me/"&gt;Fedifinder&lt;/a&gt;&lt;/strong&gt;: I mentioned some of these tools earlier for helping you find instances, but they also help you find people to follow from your existing network on Twitter. However, at the moment, they are a bit manual and rely on providing you with an exported CSV file of user accounts for you to upload and import into your Mastodon instance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://followgraph.vercel.app"&gt;Followgraph for Mastodon&lt;/a&gt;&lt;/strong&gt;: This is another great tool, but unlike the others doesn’t need you to connect your Twitter account. Instead, you just provide it with your Mastodon handle (or anyone else’s), and it recommends other accounts to follow based on who your followers follow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://fediverse.info/explore/people"&gt;Fediverse.info&lt;/a&gt;&lt;/strong&gt;: Another tool for helping you find people to follow. This one works by letting you browse by hashtags people have put in their bio.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Following Twitter accounts directly on Mastodon&lt;/strong&gt;
And finally, if you’re devoted to Mastodon and don’t feel like checking Twitter all that often anymore, you can use some &lt;a href="https://github.com/NicolasConstant/BirdsiteLive"&gt;software called BirdsiteLive&lt;/a&gt;—an "ethical ActivityPub bridge to Twitter"—to follow people that are only on Twitter. It needs to be hosted to use, but you can &lt;a href="https://fedidb.org/software/birdsitelive"&gt;find live instances of it on FediDB&lt;/a&gt;. Basically, you just type in a Twitter username, and it gives you a Mastodon username to follow. At this time, one example instance that’s online is &lt;a href="https://bird.makeup"&gt;Bird.makeup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cross-posting between Twitter and Mastodon&lt;/strong&gt;
If you’re interested in posting to both Mastodon and Twitter at the same time, you can use a tool called &lt;a href="https://moaparty.com/"&gt;Moaparty&lt;/a&gt; or &lt;a href="https://mikeindustries.com/blog/archive/2022/12/how-to-automatically-post-your-tweets-to-mastodon"&gt;do it yourself with IFTTT&lt;/a&gt;. However, as a word of caution, some Mastodon folks don’t appreciate this behavior and may even consider it spammy. I agree with that sentiment: treat Mastodon as its own thing.&lt;/p&gt;
&lt;h3&gt;Mastodon apps&lt;/h3&gt;
&lt;h3&gt;And how developers have truly embraced Mastodon&lt;/h3&gt;
&lt;img alt="Ivory Mastodon client" src="https://turbo.paulstamatiou.com/uploads/2022/12/stammy-on-mastodon-ivory-app.webp" /&gt;
&lt;small&gt;&lt;a href="https://tapbots.com/ivory/"&gt;Ivory&lt;/a&gt;, the Mastodon client for iOS and macOS, by &lt;a href="https://tapbots.com/"&gt;Tapbots&lt;/a&gt;&lt;/small&gt;
&lt;p&gt;Now for the fun part: exploring the world of Mastodon apps and clients.&lt;/p&gt;
&lt;p&gt;There are several ways to interact with Mastodon. You can, of course, use the standard Mastodon web interface for your server, as well as the official Mastodon iPhone and Android apps to log into your server.&lt;/p&gt;
&lt;p&gt;If you want a more advanced web experience, you can go to Preferences ’ Appearance and enable the "advanced web interface" to give you a multi-column layout reminiscent of TweetDeck (there&amp;#x27;s also &lt;a href="https://mastodeck.com/"&gt;Mastodeck&lt;/a&gt;). And if you would prefer a simpler web client, take a look at Pinafore.social. &lt;strong&gt;Update:&lt;/strong&gt; it looks like Pinafore is shutting down but the &lt;a href="https://elk.zone/"&gt;Elk web client looks amazing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But I’m not here to tell you about the official apps. I want to talk about the growing and thriving developer community that is fully embracing Mastodon and the potential of the decentralized protocol. To paint a picture of why this is such a big deal, you also have to understand how much of a hassle it has been over the last decade to build anything on top of the Twitter API. It was especially hard to see as a long-time employee.&lt;/p&gt;
&lt;p&gt;Long story short, Twitter made it hard. Hard to even get an API key and get your app approved. Hard to get bugs fixed or new features accessible via the API. Hard to access the data developers wanted. Hard to not hit arbitrary constraints or limits. Things were always changing, rugs were pulled, announcements about new priorities and new rugs were made, only for them to be pulled again (read &lt;a href="https://www.theverge.com/2021/11/15/22779149/twitter-api-version-2-official-decentralized"&gt;this&lt;/a&gt;, &lt;a href="https://techcrunch.com/2022/11/02/twitter-cancels-its-chirp-conference-for-developers-amid-management-transition/"&gt;this&lt;/a&gt; and &lt;a href="https://techcrunch.com/2022/12/15/developer-platforms-are-all-about-trust-and-twitter-lost-it/"&gt;this&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;No one knows this pain better than the team at Tapbots, makers of a popular third-party Twitter client called Tweetbot. I’ll be the first to say I did not like Tweetbot—not due to anything about the app itself; it was beautifully executed and well-built—I was just designing for the official Twitter apps. Tweetbot had nice details, but I never really used it much, though oddly enough, quite a few of the Twitter iOS engineers used it over the years, if that tells you anything.&lt;/p&gt;
&lt;p&gt;Needless to say, the Tapbots team had to deal with all the ups and downs that came with building on top of the Twitter API. Twitter wasn’t necessarily fond of third-party clients that they couldn’t effectively monetize or control.&lt;/p&gt;
&lt;p&gt;Mastodon brought the winds of change, an open web not controlled by any single entity. &lt;a href="https://tapbots.social/@mark/109524033562967560"&gt;Mark Jardine&lt;/a&gt; from Tapbots wrote a thread outlining his experience building on top of Twitter, contrasted with their new focus on Mastodon:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“As much as Twitter has brought us success, it has also been extremely frustrating for the past 6-7 years trying to build something great with a nerfed API.”[…]&lt;/p&gt;
&lt;p&gt;“Building an app for an open and decentralized social platform felt so refreshing. Inspirational! I haven’t been so excited designing something in a long time. With Tweetbot, we were always fighting with the API limitations while knowing in the back of our minds that someday the API could be taken away.”&lt;/p&gt;
&lt;p&gt;“I didn’t realize it then, but that killed a lot of our excitement and enthusiasm. With @ivory, we are just ecstatic every single day. It’s just been a pure joy to make software again.”&lt;/p&gt;
&lt;p&gt;—&lt;a href="https://tapbots.social/@mark/109524033562967560"&gt;@mark@tapbots.social&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tapbots is building a lovely Mastodon client for iOS and macOS called &lt;a href="https://tapbots.com/ivory/"&gt;Ivory&lt;/a&gt;. But they’re not the only ones ramping up development on a Mastodon client. There’s also Mammoth, an app in development by iOS developer &lt;a href="https://mastodon.social/@JPEGuin"&gt;Shihab Mehboob&lt;/a&gt; along with his team. I’ve also been testing Mammoth, and if you’re used to the official Twitter mobile apps, you’ll find Mammoth familiar.&lt;/p&gt;
&lt;p&gt;Then there’s Metatext, Toot!, tooot, Mastoot, Mast, Tusker, and Woolly, &lt;a href="https://github.com/Dimillian/IceCubesApp"&gt;IceCubesApp&lt;/a&gt;, among others. Some of these are in Testflight and are best found by following the developers on Mastodon. The official Mastodon site has a &lt;a href="https://joinmastodon.org/apps"&gt;list of public third-party apps&lt;/a&gt; as well.&lt;/p&gt;
&lt;h3&gt;Using Mastodon&lt;/h3&gt;
&lt;h3&gt;Toots, boosts, and bad search, oh my!&lt;/h3&gt;
&lt;p&gt;So you’ve made it. You have an account, you’ve followed some people, and you have installed a lovely Mastodon app. Time to get to know Mastodon a bit better. If you’re coming from Twitter, you’ll pick it up quickly, but there are a few quick differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tweets → Toots&lt;/strong&gt;: This was the Mastodon word for a post for a long-time, but a recent software update renamed the "Toot" button to simply "Publish." Some folks still refer to posts as toots.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ReTweets → Boosts&lt;/strong&gt;: Sharing a post on Mastodon is done by boosting, much like ReTweeting. If anything, boosting is encouraged and seemingly more common on&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;s&gt;Quote Tweets&lt;/s&gt;&lt;/strong&gt;: Mastodon has no equivalent of Quote Tweets. You can only boost a post. This was an &lt;a href="https://blog.joinmastodon.org/2018/07/cage-the-mastodon/#:~:text=Another%20feature%20that%20has%20been%20requested%20almost%20since%20the%20start%2C%20and%20which%20I%20keep%20rejecting%20is%20quoting%20messages"&gt;intentional design decision meant to curb abuse and harassment&lt;/a&gt;. However, with the &lt;a href="https://en.wikipedia.org/wiki/Eternal_September"&gt;Eternal September&lt;/a&gt; of Twitter users flooding Mastodon, it is a bit of a hot topic and has come up often recently. Here’s &lt;a href="https://macaw.social/@dml/109569707633229305"&gt;some more thoughts on the topic&lt;/a&gt; from an engineer I worked with at Twitter who knows the problem space well. But they eventually find their way on Mastodon; the Mastodon founder &lt;a href="https://mastodon.social/@Gargron/109623910323239777"&gt;recently expressed some interest&lt;/a&gt; in a version you can opt out of.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;280 character limit → 500 character limit&lt;/strong&gt;: Toots are 500 characters! Enjoy the extra space to share your thoughts without easily getting misinterpreted. You can also reply to your own toots to create a thread, though the composer doesn’t have the built-in functionality to create a thread that Twitter has (and that I designed while at Twitter). However, since it is open-source software on top of ActivityPub, anyone running their own instance &lt;a href="https://write.as/sweetmeat/customize-mastodon-to-change-your-post-character-limit"&gt;can modify this limit&lt;/a&gt; to be much larger.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;s&gt;Verification&lt;/s&gt;&lt;/strong&gt;: As mentioned earlier, Mastodon has no concept of account verification, though some users add &lt;code&gt;:verified:&lt;/code&gt; to their username and it gets rendered as a checkmark on their avatar in some Mastodon apps. Separately, links on your profile can get verified if you add a &lt;code&gt;rel="me"&lt;/code&gt; link on your site.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;s&gt;Direct Messages&lt;/s&gt;&lt;/strong&gt;: Mastodon doesn’t really have a concept of direct messages at this time. While you may see a DM tab in some Mastodon clients, they are really just toots with the privacy setting set to "only those mentioned." But the account you mention can mention someone else in their reply, and they will get added to the DM. It’s important to note that they are also not encrypted, so your Mastodon instance admin (along with the admin of any instance you&amp;#x27;re sending to) would be able to see it in the database if they really cared to look it up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lists&lt;/strong&gt;: While both Twitter and Mastodon have the ability to create and follow custom lists of accounts, they work a bit differently on Mastodon. You can only add people to a list that you’re already following. On Twitter, you could add anyone to a list even if you didn’t follow them, which made it great for creating separate timelines you could hop between as you wished. However, I did see some chatter about Mastodon considering this, under the moniker &lt;a href="https://github.com/mastodon/mastodon/issues/16556"&gt;exclusive lists&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are also a few other features like following hashtags (though it has some limitations) and the ability to put a post behind a Content Warning (the "CW" icon in the Mastodon post composer). Aside from that, you get the expected functionality of being able to attach video, images, and polls in addition to being able to adjust post privacy settings.&lt;/p&gt;
&lt;p&gt;There’s one more thing, and this one may take some getting used to. Full-text search of the contents of Mastodon posts isn’t really a thing. You can search for accounts, and you can search for hashtags. Searching for accounts works pretty well, especially when including the full &lt;code&gt;@username@example.com&lt;/code&gt; format. Searching for hashtags is a bit more limited and will only surface results that your instance knows about from local posts and federated posts. But your server doesn’t instantly know about all posts on the fediverse, so you may not see all the results you expect. You can get some very limited searching for your own posts, mentions, favorites, and bookmarks if your server admin setup &lt;a href="https://docs.joinmastodon.org/admin/optional/elasticsearch/"&gt;ElasticSearch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Search could get better if someone made their own service dedicated to indexing, relaying, and searching the fediverse, but that is no small technical (and expensive) feat.&lt;/p&gt;
&lt;p&gt;Isn’t it pretty bad that there’s no good search? Well, along with the lack of Quote Boosts, this is framed as a purposeful design decision (though it also feels like a technical challenge):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Mastodon deliberately does not support arbitrary search. If someone wants their message to be discovered, they can use a hashtag, which can be browsed. What does arbitrary search accomplish? People and brands search for their own name to self-insert into conversations they were not invited to."&lt;/p&gt;
&lt;p&gt;—&lt;a href="https://blog.joinmastodon.org/2018/07/cage-the-mastodon/"&gt;Mastodon&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;My thoughts on Mastodon&lt;/h3&gt;
&lt;h3&gt;Vast potential mixed with non-trivial constraints&lt;/h3&gt;
&lt;p&gt;Mastodon brings a lot to the table with its federated model of sharing utilizing an open protocol. It’s easy for anyone to host their own instance and own their data while participating in a large network with many communities. It’s not owned by anyone, nor can it be. It’s an open-source project run by a non-profit. Anyone can fork the code and do what they wish.&lt;/p&gt;
&lt;p&gt;But, it’s not easy for anyone to get started. There’s no question about it, you cannot come from a centralized and well-capitalized product like Twitter and expect the same of a decentralized, open-source project. Mastodon is very rough around the edges when you approach it from a mindset of the former:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hard to sign up. Hard to find communities&lt;/strong&gt;: Okay, so I just installed the app... I can’t just create an account? I have to pick a server? Even with the official Mastodon app providing a list of instances to choose from, it’s still confusing. How can I see an example of the content and people on each server to know I’m choosing the right one? What are the implications of choosing a server? Is it publicly facing? Can I change it later? Is it easy to change? Et cetera.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hard to find people&lt;/strong&gt;: Once you have an account, you’re still in for more uphill battles. How can you even find good, relevant, and interesting accounts to follow? You have to rely on word of mouth or third-party tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hard to find content&lt;/strong&gt;: It’s hard to continually discover the type of content you enjoy. There’s no algorithmic timeline to inject new content into your home timeline. You have to rely on... hashtags, people you follow boosting content from others, and remember to continually check the federated and local timelines of your Mastodon instance. Using hashtags started to feel so thirsty even years ago, but now it seems like a necessity?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Federation brings power. It brings constraints.&lt;/p&gt;
&lt;h3&gt;Challenge 1: Following accounts and engaging with posts not on your server&lt;/h3&gt;
&lt;p&gt;One early challenge you’ll run into with federation is simply around following people as well as engaging with posts you find on other instances. Let’s say I find a link to a Mastodon post externally (as in not something I come across while logged into my own Mastodon server). It could be a link on someone’s blog, from a chat app, and so on. By clicking that link, I am going to the post hosted on Mastodon instance that is not my instance. Even though I may be logged into my own Mastodon instance, this other instance doesn’t know or care about that. I’m just a visitor and have no account with that instance. I can’t favorite the post, reply, or even follow the author.&lt;/p&gt;
&lt;p&gt;Instead, the onus is on me to find a way to that same profile or post, but within my Mastodon instance where I’m logged in and can carry out any actions I want to take. &lt;strong&gt;This is super annoying&lt;/strong&gt; and happens all the time. For example, I can’t link you to my Mastodon profile &lt;a href="https://stammy.design/@stammy"&gt;@stammy@stammy.design&lt;/a&gt; and have you tap follow. That would only work if you already had an account on stammy.design. So you have to copy the username, then go to your Mastodon website or app and search for my account.&lt;/p&gt;
&lt;p&gt;What’s the solution? Nothing great. A partial solution for desktop is to use a &lt;a href="https://github.com/bramus/mastodon-profile-redirect"&gt;Chrome extension that redirects profiles&lt;/a&gt; to your own instance, but it doesn’t work for posts (you can&amp;#x27;t just reformat the URL as the ID of the post isn’t the same across instances). And I’ve seen some chatter about an &lt;a href="https://github.com/mastodon/mastodon/issues/19954"&gt;intents/handler-style way to deal with this&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Challenge 2: Sometimes you just don’t see things&lt;/h3&gt;
&lt;p&gt;A recurring theme with federation and Mastodon is around how your instance only knows about things it knows about. It doesn’t automatically know about every post across every Mastodon server online. This can lead to some unexpected behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Profiles you visit may be empty at first.&lt;/strong&gt; They won’t have any posts if your server has never encountered this account before (and no one else on your server has followed or interacted with the account). Following the account will start to get new posts from that account, but don’t expect it to instantly fill it up with every historical post from that account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Not all public replies to a post may be visible.&lt;/strong&gt; This one is the most concerning. Your server may not know about all the replies to a post, and therefore you may only be seeing a slice of the conversation on a post. Simon Willison has a great dive into the issue &lt;a href="https://fedi.simonwillison.net/@simon/109559268498004036"&gt;in this thread&lt;/a&gt; on his personal instance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You may not be able to see a complete list of someone&amp;#x27;s followers&lt;/strong&gt;. Similar to the above, your server likely has a limited view into who follows that account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Boost and favorite counts may not be accurate.&lt;/strong&gt; Same story here; your server only knows what it knows about. If you want to get a real understanding of a post&amp;#x27;s true engagement, your Mastodon client may have an action in a menu for something like "open original post."&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, how can you get your Mastodon server to know more? If you’re on a larger instance or tend to mainly communicate with people on the same server, you’re less likely to run into these issues. There’s not much of a fix aside from the admin adding a ton of relays to ingest as many posts from the fediverse as it can, but that’s not ideal for various reasons.&lt;/p&gt;
&lt;h3&gt;Challenge 3: Moderation and incentives&lt;/h3&gt;
&lt;p&gt;And one last challenge I see is typically listed as a good thing about Mastodon: moderation. I’m annoyed with Twitter because it seems like they are continually changing the rules, banning people arbitrarily, and I didn’t want my identity held hostage. Well, the exact same thing can happen on Mastodon and more.&lt;/p&gt;
&lt;p&gt;How do you moderate a federated network? You leave it up to the instances to moderate themselves. You just have to trust the admins of each instance to do the right thing, and the idea is that by picking your instance, you’re picking a community you embrace and feel part of, and everyone will follow the established rules. And if you’re doing all that, you shouldn’t have an issue, right?&lt;/p&gt;
&lt;p&gt;This quote from Noah Smith in his article &lt;a href="https://noahpinion.substack.com/p/the-internet-wants-to-be-fragmented"&gt;The internet wants to be fragmented&lt;/a&gt; sums up nicely why moderation is great:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"&lt;em&gt;Community moderation works.&lt;/em&gt; This was the overwhelming lesson of the early internet. It works because it mirrors the social interaction of real life, where social groups exclude people who don’t fit in. And it works because it distributes the task of policing the internet to a vast number of volunteers, who provide the free labor of keeping forums fun, because to them maintaining a community is a labor of love. And it works because if you don’t like the forum you’re in — if the mods are being too harsh, or if they’re being too lenient and the community has been taken over by trolls — you just walk away and find another forum."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That seems fine, and if you don’t like it, you can find another instance or host your own. But what if you couldn’t move your account? If you don’t get along with your instance admin, it seems &lt;a href="https://twitter.com/alexqgb/status/1594079081649184769"&gt;they can even block your move to another instance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Okay but that’s like the worst-case scenario, right? Yea, that’s true. But there’s one more unpleasant situation. Running a Mastodon instance is a non-trivial amount of work. There’s technical setup and regular maintenance to keep on top of, there are real costs associated with hosting, and there’s legal risk as well.&lt;/p&gt;
&lt;p&gt;Since there are no real incentives to host and moderate a Mastodon instance, you could end up on a poorly run instance. If you’re not on a well-run instance, best case scenario, sometimes your server is a little slow or isn’t always running the latest software, but it is generally okay. The worst case scenario is that, uh, your server goes down permanently, along with your Mastodon identity and all your data.&lt;/p&gt;
&lt;p&gt;All this to say, choosing your Mastodon server is an especially important task. One thing that would make this better is if Mastodon had a more streamlined way to be able to donate to Mastodon admins to pay for server costs. I imagine seeing some Stripe link in settings and being able to set up a small recurring donation.&lt;/p&gt;
&lt;h3&gt;Here’s why I like Mastodon&lt;/h3&gt;
&lt;p&gt;While those challenges complicate things, they don’t overshadow the brilliance of Mastodon and the protocol for me. One of those reasons is that it feels more intimate, just a small place with the people I follow. No one or thing unexpectedly forcibly getting injected into my home timeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On Twitter, you have three groups of accounts: celebrities and other verified accounts, brands, and then just regular people. On Mastodon, you only have people&lt;/strong&gt; (and with so many instances to choose from, no one can really squat on your desired username). No ads either. The Mastodon home timeline is always in reverse chronological order—not algorithmic or ranked like Twitter’s timeline is by default. This means your timeline is saner. It’s not filled with threads spewing the same nonsense that gets injected into your timeline by the algorithm. It’s not filled with ads.&lt;/p&gt;
&lt;p&gt;And with the longer character limit, you can more easily converse with people or share your thoughts without losing much in translation or spending lots of time trying to reword things to fit logically in one or multiple posts.&lt;/p&gt;
&lt;p&gt;Mastodon feels like the early days of Twitter, with people just sharing thoughts, not worrying about how to optimize for engagement. It feels more intimate, authentic, and close-knit. My only advice for those starting out on Mastodon is to boost posts you think others should see to help with discovery, and also, you can favorite your heart out knowing it doesn’t do unexpected things like injecting the post into others’ home timelines because there’s no algorithmic timeline.&lt;/p&gt;
&lt;p&gt;As I mentioned earlier, Mastodon’s open-source software, open protocol, and strong API without limitation (as compared to Twitter) make it a dream for developers. I’ve been seeing this flurry of excitement from developers building Mastodon apps. I’m excited to see what else gets built on top of, or for, Mastodon and ActivityPub.&lt;/p&gt;
&lt;p&gt;The challenge will then be how the Mastodon project and broader community deal with the huge influx of new users. Will they develop efficient product development processes to deal with the flood of feature requests, complaints, and associated chatter in a principled manner? Will there be enough people to push for great design?&lt;/p&gt;
&lt;p&gt;So far, everything on Mastodon feels like a page out of early Twitter. Nothing bad about that; it’s easy for people to join and generally understand how things work. But shouldn’t it evolve to best embrace the unique affordances and constraints of Mastodon? What will it mean for future functionality to look, feel and act like the decentralized network it is? People designing for the official Mastodon project need to think less about Twitter and more about what it means to be Mastodon. Perhaps third-party Mastodon clients and forks can lead the way before they get adopted.&lt;/p&gt;
&lt;h3&gt;An open web, owned by its users&lt;/h3&gt;
&lt;h3&gt;Yes, another "bring back blogging and RSS"&lt;/h3&gt;
&lt;p&gt;This is the part where I talk about my growing desire to have more control over, and own, my data and my online identity. I want to move away from relying too much on just a few large centralized tech companies when they control too much of my data. I think about worst-case scenarios. What happens if I get locked out of this account for a day, a week, for good? It’s with that mindset that I need more control, access to the raw data, and more.&lt;/p&gt;
&lt;p&gt;That way of thinking is often a bit overkill and can lead you down a complex path (have you ever tried hosting your own email??), but 2023 is the year I begin to detach myself from large tech companies like Twitter and Google. And for the services I do rely on, I expect encryption. And it was nice to see Apple roll out iCloud Advanced Data Protection recently. I love Signal and ProtonMail. It gives me comfort knowing that even if my account was compromised, the attacker would also have to know a separate encryption key to get into my mailbox.&lt;/p&gt;
&lt;p&gt;I’ve been running paulstamatiou.com for 17 years. First hosted from a Mac mini in my dorm room at Georgia Tech. Starting this site and continuing to maintain it has been one of the best things I’ve done. It gives me a space to tinker, write, share thoughts, show work, and publish photos.&lt;/p&gt;
&lt;p&gt;I miss the days when if you wanted to have a space online, you had to make it yourself. Personal blogs were their own communities. The comments sections were helpful and thriving. They were hard to discover, but we got by. We had feed readers, blog rolls, Technorati, and trackbacks.&lt;/p&gt;
&lt;p&gt;Every day, I would be excited to open my feed reader and see if anyone whose feed I subscribe to had posted anything new. Seeing that unread count badge on a favorite blog was like a little present.&lt;/p&gt;
&lt;p&gt;In those days, you followed people. Sure there were a few big publications you might keep track of, but you didn’t really follow "developer blogs" or "design blogs." It was always just people’s personal blogs publishing whatever felt interesting to them. They had their own take and personality.&lt;/p&gt;
&lt;p&gt;We’ve seemed to have lost a bit of that personality and authenticity. We’re living in a world where it often feels like everything is carefully curated and polished just so for engagement. This was well before we became far too accustomed to busy feeds, videos, threads, gamification, listicles... the constant need to be entertained and get a serotonin hit. Personal blogs offered a glimpse into the real lives and thoughts of their creators.&lt;/p&gt;
&lt;p&gt;In recent years, we made way for the rise of personal newsletters. It was only after my inbox had become unmanageable from Substack and other newsletters that I started &lt;a href="https://paulstamatiou.com/hello-rss/"&gt;using an RSS reader again&lt;/a&gt;. A modern one that could also manage newsletters and declutter my inbox. But the type of content I long for is not there. (By the way: you can just add &lt;code&gt;.rss&lt;/code&gt; to any Mastodon profile or search URL and get an RSS feed).&lt;/p&gt;
&lt;p&gt;I’m hoping that the recent interest in Mastodon will get people to start thinking more about taking control of their data and online identity. Mastodon has a great shot at recreating that feeling of community that personal blogs used to have. It can be your own space.&lt;/p&gt;
&lt;p&gt;I only ask that you don’t think of it as a Twitter replacement but as a new thing. A new way to communicate, connect and build communities. A decentralized product like Mastodon will have some learning curves, no doubt. But it’s a start. And it’s a start that’s not owned by a billionaire.&lt;/p&gt;
&lt;p&gt;If you’ve read this far, thank you! You can follow me on Mastodon by copying my full username and pasting it into the search of your Mastodon website or app to find my profile and hit follow: &lt;a href="https://stammy.design/@stammy"&gt;[object Object]&lt;/a&gt;&lt;/p&gt;</description><author>Paul Stamatiou</author><pubDate>Thu, 05 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://paulstamatiou.com/mastodon</guid></item><item><title>General book recommendations</title><link>http://notes.eatonphil.com/2023-01-04-book-recommendations.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://lists.eatonphil.com/book-recommendations.html"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 04 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-01-04-book-recommendations.html</guid></item><item><title>December Gears emulator update</title><link>https://anisse.astier.eu/gears-update-2023-01.html</link><description>&lt;p&gt;I &lt;a href="talks-emulation.html"&gt;wrote here about how I'm writing an emulator&lt;/a&gt;. How has it progressed ?&lt;/p&gt;
&lt;h1&gt;Fixing a rendering bug with backgrounds&lt;/h1&gt;
&lt;p&gt;In November I &lt;a href="https://octodon.social/@aissen/109292436894663293"&gt;wrote on mastodon&lt;/a&gt; how I was tracking down a rendering bug.&lt;/p&gt;
&lt;p&gt;To track down this issue with weird data on screen, there was already too many messages, so …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Wed, 04 Jan 2023 01:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/gears-update-2023-01.html</guid></item><item><title>The 3D Printing Learning Curveball</title><link>https://miscdotgeek.com/the-3d-printing-learning-curveball/</link><description>&lt;p&gt;For a long time, I held off getting a 3D printer for several reasons. The first is that I really felt like I just wasn&amp;#8217;t ready for Yet Another Hobby. My life wasn&amp;#8217;t together, and my finances weren&amp;#8217;t either. I know that 3D printing can be expensive, and I just didn&amp;#8217;t have the discretionary income &amp;#8230; &lt;/p&gt;
&lt;p&gt;&lt;a class="more-link btn" href="https://miscdotgeek.com/the-3d-printing-learning-curveball/" rel="noopener noreferrer" target="_self"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://miscdotgeek.com/the-3d-printing-learning-curveball/" rel="noopener noreferrer" target="_self"&gt;The 3D Printing Learning Curveball&lt;/a&gt; appeared first on &lt;a href="https://miscdotgeek.com" rel="noopener noreferrer" target="_self"&gt;MiscDotGeek&lt;/a&gt;.&lt;/p&gt;</description><author>MiscDotGeek</author><pubDate>Tue, 03 Jan 2023 08:44:09 GMT</pubDate><guid isPermaLink="true">https://miscdotgeek.com/the-3d-printing-learning-curveball/</guid></item><item><title>Faster Index Joins</title><link>https://www.marginalia.nu/log/70-faster-index-joins/</link><description>The most common (and most costly) operation of the marginalia search engine&amp;rsquo;s index is something like given a set of documents containing one keyword, find each documents containing another keyword.
The naive approach is to just iterate over each document identifier in the first set and do a membership test in the b-tree containing the second. This is an O(m log n)-operation, which on paper is pretty fast.
It turns out it can be made faster.</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 03 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/70-faster-index-joins/</guid></item><item><title>Organizing Developer Writing Retreats</title><link>https://www.swyx.io/dwr-review</link><description>&lt;p&gt;One of my biggest and scariest projects of 2022 was co-organizing the first-ever &lt;strong&gt;DEV | WRITERS | RETREAT&lt;/strong&gt; in Miami!&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 02 Jan 2023 03:30:15 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/dwr-review</guid></item><item><title>Go Defer Tricks</title><link>https://cmdev.com/blog/go-defer-tricks/</link><description>Effective use of the defer statement in the Go programming language.</description><author>The Cranky Developer on Crater Moon Development</author><pubDate>Sun, 01 Jan 2023 10:00:00 GMT</pubDate><guid isPermaLink="true">https://cmdev.com/blog/go-defer-tricks/</guid></item><item><title>My Fave New Podcasts of 2022</title><link>https://www.swyx.io/fave-podcasts-2022</link><description>&lt;p&gt;As someone who does a lot of my learning via podcasts, I've been putting up picks lists for 3 years straight (see main &lt;a href="https://www.swyx.io/fave-podcasts"&gt;2019 list&lt;/a&gt;, then my &lt;a href="https://www.swyx.io/fave-podcasts-2020"&gt;2020&lt;/a&gt; and &lt;a href="https://www.swyx.io/fave-podcasts-2021"&gt;2021&lt;/a&gt; diffs), so it's time to do year 4 (!)&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Sun, 01 Jan 2023 05:05:09 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/fave-podcasts-2022</guid></item><item><title>In response to a frontend developer asking about database development</title><link>http://notes.eatonphil.com/2023-01-01-letter-to-a-frontend-developer-asking-about-database-development.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://letters.eatonphil.com/2023-01-01-letter-to-a-frontend-developer-asking-about-database-development.html"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sun, 01 Jan 2023 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2023-01-01-letter-to-a-frontend-developer-asking-about-database-development.html</guid></item></channel></rss>