<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Home</title>
    <link>https://mikebelanger.ca/</link>
    <description>Site and blog of Mike Belanger</description>
    <atom:link href="https://mikebelanger.ca/rss" rel="self" type="application/rss+xml" />
    <item>
      <title>Quadlets: A Developer&apos;s Perspective</title>
      <link>https://mikebelanger.ca/blog/ini_quadlets_dev/</link>
      <guid>https://mikebelanger.ca/blog/ini_quadlets_dev/</guid>
      <description>A Quick Overview of Using Podman Quadlets For Web Development</description>
      <content:encoded><![CDATA[<h5 id="introduction">Introduction <a class="anchor" href="#introduction" aria-hidden="true">🔗</a></h5>
<p><a href="https://podman.io/">Podman's</a> <a href="https://docs.podman.io/en/stable/markdown/podman-quadlet.1.html">Quadlet feature</a> allows podman containers to be integrated into <a href="https://systemd.io/">systemd</a> services.</p>
<p>Why manage containers with systemd? You might argue that containers are better orchestrated with <a href="https://docs.docker.com/engine/swarm/">Docker Swarm</a>, <a href="https://kubernetes.io/">Kubernetes</a>, or even <a href="https://www.ansible.com/">Ansible</a>.  That is true for larger installations. For a single Linux box or VPS, systemd is probably running on it already. I couldn't summarize it better than Elias Bourgess: <a href="https://ebourgess.dev/posts/podman-quadlet-production/#the-control-plane-you-already-run">it's the control pane you already run</a>.</p>
<p>There are two ways to write Quadlets:</p>
<ul>
<li><a href="https://www.redhat.com/en/blog/quadlet-podman.">INI-style quadlet definitions.</a> An extension of systemd's INI-style config language. This adds sections for things <a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html">like containers and volumes</a>. This approach is for Linux system administrators who are already familiar with systemd.</li>
<li><a href="https://www.redhat.com/en/blog/compose-podman-pods">K8s-style quadlet definitions.</a> These are Kubernetes-style yaml files that define your containers. This approach is clearly aimed at developers and devops folks already familiar with <a href="https://kubernetes.io/">Kubernetes</a> or <a href="https://docs.docker.com/compose/">docker-compose</a>.</li>
</ul>
<details>
    <summary role="button" class="secondary outline">Caveats</summary>
    <ul>
<li>
<p>You may be wondering why <a href="https://github.com/containers/podman-compose">podman-compose</a> isn't mentioned as an option.  While podman-compose is obviously a docker-compose replacement, industry leaders are shying away from supporting it, in favor of <code>podman kube play</code>. More on that <a href="https://www.redhat.com/en/blog/podman-compose-docker-compose">here.</a></p>
</li>
<li>
<p>In order to get <em>k8</em>-style Kubernetes yaml, you need a <a href="https://docs.podman.io/en/latest/markdown/podman-kube.unit.5.html"><em>kube file</em></a>, which is technically an INI-style file. I'm glossing over that for now, as the bulk of the container definitions are still either expressed in Kubernetes-style YAML or INI.</p>
</li>
</ul>

</details>
<p>I'm a developer who's used to docker-compose, so I <a href="https://github.com/mikebelanger/lucky-vanilla/blob/main/containers/prod/pod.yml">tried out the k8-style first</a>. That said, curiosity got the better of me, and I wondered what INI-style Quadlet definitions would look like.</p>
<h5 id="setup">Setup <a class="anchor" href="#setup" aria-hidden="true">🔗</a></h5>
<hr />
<p>Here's a minimal setup for a Node.js application (full source code <a href="https://github.com/mikebelanger/ini_style_quadlet">can be found here</a>):</p>
<pre data-copy="true"><code class="language-sh hljs">├── app
│   ├── index.js
│   ├── node_modules
│   ├── package.json
│   └── package-lock.json
├── dev
│   ├── example-app-dev.container
│   └── example-app-dev.pod
├── prod
│   ├── Containerfile
│   ├── example-app.build
│   ├── example-app.container
│   └── example-app.pod
└── README.md
</code></pre>
<p>The quadlet-specific files here are under the <code>dev</code> and <code>prod</code> subdirectories for development and production, respectively. Here's a look at the ones in <code>dev</code>:</p>
<p><code>dev/example-app-dev.container</code>:</p>
<pre data-copy="true"><code class="language-ini hljs"><span class="hljs-keyword">[Unit]</span>
<span class="hljs-attr">Description</span><span class="hljs-operator">=</span><span class="hljs-string">Example Node.js app</span>

<span class="hljs-keyword">[Container]</span>
<span class="hljs-attr">Pod</span><span class="hljs-operator">=</span><span class="hljs-string">example-app-dev.pod</span>
<span class="hljs-attr">Image</span><span class="hljs-operator">=</span><span class="hljs-string">docker.io/node:24</span>
<span class="hljs-attr">WorkingDir</span><span class="hljs-operator">=</span><span class="hljs-string">/app</span>
<span class="hljs-attr">Exec</span><span class="hljs-operator">=</span><span class="hljs-string">/bin/sh -c &quot;npm i &amp;&amp; node --watch index.js&quot;</span>
<span class="hljs-attr">Volume</span><span class="hljs-operator">=</span><span class="hljs-string">../app:/app:Z</span>
<span class="hljs-attr">Environment</span><span class="hljs-operator">=</span><span class="hljs-string">PORT=3001</span>

<span class="hljs-keyword">[Install]</span>
<span class="hljs-attr">WantedBy</span><span class="hljs-operator">=</span><span class="hljs-string">multi-user.target</span>
</code></pre>
<p><code>dev/example-app-dev.pod</code>:</p>
<pre data-copy="true"><code class="language-ini hljs"><span class="hljs-keyword">[Unit]</span>
<span class="hljs-attr">Description</span><span class="hljs-operator">=</span><span class="hljs-string">Development version of example app</span>

<span class="hljs-keyword">[Pod]</span>
<span class="hljs-attr">PodName</span><span class="hljs-operator">=</span><span class="hljs-string">example-app-dev</span>
<span class="hljs-attr">ExitPolicy</span><span class="hljs-operator">=</span><span class="hljs-string">stop</span>
<span class="hljs-attr">PublishPort</span><span class="hljs-operator">=</span><span class="hljs-string">3001:3001</span>

<span class="hljs-keyword">[Install]</span>
<span class="hljs-attr">WantedBy</span><span class="hljs-operator">=</span><span class="hljs-string">default.target</span>
</code></pre>
<details>
    <summary role="button" class="secondary outline">Why wrap one container in a Pod?</summary>
    <p>You may be wondering why it's wrapped in a Pod. For this example, the Pod is indeed overkill. Once other containers get added to your app (a Postgres db container, a Redis cache layer, reverse-proxy, etc) pods do make it easier to manage containers, and keep them all talking to each other with relative ease. This is because containers on the same pod can all access each other via <code>localhost:PORT</code>, and not worry about whatever host addresses are already in use. Furthermore, at the pod level, only the port that needs access from the outside world (say, a reverse proxy's container port) is published, while the other containers' ports are effectively closed off.</p>

</details>
<details>
    <summary role="button" class="secondary outline">Notes</summary>
    <ol>
<li>
<h6>These aren't the actual files systemd runs</h6>
</li>
</ol>
<p>These files get translated (the technical term is &quot;lowered&quot;) into other, <em>actual</em> systemd files for systemd to bootstrap. I like to think of the relationship quadlet files to systemd files like the relationship of SASS files to CSS, or React's JSX files to JS. While the syntax of quadlets is similar to what it gets compiled into, it isn't identical. To anyone less familiar with &quot;pure&quot; systemd, it's difficult to distinguish between quadlet and systemd-specific declarations.</p>
<ol start="2">
<li>
<h6>Systemd needs application code in certain subdirectories in order to run it</h6>
</li>
</ol>
<p>I'm used to cloning repos onto any path on my disk, doing <code>docker-compose up</code> in that repo, and I'm up and running. Unfortunately, the Quadlet workflow isn't quite as straightforward.  Systemd ships with most OSs with a predefined set of paths that it looks to discover new services. Quadlets continue that trend.</p>
<p>Fortunately, your application code <em>can</em> live in any directory you'd like, provided that directory gets copied/symlinked over into one of the quadlet discovery paths. A quick <code>ln -sfn &quot;$PWD&quot; ~/.config/containers/systemd/example-app</code> (super intuitive, I know) should do the trick.</p>
<ol start="3">
<li>
<h6>Mind the volume mount</h6>
</li>
</ol>
<p><code>Volume=../app:/app:Z</code> mounts your source directory read/write, and the <code>:Z</code> suffix relabels that directory for this one container's private use. That's the right choice for a single container. But if a <em>second</em> container in a different pod ever mounts the same directory, it gets its own SELinux label and one of the two will fail with a &quot;permission denied&quot;. If you genuinely intend to share a directory, use <code>:z</code> (shared) instead.</p>
<p>One more thing worth knowing: the above assumes <em>rootless</em> podman (<code>systemctl --user</code>), where the container's <code>root</code> maps to your own user, so anything it writes to <code>../app</code> stays yours to edit and delete. Run the same directory through <code>sudo podman</code> even once, though, and the files it creates become owned by real root, leaving you reaching for <code>sudo</code> to clean them up.</p>

</details>
<h4 id="dev-workflow">Dev Workflow: <a class="anchor" href="#dev-workflow" aria-hidden="true">🔗</a></h4>
<ol>
<li>Clone the repo.</li>
<li>Find your systemd discovery path. On most systems, that resolves to <code>${XDG_CONFIG_HOME:-$HOME/.config}/containers/systemd</code>. So something like <code>~/.config/containers/systemd</code>. (If you're doing rootless, which I am)</li>
<li><code>cd</code> into your repo, and link up the repo's subdirs into your quadlet discovery path:</li>
</ol>
<pre data-copy="true"><code class="language-sh hljs"><span class="hljs-built_in">cd</span> project-dir

<span class="hljs-comment"># symlink the entire project directory over. Note that &quot;example-app&quot; can be anything, and should be uniquely named</span>
<span class="hljs-comment"># to prevent clashes with other systemd services</span>
ln -sfn <span class="hljs-string">&quot;</span><span class="hljs-variable">$PWD</span><span class="hljs-string">&quot;</span> ~/.config/containers/systemd/example-app
</code></pre>
<p>Now reload systemd with this new application, and confirm all is well</p>
<pre data-copy="true"><code class="language-sh hljs"><span class="hljs-comment"># This rebuilds the quadlets -&gt; actual systemd runtime files</span>
systemctl --user daemon-reload

<span class="hljs-comment"># Confirm that quadlet picked it up with `podman quadlet list`. If all went well, you&#39;ll see your pod listed.</span>
podman quadlet list

<span class="hljs-comment"># Will probably give you something like</span>
<span class="hljs-comment"># NAME                       UNIT NAME                    PATH ON DISK                                       STATUS          APPLICATION</span>
<span class="hljs-comment"># example-app-dev.container  example-app-dev.service      /your/dev-dir/dev/example-app-dev.container        active/running  .dev.app</span>
<span class="hljs-comment"># example-app-dev.pod        example-app-dev-pod.service  /your/dev-dir/dev/example-app-dev.pod              active/running  .dev.app</span>

<span class="hljs-comment"># Check the status of the service</span>
systemctl --user status example-app-dev.service

<span class="hljs-comment"># BONUS: If you&#39;d prefer your services to keep running after you log out (and start at boot), enable lingering for your user</span>
sudo loginctl enable-linger <span class="hljs-variable">$USER</span>
</code></pre>
<h3 id="now-to-the-actual-developing">Now to the actual developing! <a class="anchor" href="#now-to-the-actual-developing" aria-hidden="true">🔗</a></h3>
<p>The main source files under <code>app</code> are volume-mounted, so go ahead, start changing <code>index.js</code>, for example. Reload the browser to see your changes.</p>
<h4 id="deployment-workflow">Deployment Workflow <a class="anchor" href="#deployment-workflow" aria-hidden="true">🔗</a></h4>
<p>If you're deploying to the same system you develop on, then all you're really doing is <code>COPY</code>ing the node source files instead of volume-mounting. If you're using Typescript or anything else requiring compilation, you'll have to add that to your <code>Containerfile</code> too.</p>
<p><code>prod/example-app.container</code>:</p>
<pre data-copy="true"><code class="language-ini hljs"><span class="hljs-keyword">[Unit]</span>
<span class="hljs-attr">Description</span><span class="hljs-operator">=</span><span class="hljs-string">Example Node.js app</span>

<span class="hljs-keyword">[Container]</span>
<span class="hljs-attr">Pod</span><span class="hljs-operator">=</span><span class="hljs-string">example-app.pod</span>
<span class="hljs-attr">Image</span><span class="hljs-operator">=</span><span class="hljs-string">example-app.build</span>
<span class="hljs-attr">Exec</span><span class="hljs-operator">=</span><span class="hljs-string">/bin/sh -c &quot;node index.js&quot;</span>
<span class="hljs-attr">Environment</span><span class="hljs-operator">=</span><span class="hljs-string">PORT=3005</span>

<span class="hljs-keyword">[Install]</span>
<span class="hljs-attr">WantedBy</span><span class="hljs-operator">=</span><span class="hljs-string">default.target</span>
</code></pre>
<p><code>prod/example-app.pod</code>:</p>
<pre data-copy="true"><code class="language-ini hljs"><span class="hljs-keyword">[Unit]</span>
<span class="hljs-attr">Description</span><span class="hljs-operator">=</span><span class="hljs-string">Production version of example app</span>

<span class="hljs-keyword">[Pod]</span>
<span class="hljs-attr">PodName</span><span class="hljs-operator">=</span><span class="hljs-string">example-app-pod</span>
<span class="hljs-attr">ExitPolicy</span><span class="hljs-operator">=</span><span class="hljs-string">stop</span>
<span class="hljs-attr">PublishPort</span><span class="hljs-operator">=</span><span class="hljs-string">3005:3005</span>

<span class="hljs-keyword">[Install]</span>
<span class="hljs-attr">WantedBy</span><span class="hljs-operator">=</span><span class="hljs-string">default.target</span>
</code></pre>
<p><code>prod/Containerfile</code>:</p>
<pre data-copy="true"><code class="language-Dockerfile hljs"><span class="hljs-keyword">FROM</span> <span class="hljs-string">docker.io/library/node:24-alpine</span>

<span class="hljs-keyword">WORKDIR</span> <span class="hljs-string">/app</span>

<span class="hljs-keyword">COPY</span> app/ /app/

<span class="hljs-keyword">RUN</span> npm ci
</code></pre>
<p><code>prod/example-app.build</code>:</p>
<pre data-copy="true"><code class="language-ini hljs"><span class="hljs-keyword">[Build]</span>
<span class="hljs-attr">ImageTag</span><span class="hljs-operator">=</span><span class="hljs-string">localhost/example-app</span>
<span class="hljs-attr">File</span><span class="hljs-operator">=</span><span class="hljs-string">Containerfile</span>
<span class="hljs-attr">SetWorkingDirectory</span><span class="hljs-operator">=</span><span class="hljs-string">..</span>
</code></pre>
<p>You've already linked the production files over, its just a matter of rebuilding.</p>
<pre data-copy="true"><code class="language-sh hljs"><span class="hljs-comment"># Rebuilds any necessary files</span>
systemctl --user daemon-reload

<span class="hljs-comment"># Start the production service</span>
systemctl --user restart example-app.service
</code></pre>
<p>Point a browser at <code>localhost:3005</code> and you should see your production app!</p>
<details>
    <summary role="button" class="secondary outline">What about Quadlet Install?</summary>
    <p>The short answer is, eventually I think we should. I'm only on <em>Podman 5.8.7</em> (for the latest stable Fedora, as of this writing). On this version, <code>podman quadlet install</code> effectively copies over all your source code files into <a href="https://github.com/podman-container-tools/podman/blob/4c3027c149ad54dbb6f96328694e7b852623b4b8/pkg/domain/infra/abi/quadlet.go#L147-L162"><em>the base quadlet discovery directory</em></a>. If you're managing multiple services/quadlets, this would fill up full of <code>.container</code>'s and <code>.pods</code> pretty fast! To solve this, they add an <code>.app</code> file to denote which <code>.container</code> files belong to which, but this seems incredibly messy to me. Fortunately, they've changed this in Podman 6, and allow services to be grouped under different directory names <a href="https://docs.podman.io/en/stable/markdown/podman-quadlet-install.1.html#application-string">(via the <code>--application</code> flag)</a>.</p>
<p>In other words, once Fedora upgrades, I'd go with something like <code>podman quadlet install --application=&quot;example-app-dev&quot; ..</code> instead.</p>

</details>
<h4 id="final-thoughts">Final Thoughts <a class="anchor" href="#final-thoughts" aria-hidden="true">🔗</a></h4>
<p>Honestly, I prefer the k8-style workflow over this. The idea of linking my project subdirectory into an arbitrary system path to start my service just feels so, <em>alien</em>. Even with the changes to Podman 6 and the <code>--application</code> flag, it feels like an odd workflow.</p>
<p>On the flipside, INI-style quadlets <em>feel</em> &quot;closer to the metal&quot;. Having everything already written in systemd INI files is a pretty close representation of what actually gets loaded by systemd. There's a movement in the frontend ecosystem which eschews elaborate frameworks for plain &quot;vanilla&quot; javascript, and to <a href="https://elisehe.in/2021/08/22/using-the-platform">just use the platform</a>. I felt like there was a parallel to be found with Quadlets, and that was worth exploring.</p>
]]></content:encoded>
      <pubDate>Mon, 12 Oct 2026 00:00:00 +0000</pubDate>
      <category>podman</category>
      <category>web</category>
      <category>linux</category>
      <category>general</category>
    </item>
    <item>
      <title>Why Podman?</title>
      <link>https://mikebelanger.ca/blog/why_podman/</link>
      <guid>https://mikebelanger.ca/blog/why_podman/</guid>
      <description>My experiences with using Podman</description>
      <content:encoded><![CDATA[<p><a href="https://podman.io">Podman</a> and its affiliated projects can help manage <a href="https://opencontainers.org/about/overview/">OCI-compatible containers</a> in several different ways, making it more than a <a href="https://docker.com">Docker</a> substitute:</p>
<section class="listicle"><ol>
<li>
<h6>Licensing</h6>
</li>
</ol>
<p><a href="https://podman.io">Podman</a> is Apache-2.0 licensed. Basically, it's open source and can be used by an unlimited number of users for free. If you like using <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a>, its <a href="https://podman-desktop.io/">Podman equivalent</a> is free and open source as well, and won't <a href="https://docs.docker.com/subscription-billing/desktop-license/">ask you to pay if your company gets past a certain size</a>.</p>
<ol start="2">
<li>
<h6>Security</h6>
</li>
</ol>
<p>Podman allows its containers to be <a href="https://developers.redhat.com/blog/2020/09/25/rootless-containers-with-podman-the-basics#">rootless</a>. What is rootless? It means there isn't one central process (daemon) that's in charge of all your containers. Not one central process that could crash and bring down all your containers, not one central process that could get compromised, and start acting maliciously. To be fair, Docker <a href="https://docs.docker.com/engine/security/rootless/">has rootless mode now too</a>, so if rootless is your only attraction, just switch to that.</p>
<ol start="3">
<li>
<h6>Integration with SystemD</h6>
</li>
</ol>
<p>Podman's <a href="https://docs.podman.io/en/v5.8.1/markdown/podman-quadlet.1.html">Quadlets</a> facilitate integrating with <a href="https://systemd.io/">systemd</a> as well. Why bother integrating with systemd? Your web app is probably running on a server with Linux, and that version of Linux is probably using systemd. systemd isn't technically part of Linux itself, but it's the unofficial standard init system for most Linux distros, including <a href="https://fedoraproject.org/">Fedora</a>, <a href="https://ubuntu.com/">Ubuntu</a>, and many others. Unless your server is running <a href="http://www.slackware.com/config/init.php">Slackware</a>, integrating with systemd is a no-brainer.</p>
<ol start="4">
<li>
<h6>Pods</h6>
</li>
</ol>
<p>As the name suggests, Podman works with sets of <a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/building_running_and_managing_containers/assembly_working-with-pods_building-running-and-managing-containers">pods</a>. Although Docker has no equivalent feature, pods aren't specific to Podman either, as <a href="https://kubernetes.io/docs/concepts/workloads/pods/">the concept was inspired from Kubernetes</a>. Pods allow containers to be grouped together and given their own network namespace. Once containers are grouped into “pods”, those pods can be started/restarted together, like <code>podman pod start my-app</code>.</p>
<p>This feature is less geared towards developers, more so system admins/devops who manage lots of containers. That said, they're easy to set up, and alleviate some of the tedium associated with spinning up/down large numbers of containers.</p>
</section>
<p>I've been using Podman for a few years now, and overall I've found it a completely acceptable replacement for Docker. That said, I've found most of the tutorials/resources (and subsequently, AI) have described Podman from a more system-admin/devops workflow, not a web developer workflow. So in the next few posts, I'm going to outline some ways I've found to work with Podman containers. Some of them will be fairly familiar (like <a href="https://docs.podman.io/en/latest/markdown/podman-compose.1.html">podman-compose</a>), while others are much newer (like a <a href="https://www.redhat.com/en/blog/multi-container-application-podman-quadlet">Kubernetes-style “kube” file</a>).</p>
]]></content:encoded>
      <pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate>
      <category>podman</category>
      <category>web</category>
      <category>linux</category>
      <category>general</category>
    </item>
    <item>
      <title>Mixing instances of parent and child in Crystal</title>
      <link>https://mikebelanger.ca/blog/crystal_oo_types/</link>
      <guid>https://mikebelanger.ca/blog/crystal_oo_types/</guid>
      <description>A strange error message from the Crystal compiler.</description>
      <content:encoded><![CDATA[<p>I've been getting into <a href="https://crystal-lang.org">Crystal</a> lately.  Its syntax is a near-copy of <a href="https://ruby-lang.org/">Ruby's</a>, but its type system and compile-time checks are extremely strong.  It compiles using <a href="https://llvm.org/">LLVM</a>, like <a href="http://rust-lang.org">Rust</a> does, which results in really fast binaries.</p>
<p>While the language is fairly readable, its error-messages aren't always clear. I was working on something like this today:</p>
<pre data-copy="true"><code class="language-crystal hljs"><span class="hljs-keyword">class</span> <span class="hljs-variable constant_">Animal</span>
  <span class="hljs-keyword">def</span> <span class="hljs-variable">initialize</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-keyword">class</span> <span class="hljs-variable constant_">Dog</span> <span class="hljs-operator">&lt;</span> <span class="hljs-variable constant_">Animal</span>
  <span class="hljs-keyword">def</span> <span class="hljs-variable">initialize</span>(name : <span class="hljs-variable constant_">String</span>)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-keyword">class</span> <span class="hljs-variable constant_">PetCemetary</span>
  <span class="hljs-keyword">def</span> <span class="hljs-variable">initialize</span>(@pets : <span class="hljs-variable constant_">Array</span>(<span class="hljs-variable constant_">Animal</span> <span class="hljs-operator">|</span> <span class="hljs-variable constant_">Dog</span>))
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

rufus <span class="hljs-operator">=</span> <span class="hljs-variable constant_">Dog</span>.new(name: <span class="hljs-string">&quot;rufus&quot;</span>)

deadPets <span class="hljs-operator">=</span> <span class="hljs-variable constant_">Array</span>(<span class="hljs-variable constant_">Animal</span>).new
deadPets <span class="hljs-operator">&lt;&lt;</span> rufus
cemetary <span class="hljs-operator">=</span> <span class="hljs-variable constant_">PetCemetary</span>.new(deadPets)
</code></pre>
<p>This yielded a compile-error:</p>
<pre data-copy="true"><code class="language-sh hljs">Showing last frame. Use --error-trace <span class="hljs-keyword">for</span> full trace.

In classTest.cr:20:28

 <span class="hljs-number">20</span> <span class="hljs-punctuation">|</span> <span class="hljs-variable">cemetary</span> <span class="hljs-operator">=</span> PetCemetary.new<span class="hljs-operator">(</span>deadPets<span class="hljs-operator">)</span>
                                 ^-------
Error: expected argument <span class="hljs-comment">#1 to &#39;PetCemetary.new&#39; to be Array(Animal), not Array(Animal)</span>

Overloads are:
 - PetCemetary.new<span class="hljs-operator">(</span>pets : Array<span class="hljs-operator">(</span>Animal <span class="hljs-punctuation">|</span> Dog<span class="hljs-operator">))</span>
</code></pre>
<p>In particular:</p>
<blockquote>
<p>Error: expected argument #1 to 'PetCemetary.new' to be Array(Animal), not Array(Animal)</p>
</blockquote>
<p>The error message implies a type-mismatch, but the types are identical.</p>
<p>So what's the issue?</p>
<p>Crystal can accept descendant classes (in this case, <code>Dog</code>) where they just ask for the parent classes (here that's <code>Animal</code>).  So the solution was to remove the union from the parameter, and the <code>Dog</code> instance will still be accepted in to an array of <code>Animal</code>:</p>
<pre data-copy="true"><code class="language-crystal hljs"><span class="hljs-keyword">class</span> <span class="hljs-variable constant_">Animal</span>
  <span class="hljs-keyword">def</span> <span class="hljs-variable">initialize</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-keyword">class</span> <span class="hljs-variable constant_">Dog</span> <span class="hljs-operator">&lt;</span> <span class="hljs-variable constant_">Animal</span>
  <span class="hljs-keyword">def</span> <span class="hljs-variable">initialize</span>(name : <span class="hljs-variable constant_">String</span>)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-keyword">class</span> <span class="hljs-variable constant_">PetCemetary</span>
  <span class="hljs-keyword">def</span> <span class="hljs-variable">initialize</span>(@pets : <span class="hljs-variable constant_">Array</span>(<span class="hljs-variable constant_">Animal</span>))
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

rufus <span class="hljs-operator">=</span> <span class="hljs-variable constant_">Dog</span>.new(name: <span class="hljs-string">&quot;rufus&quot;</span>)

deadPets <span class="hljs-operator">=</span> <span class="hljs-variable constant_">Array</span>(<span class="hljs-variable constant_">Animal</span>).new
deadPets <span class="hljs-operator">&lt;&lt;</span> rufus
cemetary <span class="hljs-operator">=</span> <span class="hljs-variable constant_">PetCemetary</span>.new(deadPets)
</code></pre>
<p>But why would union of its type and sub-type throw an error?  Especially if its subtype and parent type are interchangeable.  Sure, the union is redundant, but not erroneous.</p>
<p>After discussing the error on the Crystal discord (thanks to <strong>@Blacksmoke16</strong> and <strong><a href="https://github.com/HertzDevil">@HertzDevil</a></strong> for checking this out) it was determined this is a compiler bug of sorts. Not a terrible one, but confusing nonetheless.</p>
<p>Basically, the type-checker might be <a href="https://github.com/crystal-lang/crystal/issues/14091">overly restrictive.</a> when comparing types in a union operator.  Things are a little more complicated by the fact that while it throws an error when expressing things in a <code>ParentType | ChildType</code> format, it doesn't throw an error when expressing it as <code>Union(ParentType, ChildType)</code>.  So really, its an inconsistency.</p>
<p>Anyways, I stil think Crystal is a fantastic language, and despite sometimes cryptic compile-time errors, I find the language is easy to use, productive, and relatively safe.  If you like the Ruby's syntax, but can't stand its sluggish performance, not to mention lack of null/nil safety and duck-typing system, give Crystal a shot. What's more, I've found the community of developers and contributors really friendly and responsive.</p>
]]></content:encoded>
      <pubDate>Thu, 14 Dec 2023 00:00:00 +0000</pubDate>
      <category>crystal</category>
      <category>general</category>
    </item>
  </channel>
</rss>
