<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Younes Merzouka</title>
    <description>The latest articles on DEV Community by Younes Merzouka (@ymerzouka).</description>
    <link>https://dev.to/ymerzouka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3778424%2F9fa7b890-b180-45e6-ab7b-f06c8fbdd6a3.png</url>
      <title>DEV Community: Younes Merzouka</title>
      <link>https://dev.to/ymerzouka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ymerzouka"/>
    <language>en</language>
    <item>
      <title>How to do work queuing?</title>
      <dc:creator>Younes Merzouka</dc:creator>
      <pubDate>Wed, 19 Aug 2026 16:37:32 +0000</pubDate>
      <link>https://dev.to/ymerzouka/how-to-do-work-queuing-3loc</link>
      <guid>https://dev.to/ymerzouka/how-to-do-work-queuing-3loc</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;One of the important things to consider for this CI/CD tool is how to execute the different jobs.&lt;br&gt;
CI/CD jobs are long running and can take up to many hours depending on the complexity of the code&lt;br&gt;
base, language, tooling, etc. This means that we cannot execute such tasks during the lifetime of a&lt;br&gt;
request.&lt;/p&gt;

&lt;p&gt;This would require having a job queue that multiple background workers read from to execute the&lt;br&gt;
different jobs. After we are done with a job we can save its status to a database for later&lt;br&gt;
querying. The user can poll the server to see the status of the CI/CD job.&lt;/p&gt;

&lt;p&gt;For the actual implementation of such a job queue we have a few options. But we first need to know&lt;br&gt;
what requirements we need the work queue to fulfill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do we need in a work queue?
&lt;/h2&gt;

&lt;p&gt;There are a few things we need in a work queue:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;At its most basic form a work queue needs to be able to store messages, and for those messages
to be able to be queried by multiple workers.&lt;/li&gt;
&lt;li&gt;Suppose a worker claims a job from the queue, then it crashes, or the message never reaches it
because of a network partition. If we removed the message after the worker request, then that
job is gone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means we cannot do anything to the job until we are sure of the delivery of the message. In&lt;br&gt;
   other words, we need an &lt;em&gt;at-least-once&lt;/em&gt; delivery guarantee.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Suppose now that a worker receives the message and confirms the receipt, then crashes while
executing the job. There are two potential issues in such a scenario:

&lt;ul&gt;
&lt;li&gt;If we just deliver the job without persisting it, that job is lost when the worker crashes, we
have no way of handling the failure.&lt;/li&gt;
&lt;li&gt;Even if we persisted the job, we need a specific retry mechanism.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;We cannot keep retrying forever, we need a way to know when we retried a job too much. This is
done by tracking the number of retries for a given job. In case we want to debug a potential issue
in our system that might be causing job failure, we can save the job to a &lt;em&gt;Dead Letter Queue&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the specific case of a CI/CD pipeline, we don't need retries. CI/CD jobs run for long duration&lt;br&gt;
and a job failure doesn't always translate to a system failure - it could be that the job is not&lt;br&gt;
defined correctly. This is also what popular CI/CD tools do: GitHub, GitLab, etc. This means that we&lt;br&gt;
don't need point 3 and 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dedicated message queues
&lt;/h2&gt;

&lt;p&gt;Now that we know what we need from our queue - or our implementation of one - we can start with the&lt;br&gt;
first solution for a work queue, which is to use a dedicated message queue such as RabbitMQ or Kafka.&lt;/p&gt;

&lt;p&gt;A message queue is difficult to get right, and using a dedicated tool for it allows us to move the&lt;br&gt;
complexity of such a tool to the infrastructure, while delivering the guarantees we need. RabbitMQ&lt;br&gt;
and Kafka have dedicated &lt;em&gt;brokers&lt;/em&gt; that handle things such as persistence, acknowledgements, retries,&lt;br&gt;
message tracking, and re-delivery.&lt;/p&gt;

&lt;p&gt;They might, however, need some tuning to get the features needed - for example, RabbitMQ requires&lt;br&gt;
enabling persistence to allow recovery after crashes, and you also need to configure consumers of&lt;br&gt;
messages to acknowledge messages when they are done.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kafka can process up to 1 million jobs/second and RabbitMQ around 10K/s.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Redis
&lt;/h2&gt;

&lt;p&gt;Another common way of doing work queues - used by tools like Celery - is to use Redis. Redis is a&lt;br&gt;
key-value store, which means it is not purposefully built to handle message queuing. However it has&lt;br&gt;
a few data structures that allow implementing a message queue that can satisfy the requirements&lt;br&gt;
listed above. We have two options: Lists and Streams.&lt;/p&gt;

&lt;p&gt;A common way of implementing message queues in Redis is to use normal and sorted lists. The main&lt;br&gt;
list holds the jobs. After claiming a job, the worker moves it from the main list to a sorted list&lt;br&gt;
acting as the &lt;code&gt;processing&lt;/code&gt; list - we'll get into why it needs to be sorted shortly. Once the job is&lt;br&gt;
done, the worker removes it from the sorted list and either deletes it or writes the final result to&lt;br&gt;
a database.&lt;/p&gt;

&lt;p&gt;Since the worker explicitly moves the job between lists, we get an at-least-once delivery guarantee -&lt;br&gt;
the worker has confirmed receipt. But if the worker fails mid-job, the job stays stuck in the&lt;br&gt;
processing list. To handle this we need a background process that scans the processing list and&lt;br&gt;
re-queues jobs that are past a time threshold. This is why the list needs to be sorted - it makes it&lt;br&gt;
easier to retrieve the oldest stuck items.&lt;/p&gt;

&lt;p&gt;We also don't want to rerun a buggy job forever, so we track failures using a retry field on the job.&lt;br&gt;
Past a certain threshold, the job moves to a dead letter queue for later debugging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fizwqlobbejetuho9g2bk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fizwqlobbejetuho9g2bk.png" alt="Redis-based Queue" width="800" height="743"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The stream in Redis is also another way of implementing a queue. How it works is similar to Kafka in&lt;br&gt;
that it is a log-based data structure that logs any incoming messages. The stream is a bit more&lt;br&gt;
complicated than plain lists, however, and is more suitable when having a lot of workers that need to&lt;br&gt;
be coordinated in different ways. Since it relies on a log-based mechanism for storage, it requires&lt;br&gt;
compaction which needs to be done manually.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Redis queues can process up to 20K jobs/second.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Postgres &amp;amp; MySQL
&lt;/h2&gt;

&lt;p&gt;In relational databases a job queue is simply another table in your database. This means that you&lt;br&gt;
track many of the states of a given job using fields in your table schema (status), and you just&lt;br&gt;
need to implement the logic around it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Job claiming changes status to processing.&lt;/li&gt;
&lt;li&gt;Finishing a job changes the status to done/failed depending on the result.&lt;/li&gt;
&lt;li&gt;Only jobs that are queued/unprocessed will be picked up by idle workers.&lt;/li&gt;
&lt;li&gt;A background job rechecks for timed out jobs and changes the status to
queued/unprocessed/created in case a worker fails before completing a job.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The main challenge with relational databases is competing workers. If both worker A and worker B do&lt;br&gt;
a select to get another job to execute and both get the same job (say with id 1), they will both&lt;br&gt;
claim the job and start processing it.&lt;/p&gt;

&lt;p&gt;You might ask &lt;em&gt;can't that also be an issue for Redis?&lt;/em&gt; Well... not really, Redis can only execute one&lt;br&gt;
operation at a time so no competing workers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5osluh2lyesfqk9wgztd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5osluh2lyesfqk9wgztd.png" alt="relational Queue Unlocked Rows" width="799" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To solve this we can use &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; which would select a row and lock it so that no&lt;br&gt;
other transaction (run by a given worker) can read it. The worker would then update the row to&lt;br&gt;
&lt;code&gt;processing&lt;/code&gt; on the same transaction where it claimed it. The lock is then released when the&lt;br&gt;
transaction commits.&lt;/p&gt;

&lt;p&gt;There is one issue however: if another worker tries to look for a job to process in another&lt;br&gt;
transaction, it would get blocked on the locked row - this is because relational databases block on&lt;br&gt;
locks by default. In order to avoid this we can use &lt;code&gt;SKIP LOCKED&lt;/code&gt; which would skip any locked rows.&lt;br&gt;
So the full statement &lt;code&gt;SELECT ... FOR UPDATE SKIP LOCKED&lt;/code&gt; would select a new unlocked row and apply a&lt;br&gt;
lock on the row for the worker to update it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3ewb4ievp99szxhqd73g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3ewb4ievp99szxhqd73g.png" alt="Relational Database Queue Lock Rows" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the actual implementation a few things are worth noting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Locks can degrade the performance of the database, so it is best to keep transactions as
short-lived as possible to release locks - this means you shouldn't process the jobs in the
transactions.&lt;/li&gt;
&lt;li&gt;Consider adding an index to speed up the querying of jobs.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Work queues based on Postgres process up to 12K requests/second. I don't have any information about MySQL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Discussion
&lt;/h2&gt;

&lt;p&gt;While many of the existing solutions are well suited for implementing a work queue, which one to&lt;br&gt;
reach for depends on your specific use case, and on what infrastructure you already have or how much&lt;br&gt;
you are willing to invest in new infrastructure.&lt;/p&gt;

&lt;p&gt;For my current goal with KCX, which is the tool being a single deployable unit, none of the existing&lt;br&gt;
solutions are suitable. They all require adding another component to the system in order to have a&lt;br&gt;
queue. This is why I chose to use SQLite, which would require just an extra volume mounted when&lt;br&gt;
deployed on a Kubernetes cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final implementation
&lt;/h2&gt;

&lt;p&gt;While being an SQL database, SQLite has the same concurrency model as Redis, at least to some&lt;br&gt;
degree. In its default mode, readers block writers and writers block each other.&lt;/p&gt;

&lt;p&gt;This is fine for KCX's use case. SQLite lets me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have a single deployable unit without an external dependency&lt;/li&gt;
&lt;li&gt;Keep write volume low, since CI/CD jobs are naturally long running&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However it is still worthwhile to apply some optimizations to improve performance:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable Write-Ahead Logging (WAL) mode so that writers don't block readers. Writers still block
each other, however.&lt;/li&gt;
&lt;li&gt;In order for a writer to write to the database it must acquire a file (database) level lock.
Other writers will receive a busy error when attempting to write in such a situation. In order to
avoid the error, we must set a &lt;code&gt;busy_timeout&lt;/code&gt; which would allow the writers to wait a bit before
throwing an error. Another solution is to allow a single database connection. In order for writers
to not block readers - in WAL mode - we need a separate connection for reading.&lt;/li&gt;
&lt;li&gt;Another situation in which we could have a busy error is if a transaction promotes from a simple
read transaction to a write transaction due to the execution of a write operation. The
&lt;code&gt;busy_timeout&lt;/code&gt; option can solve this but it is better to avoid it altogether. To avoid the error
we must start a transaction as a write transaction using &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wasn't able to find a reliable source for how much SQLite can process, but one blog post reports&lt;br&gt;
requests in the order of tens/second.&lt;/p&gt;

&lt;p&gt;I will be considering moving to another queue system in the future to improve performance, but&lt;br&gt;
SQLite suits my purposes for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@harsh.vaghela.work/postgres-is-the-only-queue-you-need-until-50k-jobs-sec-5931611b551c" rel="noopener noreferrer"&gt;Postgres Queue Implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.percona.com/blog/using-skip-lock-for-queue-processing-in-mysql/" rel="noopener noreferrer"&gt;MySQL SKIP LOCKED&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.index.dev/skill-vs-skill/redis-pubsub-vs-kafka-vs-rabbitmq" rel="noopener noreferrer"&gt;Kafka and RabbitMQ Performance Comparison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://riverqueue.com/docs/sqlite" rel="noopener noreferrer"&gt;SQLite-based Work Queue Guide By River&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sidekiq/sidekiq/wiki/Using-Redis" rel="noopener noreferrer"&gt;Redis Performance Measurement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dbos.dev/blog/benchmarking-workflow-execution-scalability-on-postgres" rel="noopener noreferrer"&gt;Postgres Performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/d_security/why-i-built-a-job-queue-with-sqlite-instead-of-redis-and-what-i-learned-4f05"&gt;SQLite Queue Implementation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devlog</category>
      <category>database</category>
      <category>redis</category>
      <category>postgres</category>
    </item>
    <item>
      <title>The Start</title>
      <dc:creator>Younes Merzouka</dc:creator>
      <pubDate>Wed, 12 Aug 2026 22:55:35 +0000</pubDate>
      <link>https://dev.to/ymerzouka/kcx-devlog-1-3a9f</link>
      <guid>https://dev.to/ymerzouka/kcx-devlog-1-3a9f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello!!!&lt;/p&gt;

&lt;p&gt;So I decided to learn a bit more about CI/CD pipelines, and what better way to learn about them than to create your own!!! However, I also wanted to add my own spin to it: integrating it with Kubernetes and maybe Docker, to build a Kubernetes-native CI/CD tool. I'm not sure if the idea already exists out there, but I think it's worthwhile exploring for learning purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Name
&lt;/h2&gt;

&lt;p&gt;The goal is to make use of what Kubernetes natively provides and build on top of it to create a more "Kubernetes-native" solution to CI/CD. Hence the first letter in &lt;code&gt;KCX&lt;/code&gt; is &lt;strong&gt;K&lt;/strong&gt;ubernetes.&lt;/p&gt;

&lt;p&gt;Since this is a CI/CD tool, I reduced both into &lt;strong&gt;CX&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So in a sense, it's named &lt;strong&gt;Kubernetes CI/CD&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This DevLog
&lt;/h2&gt;

&lt;p&gt;The goal behind this devlog is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share my progress&lt;/li&gt;
&lt;li&gt;Share any interesting ideas I run into while working on the tool&lt;/li&gt;
&lt;li&gt;Share my experience working with Go&lt;/li&gt;
&lt;li&gt;Engage with the community and listen for feedback on things I can improve, which is always welcome&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Will I Use to Build It?
&lt;/h2&gt;

&lt;p&gt;Since many tools in the cloud space already use Go, I decided to follow their lead. This also helps me get more familiar with the language, since I've wanted to learn it for a while.&lt;/p&gt;

&lt;h2&gt;
  
  
  Any Progress?
&lt;/h2&gt;

&lt;p&gt;So far I've started exploring a bit around the core ideas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloning a repository and executing a simple build script&lt;/li&gt;
&lt;li&gt;Creating a webhook and wiring it up with GitHub&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also tried out a distributed work queue setup — multiple workers wired to an API service through a RabbitMQ broker. After testing it, I decided to keep the tool minimal for now, with workers running as local threads rather than their own services. Eventually, I plan to have the tool support a distributed setup too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Will I Use LLMs?
&lt;/h2&gt;

&lt;p&gt;Not as of yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Will These DevLogs Be Structured?
&lt;/h2&gt;

&lt;p&gt;I'll aim for weekly. Whether I actually stick to that depends on what else I have going on — we'll see!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next up, I want to get a full local build running end-to-end, with support for both public and private repositories. I also want to test this across multiple platforms — GitLab, Codeberg, and GitHub — to make sure the tool isn't tied to just one ecosystem.&lt;/p&gt;

</description>
      <category>sideprojects</category>
      <category>go</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Microservice Data Handling — Saga Pattern</title>
      <dc:creator>Younes Merzouka</dc:creator>
      <pubDate>Wed, 05 Aug 2026 15:47:50 +0000</pubDate>
      <link>https://dev.to/ymerzouka/microservice-data-handling-saga-pattern-3hfk</link>
      <guid>https://dev.to/ymerzouka/microservice-data-handling-saga-pattern-3hfk</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In a microservice architecture, it is often recommended that each service has its own separate &lt;br&gt;
data storage. This is because the nature of microservices — &lt;em&gt;and why they are created&lt;/em&gt; — &lt;br&gt;
mandates such design.&lt;/p&gt;

&lt;p&gt;Implementing such architecture would allow for many things, such as: separate schema updates,&lt;br&gt;
the use of proper storage technologies for the task the microservice is trying to achieve, ...&lt;br&gt;
But how can we handle data operations (retrieval/update/...) that span multiple services? For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A transaction requires multiple updates: &lt;em&gt;update balance and place order&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;A transaction requires joins across different services: 
&lt;em&gt;what is a customer's most commonly ordered items?&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These data operations can fall under one of two categories: either we are trying to retrieve data&lt;br&gt;
from multiple services and aggregate them, or we are trying to make modifications/deletions/insertions&lt;br&gt;
that span multiple services.&lt;/p&gt;

&lt;p&gt;In this post, we will focus on the latter — or to be more specific: &lt;br&gt;
&lt;strong&gt;how do we handle cross-service transactions?&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Transactions
&lt;/h2&gt;

&lt;p&gt;Before we look into how to solve cross-service transactions, we first need to understand: &lt;br&gt;
&lt;strong&gt;&lt;em&gt;what are transactions?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose you want to update a single row in a database: a customer's address. You would need to &lt;br&gt;
execute a single instruction/operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'***'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'***'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Since this is a single operation, it would either succeed or fail. This is what we call an &lt;br&gt;
&lt;strong&gt;atomic&lt;/strong&gt; operation since it satisfies the &lt;strong&gt;all-or-nothing&lt;/strong&gt; behaviour, meaning it either &lt;br&gt;
succeeds in its entirety or fails in its entirety.&lt;/p&gt;

&lt;p&gt;Now suppose it is our product's anniversary and we are feeling generous. We want to give our loyal &lt;br&gt;
customers (those that stayed for at least 10 years) a staggering &lt;strong&gt;1 dollar!!!&lt;/strong&gt; as in-app credits.&lt;br&gt;
The SQL for that is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;***&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Suppose now that we have this amounts to 1000 updates, and the database server fails mid-operation.&lt;br&gt;
This would leave the system in an inconsistent state: some of the customers wouldn't receive our &lt;br&gt;
generous offer. This is an example of a &lt;strong&gt;non-atomic&lt;/strong&gt; operation. In such situation, it is more &lt;br&gt;
desirable to at least go back to the previous state and retry later.&lt;/p&gt;

&lt;p&gt;Transactions aim to address the problem of &lt;strong&gt;atomicity&lt;/strong&gt;. They allow you to execute multiple &lt;br&gt;
instructions that either succeed all together, or &lt;strong&gt;rollback&lt;/strong&gt; — allowing the preservation of the &lt;br&gt;
consistency of the database.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transactions in distributed systems
&lt;/h2&gt;

&lt;p&gt;Now that we know what transactions are, how do they look when the operations need to happen across &lt;br&gt;
different nodes connected through a network? Or more formally: &lt;em&gt;how do they work in distributed systems?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But wait... what do distributed systems have to do with microservices?&lt;/em&gt; In some sense, &lt;br&gt;
microservices are a kind of distributed system where nodes are services connected through a network.&lt;/p&gt;

&lt;p&gt;Let us consider a scenario as an example to understand why distributed transactions differ from &lt;br&gt;
traditional ones: a customer makes a purchase, and as such we need to insert a new row and update his &lt;br&gt;
balance in an atomic manner. Why atomic, you might say? Suppose that we are able to insert a &lt;br&gt;
purchase row, but the update to the customer balance fails; then it is as if the customer has &lt;br&gt;
made a free purchase.&lt;/p&gt;

&lt;p&gt;For a single database, the atomicity of the operation can be insured using the traditional transaction we talked &lt;br&gt;
about earlier, that either succeeds or rolls back. But what if the &lt;code&gt;customers&lt;/code&gt; and &lt;code&gt;purchases&lt;/code&gt; &lt;br&gt;
table live on different nodes? How can we coordinate the two nodes to ensure the atomicity of the operation?&lt;/p&gt;

&lt;p&gt;This problem is not new, and databases have come up with multiple solutions for what is called &lt;br&gt;
&lt;strong&gt;&lt;em&gt;distributed transactions&lt;/em&gt;&lt;/strong&gt;. A common example is &lt;strong&gt;&lt;em&gt;Two-Phase Commit&lt;/em&gt;&lt;/strong&gt; (&lt;em&gt;or **2PC&lt;/em&gt;* for short*),&lt;br&gt;
but it is out of the scope of this blog post.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Ok then, problem solved!!!"&lt;/em&gt; you might say... but there are a few things that prevent us from &lt;br&gt;
using this solution directly when it comes to microservice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Even though some databases already have support for distributed transactions, in a microservice 
architecture, different services might use different storage systems (for example, MongoDB and Postgres),
which prevents implementing them at the database layer. Using a single storage backend across all 
services is also not an option, since the whole point of the architecture is to allow agility and 
the use of the proper tool for the job.&lt;/li&gt;
&lt;li&gt;Storage systems sit behind the services themselves, which prevents direct access and requires 
that we handle such transactions at the level of the services rather than leave it out for storage systems.&lt;/li&gt;
&lt;li&gt;Implementing a fully blown &lt;em&gt;2PC&lt;/em&gt; distributed transaction is complex even for a single service,
let alone across dozens or hundreds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So to solve for this, we need a different method that allows for the &lt;em&gt;all-or-nothing&lt;/em&gt; capability of &lt;br&gt;
transactions, but that is simpler and implementable at the service level.&lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;Saga pattern&lt;/strong&gt; comes in...&lt;/p&gt;




&lt;h2&gt;
  
  
  The Saga Pattern
&lt;/h2&gt;

&lt;p&gt;Let us take a simple example of a banking application:&lt;br&gt;
We have two services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;transactions service&lt;/strong&gt; to handle all transactions done by customers.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;customers service&lt;/strong&gt; which stores information about customers and their banking information — 
most importantly, their account balance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now suppose in our example that the customer makes a purchase to buy an AI subscription &lt;br&gt;
(&lt;em&gt;because of all of what he has being hearing about its productivity benefits&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;The bank's backend receives the request for the purchase. In a normal monolithic application, &lt;br&gt;
the process is simple: you would insert a new row in the transactions table referencing the customer,&lt;br&gt;
update the customer's balance, wrapping everything in a transaction to make sure this is applied atomically.&lt;/p&gt;

&lt;p&gt;But in a microservice architecture, this isn't so straightforward as discussed earlier. &lt;/p&gt;

&lt;p&gt;Let us consider the happy path. The transactions service inserts a new row with a &lt;code&gt;PENDING&lt;/code&gt; status. &lt;br&gt;
It then synchronizes with the customers service, which handles the update to the customer row. &lt;br&gt;
Upon confirmation of the update, the transactions row would update its own newly inserted row to &lt;code&gt;COMPLETE&lt;/code&gt;,&lt;br&gt;
for example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6j51c1vhnyzvoelplt7n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6j51c1vhnyzvoelplt7n.png" alt="Saga Pattern: Happy Path Sequence" width="719" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let us suppose that the transactions service fails to synchronize the update with the customers service,&lt;br&gt;
or the customers service fails to inform the transactions service about its successful update.&lt;br&gt;
This is where the rollback part of the saga comes in, where each saga step has a&lt;br&gt;
(&lt;em&gt;compensating transaction&lt;/em&gt;) which works by rolling back all previous steps if a single one fails.&lt;br&gt;
This is in part similar to how a database does transactions, but only ensures &lt;strong&gt;eventual consistency&lt;/strong&gt;&lt;br&gt;
rather than the atomicity insured by the database's ACID transactions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frj40bi6yvcvplzf85g49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frj40bi6yvcvplzf85g49.png" alt="Saga Pattern: Rollback &amp;amp; Compensation Sequence" width="719" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two ways of implementing Sagas and coordinating services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choreography:&lt;/strong&gt; a service processes its part of the saga and publishes events that trigger updates to other services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration:&lt;/strong&gt; a central saga orchestrator manages the full saga, saving its state and rolling back appropriately on failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Finrs4ze1mq7s0m89c9mb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Finrs4ze1mq7s0m89c9mb.png" alt="Saga Choreography: Event-Driven Architecture" width="800" height="99"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsu0o5uufl9ikn9lygjm0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsu0o5uufl9ikn9lygjm0.png" alt="Saga Orchestration: Central Coordinator Architecture" width="644" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One important caveat about Sagas is that the local write and the network call need to be atomic &lt;br&gt;
— &lt;em&gt;often resolved using the **Transactional Outbox Pattern&lt;/em&gt;** — or else the system &lt;br&gt;
will become stuck in an inconsistent state.&lt;/p&gt;

&lt;p&gt;We also ignored one part, which is the client waiting for his transaction to end (&lt;em&gt;to start his Agentic life&lt;/em&gt;).&lt;br&gt;
The most plausible way of solving this — &lt;em&gt;other than keeping him waiting&lt;/em&gt; — would be to provide a &lt;br&gt;
&lt;code&gt;transactionId&lt;/code&gt; to the customer and a special endpoint for him to poll to see if his purchase succeeded or not.&lt;/p&gt;




&lt;p&gt;Do you know of any other patterns?&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>software</category>
      <category>architecture</category>
      <category>data</category>
    </item>
    <item>
      <title>Picking a Package Manager for Your CI/CD Pipeline</title>
      <dc:creator>Younes Merzouka</dc:creator>
      <pubDate>Sun, 15 Mar 2026 22:12:22 +0000</pubDate>
      <link>https://dev.to/ymerzouka/picking-a-package-manager-for-your-cicd-pipeline-59lc</link>
      <guid>https://dev.to/ymerzouka/picking-a-package-manager-for-your-cicd-pipeline-59lc</guid>
      <description>&lt;p&gt;If you want to ship fast then CI/CD is the most important thing for you to consider. But have you ever thought "Does the package manager I use really matter for CI/CD?". This might not be an issue for other languages like Python, but JavaScript does have a few options to choose from: some claiming to be the fastest, others prioritizing developer experience and disk usage, and then there's good old NPM.&lt;/p&gt;

&lt;p&gt;In this blog post we'll look into different package managers, examples of how to use each, and how well each performs when it comes to &lt;strong&gt;CI/CD&lt;/strong&gt; pipelines. Of course, with everybody's favorite: &lt;strong&gt;Benchmarks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The package managers we will discuss are &lt;strong&gt;PNPM&lt;/strong&gt;, &lt;strong&gt;NPM&lt;/strong&gt;, &lt;strong&gt;Yarn&lt;/strong&gt; and &lt;strong&gt;Bun&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;The application used is the standard &lt;strong&gt;NestJS&lt;/strong&gt; starter app, running on a GitLab CI pipeline. Below are the &lt;code&gt;Dockerfile&lt;/code&gt; for each of the package managers with a brief explanation of the flags used and some extra info.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We will not focus on securing the images for now, so the examples don't include any security measures. Distroless is used to minimize the size of the final container image.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  NPM
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;ci&lt;/code&gt; option ensures that &lt;code&gt;package-lock.json&lt;/code&gt; is used, preventing any accidental changes to dependency versions. &lt;code&gt;--omit=dev&lt;/code&gt; ensures only production dependencies are installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22.22.1-slim&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="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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="s"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./package.json ./package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&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="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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="s"&gt;deps&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./package.json ./package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev


&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;gcr.io/distroless/nodejs24-debian13&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="s"&gt;prod&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/node_modules ./node_modules/&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "/app/dist/main.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PNPM
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;--frozen-lockfile&lt;/code&gt; has the same effect as &lt;code&gt;npm ci&lt;/code&gt; — it locks the install to what's in the lockfile. The PNPM global store is placed at &lt;code&gt;/pnpm&lt;/code&gt; inside the Docker image, configured via &lt;code&gt;PNPM_HOME&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22.22.1-slim&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="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PNPM_HOME="/pnpm"&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="$PNPM_HOME:$PATH"&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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="s"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./package.json ./pnpm-lock.yaml ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&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="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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="s"&gt;deps&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./package.json ./pnpm-lock.yaml ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt; &lt;span class="nt"&gt;--prod&lt;/span&gt;


&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;gcr.io/distroless/nodejs24-debian13&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="s"&gt;prod&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/node_modules ./node_modules/&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "dist/main.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Yarn
&lt;/h3&gt;

&lt;p&gt;The default Yarn configuration that comes with NestJS uses the old classic version rather than the modern one with &lt;strong&gt;Plug'n'Play&lt;/strong&gt;. To upgrade, run &lt;code&gt;yarn set version stable&lt;/code&gt;, then enable PnP with &lt;code&gt;yarn config set nodeLinker pnp&lt;/code&gt; — this skips &lt;code&gt;node_modules&lt;/code&gt; entirely. Then install with &lt;code&gt;yarn install&lt;/code&gt;. For the rest of this post, "Yarn" refers to this latest Plug'n'Play version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22.22.1-slim&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="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json yarn.lock .yarnrc.yml ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--immutable&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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="s"&gt;deps&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json yarn.lock ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; yarn workspaces focus &lt;span class="nt"&gt;--production&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;gcr.io/distroless/nodejs24-debian13:debug&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="s"&gt;prod&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /root/.yarn/berry/cache /root/.yarn/berry/cache&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/.pnp.cjs /app/.pnp.loader.mjs ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/.yarn ./.yarn&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "--require", "./.pnp.cjs", "dist/main.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bun
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;docker.io/oven/bun:1.3-alpine&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="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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="s"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./bun.lock ./package.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;bun &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&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="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;bun run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;docker.io/oven/bun:1.3-alpine&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="s"&gt;prod&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /usr/src/app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=install /usr/src/app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; [ "bun", "dist/main.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How Package Managers Work — and Why It Matters for CI
&lt;/h2&gt;

&lt;p&gt;Before jumping to the benchmarks, it's worth understanding what actually differentiates these package managers under the hood, since their design choices directly affect both install speed and image size.&lt;/p&gt;

&lt;h3&gt;
  
  
  PNPM
&lt;/h3&gt;

&lt;p&gt;PNPM's main goal is solving the disk space problem that &lt;code&gt;node_modules&lt;/code&gt; are notorious for. On a typical machine, every project gets its own full copy of every dependency — meaning if you have ten projects all using React, you have ten copies of React on disk.&lt;/p&gt;

&lt;p&gt;PNPM solves this by maintaining a single global content-addressable store (pointed to by &lt;code&gt;$PNPM_HOME&lt;/code&gt;), where every package file is stored exactly once. Each project's &lt;code&gt;node_modules&lt;/code&gt; then contains &lt;strong&gt;hard links&lt;/strong&gt; pointing back to that central store rather than actual copies of the files. To keep the store lean, PNPM also tracks file-level changes between package versions using hashes — only storing the differing files, not a full new copy of the package.&lt;/p&gt;

&lt;p&gt;Beyond disk savings, PNPM also addresses a subtle but important issue called &lt;strong&gt;phantom packages&lt;/strong&gt;. Here's the problem: NPM has historically &lt;strong&gt;hoisted&lt;/strong&gt; dependencies — meaning if your dependency &lt;code&gt;express&lt;/code&gt; depends on &lt;code&gt;lodash&lt;/code&gt;, NPM would move &lt;code&gt;lodash&lt;/code&gt; up into the root &lt;code&gt;node_modules&lt;/code&gt; folder, making it accidentally importable in your own code. Your project would work even though you never listed &lt;code&gt;lodash&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt;. If &lt;code&gt;express&lt;/code&gt; later drops it or changes its version, your code silently breaks.&lt;/p&gt;

&lt;p&gt;PNPM avoids this by keeping each package's dependencies isolated under &lt;code&gt;node_modules/.pnpm/package@version/node_modules/&lt;/code&gt;, and exposing only your direct dependencies as &lt;strong&gt;symlinks&lt;/strong&gt; at the root of &lt;code&gt;node_modules&lt;/code&gt;. This makes the dependency tree accurate and predictable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqlzhlwlohw2uq0tqi68.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqlzhlwlohw2uq0tqi68.png" alt="PNPM Store and Links Illustration" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What this means for Docker
&lt;/h4&gt;

&lt;p&gt;While PNPM's central store is extremely useful during development — especially in monorepos — it doesn't give you the same benefit inside a Docker container. Each container is its own isolated environment with its own dependencies, so the global store never gets reused across builds the way it would on a developer machine. In practice, PNPM ends up behaving similarly to NPM in a container context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bun
&lt;/h3&gt;

&lt;p&gt;Bun takes a fundamentally different approach to performance: it treats package installation as a &lt;strong&gt;systems problem&lt;/strong&gt; rather than a JavaScript problem.&lt;/p&gt;

&lt;p&gt;The core of this is Bun being written in &lt;strong&gt;Zig&lt;/strong&gt;, a compiled systems language, rather than JavaScript. This alone removes a significant layer of overhead. But Bun also targets three specific bottlenecks that slow down Node.js-based package managers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System calls:&lt;/strong&gt; Node.js uses &lt;code&gt;libuv&lt;/code&gt;, a C library, to make OS-level calls for things like reading files and managing threads. Each call involves some compatibility overhead, and Node.js adds further overhead managing its thread pools. Bun calls the OS more directly and with fewer round-trips. You can see this concretely in the &lt;code&gt;strace&lt;/code&gt; output below, from &lt;a href="https://bun.com/blog/behind-the-scenes-of-bun-install" rel="noopener noreferrer"&gt;Bun's blog&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Benchmark 1: strace -c -f npm install
    Time (mean ± σ):  37.245 s ±  2.134 s [User: 8.432 s, System: 4.821 s]
    Range (min … max):   34.891 s … 41.203 s    10 runs

    System calls: 996,978 total (108,775 errors)
    Top syscalls: futex (663,158),  write (109,412), epoll_pwait (54,496)

  Benchmark 2: strace -c -f bun install
    Time (mean ± σ):      5.612 s ±  0.287 s [User: 2.134 s, System: 1.892 s]
    Range (min … max):    5.238 s …  6.102 s    10 runs

    System calls: 165,743 total (3,131 errors)
    Top syscalls: openat(45,348), futex (762), epoll_pwait2 (298)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nearly 6× fewer system calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manifest parsing:&lt;/strong&gt; NPM reads package metadata from human-readable formats like JSON and YAML, which need to be parsed on every install. Bun stores this metadata in a binary format that can be loaded directly without parsing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decompression:&lt;/strong&gt; When downloading packages (which are compressed tarballs), most tools decompress on the fly, guessing at buffer sizes and reallocating memory as data streams in. Bun instead downloads the full compressed file first, determines the exact buffer size needed using the gzip format, allocates it once, and then decompresses — eliminating unnecessary memory copies.&lt;/p&gt;

&lt;p&gt;Bun also starts DNS lookups while reading &lt;code&gt;package.json&lt;/code&gt; rather than waiting until after, trimming latency at the very start of the install process. There are a few more optimizations covered in the references if you want to go deeper.&lt;/p&gt;

&lt;h3&gt;
  
  
  Yarn
&lt;/h3&gt;

&lt;p&gt;Yarn's Plug'n'Play mode works differently from both NPM and PNPM. Rather than populating a &lt;code&gt;node_modules&lt;/code&gt; folder at all, Yarn downloads packages as &lt;code&gt;.zip&lt;/code&gt; archives and stores them in a central cache folder (viewable via &lt;code&gt;yarn config get cacheFolder&lt;/code&gt; — in our container, this is &lt;code&gt;/root/.yarn/berry/cache&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;At runtime, instead of Node.js resolving modules from &lt;code&gt;node_modules&lt;/code&gt;, Yarn injects a custom loader (&lt;code&gt;.pnp.cjs&lt;/code&gt;) that intercepts &lt;code&gt;require()&lt;/code&gt; calls and loads modules directly from the &lt;code&gt;.zip&lt;/code&gt; archives. The current install state is saved in &lt;code&gt;.yarn/install-state.gz&lt;/code&gt;, and the package cache lives in &lt;code&gt;.yarn/cache&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This design means no &lt;code&gt;node_modules&lt;/code&gt; directory is created when you run &lt;code&gt;yarn install&lt;/code&gt; — which also means no hoisting, no phantom packages, and significantly less I/O.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Yarn performed so well
&lt;/h4&gt;

&lt;p&gt;The architecture above translates directly into install speed. Yarn only needs to download and archive packages, then generate the loader — no creating thousands of symlinks or hard links per file, simpler hoisting logic, no repeated I/O across a deep directory tree, and proper tracking of your project's dependencies. For a fresh Docker build where none of those steps can be cached, that's a meaningful advantage.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package Manager&lt;/th&gt;
&lt;th&gt;Run 1 (f813c0c5)&lt;/th&gt;
&lt;th&gt;Run 2 (ecb90ce2)&lt;/th&gt;
&lt;th&gt;Run 3 (0938ee84)&lt;/th&gt;
&lt;th&gt;Average Time&lt;/th&gt;
&lt;th&gt;Average Size (MiB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;yarn&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1:20&lt;/td&gt;
&lt;td&gt;1:23&lt;/td&gt;
&lt;td&gt;1:14&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1:19&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;58.26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;npm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1:49&lt;/td&gt;
&lt;td&gt;2:06&lt;/td&gt;
&lt;td&gt;1:51&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1:55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;57.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;pnpm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1:55&lt;/td&gt;
&lt;td&gt;1:54&lt;/td&gt;
&lt;td&gt;2:12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2:00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;57.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;bun&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2:06&lt;/td&gt;
&lt;td&gt;2:01&lt;/td&gt;
&lt;td&gt;2:14&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2:07&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;72.14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even though I personally expected Bun to win this, it ended up performing on par with NPM — and the extra few MiB don't really justify the switch. So the winner here is Yarn, by a clear margin.&lt;/p&gt;

&lt;p&gt;I did some digging trying to explain why Bun underperformed, but couldn't find a satisfying answer. I'd be very interested in hearing your thoughts in the comments, and depending on the discussion, a deeper dive might be worth its own post.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://yarnpkg.com/features/pnp" rel="noopener noreferrer"&gt;Yarn Plug'n'Play&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bun.com/blog/behind-the-scenes-of-bun-install" rel="noopener noreferrer"&gt;Bun's performance improvements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pnpm.io/motivation" rel="noopener noreferrer"&gt;How pnpm works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pnpm.io/blog/2020/05/27/flat-node-modules-is-not-the-only-way" rel="noopener noreferrer"&gt;How pnpm does hoisting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/5inchiroca/nestjs-yarn-v4-installation-and-deployment-kjd"&gt;Setting up Yarn for NestJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/younes-merzouka-blog/package-managers-and-ci.git" rel="noopener noreferrer"&gt;Source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>performance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>What Rust Does Differently: A Beginner's Perspective</title>
      <dc:creator>Younes Merzouka</dc:creator>
      <pubDate>Sat, 07 Mar 2026 22:11:29 +0000</pubDate>
      <link>https://dev.to/ymerzouka/what-rust-does-differently-a-beginners-perspective-9h2</link>
      <guid>https://dev.to/ymerzouka/what-rust-does-differently-a-beginners-perspective-9h2</guid>
      <description>&lt;p&gt;Rust makes a lot of interesting decisions about how things work — what you can and cannot do, and how you do certain things. Coming from a mostly web development background, three concepts in particular caught my attention as a beginner: Mutability, Ownership, and Handling Null.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mutability
&lt;/h2&gt;

&lt;p&gt;Rust variables are immutable by default. So code like this in JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;a&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...wouldn't work in Rust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;a&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="c1"&gt;// compiler error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To mutate a variable, you need to add the &lt;code&gt;mut&lt;/code&gt; keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;a&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason is safety. This mechanism forces you to explicitly opt into mutability, which makes your code more deterministic and avoids problems — especially with concurrency, where two threads might otherwise modify the same variable without you realising it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mutable References
&lt;/h3&gt;

&lt;p&gt;One thing that tripped me up early on was mutable references. Suppose we want to read from &lt;code&gt;stdin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.read_line&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;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// passing a reference to s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks reasonable — similar to how you'd do it in C. But it actually results in a compiler error telling you that &lt;code&gt;read_line&lt;/code&gt; expects a &lt;em&gt;mutable&lt;/em&gt; reference. The correct version is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.read_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have to explicitly tell the compiler that the reference itself is mutable. Why this matters will become clearer once we talk about ownership.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ownership
&lt;/h2&gt;

&lt;p&gt;Managing allocated memory is a well-known challenge in systems programming, and there are a few common approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Garbage collection&lt;/strong&gt; — many high-level languages manage memory for you through a GC. This simplifies things for developers but can impact performance through "GC pauses", where the entire application halts while unused memory is freed. Not ideal for systems software.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual memory management&lt;/strong&gt; — the approach C takes. You allocate memory and you're responsible for freeing it. This has historically led to many bugs and security vulnerabilities: null pointer dereferences, buffer overflows, use-after-free, and so on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rust takes a different approach through &lt;strong&gt;ownership&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Consider this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// frees the heap memory&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"World"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// compiler error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function &lt;code&gt;add_suffix&lt;/code&gt; receives a &lt;code&gt;String&lt;/code&gt; allocated on the heap, drops it (equivalent to &lt;code&gt;free&lt;/code&gt; in C), and then &lt;code&gt;main&lt;/code&gt; tries to access that freed memory — a classic use-after-free bug. Rust won't let this compile.&lt;/p&gt;

&lt;p&gt;You might think the fix is simply to remove the &lt;code&gt;drop&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suf&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="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"World"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// still a compiler error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this still doesn't work. Passing &lt;code&gt;s&lt;/code&gt; to &lt;code&gt;add_suffix&lt;/code&gt; as an argument &lt;strong&gt;transfers ownership&lt;/strong&gt; of &lt;code&gt;s&lt;/code&gt; to that function.&lt;/p&gt;

&lt;p&gt;So what does &lt;em&gt;transferring ownership&lt;/em&gt; actually mean? In Rust, each heap-allocated value is owned by the scope in which it was created. When that scope ends, the memory is automatically freed. So when &lt;code&gt;add_suffix&lt;/code&gt; returns, &lt;code&gt;s&lt;/code&gt; is freed — and trying to use it in &lt;code&gt;println!&lt;/code&gt; afterwards is accessing a dead memory region.&lt;/p&gt;

&lt;p&gt;Here's another example of the same idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// s is dropped here, memory is freed&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// compiler error: p points to freed memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correct way to achieve what we originally wanted is through &lt;strong&gt;borrowing&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// takes a mutable reference&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suf&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="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"World"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// lends s to the function&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// s is still valid here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By passing a reference (&lt;code&gt;&amp;amp;mut s&lt;/code&gt;), &lt;code&gt;main&lt;/code&gt; remains the owner of the memory. The function simply &lt;em&gt;borrows&lt;/em&gt; it temporarily. When the function returns, the borrow ends — but the memory isn't freed, because &lt;code&gt;main&lt;/code&gt; still owns it.&lt;/p&gt;

&lt;p&gt;This same principle applies even without mutation. If you don't need to mutate the value, you use &lt;code&gt;&amp;amp;&lt;/code&gt; instead of &lt;code&gt;&amp;amp;mut&lt;/code&gt;. Now the reason you need &lt;code&gt;&amp;amp;mut&lt;/code&gt; for mutable references (as we saw with &lt;code&gt;read_line&lt;/code&gt;) should make a lot more sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  Option
&lt;/h2&gt;

&lt;p&gt;Rust doesn't have &lt;code&gt;null&lt;/code&gt;. But there are situations where you genuinely want to express: &lt;em&gt;"this function either returns a result, or nothing — not because of an error, but because there's simply nothing to return."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Consider this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_slice_by_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.char_indices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;needle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// what do we return if the character isn't found?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;null&lt;/code&gt; might seem useful here. For exactly these cases, Rust has &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_slice_by_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.char_indices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;needle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Some&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;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What makes &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt; special is that it addresses the classic problem with &lt;code&gt;null&lt;/code&gt;: forgetting to handle it. &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt; forces you to explicitly deal with both cases. For example, this won't compile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_slice_by_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello, there"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// compiler error: expected &amp;amp;str, found Option&amp;lt;&amp;amp;str&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correct approach is to use &lt;code&gt;match&lt;/code&gt; (similar to a switch statement) to handle both possibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nf"&gt;get_slice_by_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello, there"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;None&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nd"&gt;panic!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Character not found"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler won't let you use the value inside an &lt;code&gt;Option&lt;/code&gt; without first unpacking it — which means you can never accidentally forget to handle the "nothing" case.&lt;/p&gt;




&lt;p&gt;These three concepts — mutability, ownership, and &lt;code&gt;Option&lt;/code&gt; — felt strange to me at first coming from other languages. But once they click, you start to see how they all work together to make Rust code safe without needing a garbage collector. Pretty elegant, honestly.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>beginners</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
