<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<title>Olivier &#x27;reivilibre&#x27;</title>
	<link href="https://o.librepush.net/atom.xml" rel="self" type="application/atom+xml"/>
	<link href="https://o.librepush.net"/>
	<generator uri="https://www.getzola.org/">Zola</generator>
	<updated>2026-02-24T00:00:00+00:00</updated>
	<id>https://o.librepush.net/atom.xml</id>
	
	
	
	<entry xml:lang="en">
		<title>Setting up a M93p mini offsite backup server, with power and cost calculations</title>
		<published>2024-07-03T00:00:00+00:00</published>
		<updated>2024-07-03T00:00:00+00:00</updated>
		<link rel="alternate" href="https://o.librepush.net/blog/2024-07-03-m93p-offsite-backup-server/" type="text/html"/>
		<id>https://o.librepush.net/blog/2024-07-03-m93p-offsite-backup-server/</id>
		<content type="html">&lt;p&gt;This blog post will cover:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;what I&#x27;m doing for my offsite backup system using a M93p mini PC;&lt;&#x2F;li&gt;
&lt;li&gt;how much energy it uses;&lt;&#x2F;li&gt;
&lt;li&gt;how much it costs every year; and&lt;&#x2F;li&gt;
&lt;li&gt;how that compares against commercial services.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;At the end, there are some bonus scribbles about:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;how I fixed a problem with suspend (it would keep turning off instead of suspending);&lt;&#x2F;li&gt;
&lt;li&gt;how to reset the BIOS variables &#x2F; &#x27;CMOS memory&#x27;; and&lt;&#x2F;li&gt;
&lt;li&gt;how I configured my BIOS.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;motivation&quot;&gt;Motivation&lt;a class=&quot;zola-anchor&quot; href=&quot;#motivation&quot; aria-label=&quot;Anchor link for: motivation&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I need to do a better job at keeping backups, because realistically it&#x27;s only a matter of time until something bad happens and I lose something that I consider sentimentally important.&lt;br &#x2F;&gt;
I think basically everyone should consider having off-site backups — backups that aren&#x27;t in your home — which would not be lost in the event of a fire, theft, or so on, alongside your main home for the data.&lt;&#x2F;p&gt;
&lt;p&gt;Perhaps an obvious solution would be to rent some online storage space at some server provider &#x2F; cloud provider.&lt;br &#x2F;&gt;
This can work, but the following issues arise:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You may not want to trust some cloud providers to not lock your account out at the worst time.&lt;br &#x2F;&gt;
(Example: A certain big company starting with G is slightly notorious for locking out accounts on a whim and yet not having any kind of customer support.
In a disaster scenario, you may be logging in from a different location or you may not have one of your devices for multi-step authentication. That could just be the red flag that locks you out.)&lt;&#x2F;li&gt;
&lt;li&gt;You may not trust the privacy of such a provider.
&lt;ul&gt;
&lt;li&gt;This can be worked around to some extent with encryption, which I suggest anyway, but I still would hesitate to give money to the forces of evil if I could avoid it.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;If you have a lot of data, costs quickly add up.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For a while I have been renting space at HostBrr (5 TB, approx. £8.83&#x2F;month) and iHostArt (7 TB, approx. £8.42&#x2F;month) — having two in order not to keep all my eggs in one basket! This has been fine as a medium-term &#x27;something is better than nothing&#x27; solution, but:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Although these are actually CHEAP providers, this is costing me roughly £207 per year for 2 offsites.&lt;&#x2F;li&gt;
&lt;li&gt;These providers feel a bit less professional than major providers, so without wanting to be mean, I am not sure how much confidence I have in relying on them.&lt;&#x2F;li&gt;
&lt;li&gt;My internet speed is poor, which makes backing up (especially the initial backup) awkward and if I ever need to do a full restore, that will also take a long time.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;bring-it-under-control-bring-it-in-house&quot;&gt;Bring it under control: Bring it in house&lt;a class=&quot;zola-anchor&quot; href=&quot;#bring-it-under-control-bring-it-in-house&quot; aria-label=&quot;Anchor link for: bring-it-under-control-bring-it-in-house&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;It occurred to me that buying my own hardware is probably the only way to cut the cost here.&lt;&#x2F;p&gt;
&lt;p&gt;Yet, because I want to keep the backups automatic, an online solution is needed — i.e. I need to be thinking of buying a server or two,
not just external hard drives that I could pretend I remember to plug in every so often.&lt;br &#x2F;&gt;
The whole point is having it off-site, so I&#x27;ll have to beg friends&#x2F;family to take in the servers. That then means they need to be &lt;strong&gt;compact&#x2F;unintrusive&lt;&#x2F;strong&gt; and &lt;strong&gt;not too electrically thirsty&lt;&#x2F;strong&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;For my first mini-server I&#x27;ve found a second-hand &lt;em&gt;Lenovo ThinkCentre M93p&lt;&#x2F;em&gt; on eBay — for a little under £50 (including power supply, bought separately — all the sellers of these things don&#x27;t seem to include the power supplies for whatever reason).&lt;&#x2F;p&gt;
&lt;p&gt;Size-wise, this fits the bill; it&#x27;s truly tiny as far as PCs go and it shouldn&#x27;t be too intrusive.
Specs-wise, this has an Intel i5-4590T, 8 GB of DDR3 RAM and a 240 GB Solid State Drive. This sounds ample.
The power supply is rated for 65 W and it has the rectangular connector that I see on some Lenovo laptops (kind of funny to see it on a desktop).&lt;&#x2F;p&gt;
&lt;figure class=&quot;figure_wrapper &quot; id=&quot;figure-m93p_overview&quot;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2024-07-03-m93p-offsite-backup-server&#x2F;m93p_collage.webp&quot; width=&quot;1739&quot; height=&quot;688&quot; alt=&quot;M93p Tiny Overview Photograph&quot;&gt;
  &lt;div class=&quot;figure_label&quot;&gt;
&lt;p&gt;&lt;a href=&quot;#figure-m93p_overview&quot; class=&quot;table_anchor&quot;&gt;Figure 1&lt;&#x2F;a&gt;: My M93p Tiny ThinkCentre and its power supply.&lt;br&gt;&lt;strong&gt;Front:&lt;&#x2F;strong&gt; 2 USB 3.0 ports, headphone port, microphone port, power button.&lt;br&gt;&lt;strong&gt;Back:&lt;&#x2F;strong&gt; Rectangular DC input, DisplayPort++, 3 USB 3.0 ports, VGA, another DisplayPort, Ethernet, antenna connector for WiFi antenna (not shown)&lt;&#x2F;p&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Since the Solid State Drive is just 240 GB and there isn&#x27;t space inside it for any big 3.5 inch hard drives,
I&#x27;ll use the SSD for the operating system and connect an external 8 TB USB 3.0 hard drive.&lt;br &#x2F;&gt;
I purchased this (&#x27;Seagate Backup Plus Hub&#x27;) for £130 from Currys in 2019; there might have been a deal going on at the time. I can find 8 TB for £144 these days, or 10 TB for £180. (Interestingly you can even get 4 TB NVMe SSDs for £200 these days...)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;power-characteristics&quot;&gt;Power characteristics&lt;a class=&quot;zola-anchor&quot; href=&quot;#power-characteristics&quot; aria-label=&quot;Anchor link for: power-characteristics&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Electricity is expensive in this country, so it seemed worth trying to figure out how much of other peoples&#x27; &lt;em&gt;leccy&lt;&#x2F;em&gt; I was going to end up spending.&lt;&#x2F;p&gt;
&lt;p&gt;I am using a cheap mains power meter — not laboratory-grade calibrated equipment, but it should be fine for an illustration.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;standalone&quot;&gt;Standalone&lt;a class=&quot;zola-anchor&quot; href=&quot;#standalone&quot; aria-label=&quot;Anchor link for: standalone&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;First I started with just the M93p PC — no external hard disk. I performed a few basic tests:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;how much power is used with the computer &lt;strong&gt;off&lt;&#x2F;strong&gt;?&lt;&#x2F;li&gt;
&lt;li&gt;how about when it&#x27;s &lt;strong&gt;suspended&lt;&#x2F;strong&gt;?&lt;&#x2F;li&gt;
&lt;li&gt;how about when it&#x27;s &lt;strong&gt;on&lt;&#x2F;strong&gt;, but not doing anything taxing (&lt;strong&gt;idle&lt;&#x2F;strong&gt;)?&lt;&#x2F;li&gt;
&lt;li&gt;how about when running single-threaded &lt;code&gt;sysbench cpu run&lt;&#x2F;code&gt;, using the powersave CPU governor (&lt;code&gt;cpupower frequency-set -g powersave&lt;&#x2F;code&gt;)?&lt;&#x2F;li&gt;
&lt;li&gt;how about when running single-threaded &lt;code&gt;sysbench cpu run&lt;&#x2F;code&gt;, using the ondemand CPU governor (&lt;code&gt;cpupower frequency-set -g ondemand&lt;&#x2F;code&gt;)?&lt;&#x2F;li&gt;
&lt;li&gt;how about when running 4-threaded &lt;code&gt;sysbench cpu run --threads=4&lt;&#x2F;code&gt;, using the powersave CPU governor (&lt;code&gt;cpupower frequency-set -g powersave&lt;&#x2F;code&gt;)?&lt;&#x2F;li&gt;
&lt;li&gt;how about when running 4-threaded &lt;code&gt;sysbench cpu run --threads=4&lt;&#x2F;code&gt;, using the ondemand CPU governor (&lt;code&gt;cpupower frequency-set -g ondemand&lt;&#x2F;code&gt;)?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;div class=&quot;table_wrapper auto-pure-tables pure-table-striped fullwidth&quot; id=&quot;table-power-standalone&quot;&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;Test&lt;&#x2F;th&gt;&lt;th&gt;Power consumption&lt;&#x2F;th&gt;&lt;th&gt;Relevant metric&lt;&#x2F;th&gt;&lt;th&gt;Comparison&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;Off&lt;&#x2F;td&gt;&lt;td&gt;0.3 W&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;Suspend&lt;&#x2F;td&gt;&lt;td&gt;1.2 W&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Compared to Off:&lt;&#x2F;strong&gt; +0.9 W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;On&#x2F;Idle&lt;&#x2F;td&gt;&lt;td&gt;9.7 W&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Compared to Off:&lt;&#x2F;strong&gt; +9.4 W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;cpu sysbench&lt;br&gt;(1 thread, powersave)&lt;&#x2F;td&gt;&lt;td&gt;11.5 W&lt;&#x2F;td&gt;&lt;td&gt;240 events&#x2F;s&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Compared to Idle:&lt;&#x2F;strong&gt; +1.8 W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;cpu sysbench&lt;br&gt;(1 thread, ondemand)&lt;&#x2F;td&gt;&lt;td&gt;19.8 W&lt;&#x2F;td&gt;&lt;td&gt;892 events&#x2F;s&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Compared to Idle:&lt;&#x2F;strong&gt; +10.1 W&lt;br&gt;&lt;strong&gt;Compared to 1 thread, powersave:&lt;&#x2F;strong&gt; +72% power, +271% performance&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;cpu sysbench&lt;br&gt;(4 threads; powersave)&lt;&#x2F;td&gt;&lt;td&gt;14.1 W&lt;&#x2F;td&gt;&lt;td&gt;958 events&#x2F;s&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Compared to 1 thread, powersave:&lt;&#x2F;strong&gt; +2.6 W (+23% power), +299% performance&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;cpu sysbench&lt;br&gt;(4 threads; ondemand)&lt;&#x2F;td&gt;&lt;td&gt;29.4 W&lt;&#x2F;td&gt;&lt;td&gt;3119 events&#x2F;s&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Compared to 1 thread, powersave:&lt;&#x2F;strong&gt; +17.9 W (+156% power), +1100% performance&lt;br&gt;&lt;strong&gt;Compared to 1 thread, ondemand:&lt;&#x2F;strong&gt; +9.6 W (+48% power), +250% performance&lt;br&gt;&lt;strong&gt;Compared to 4 threads, powersave:&lt;&#x2F;strong&gt; +15.3 W (+109% power), +226% performance&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
  &lt;div class=&quot;table_label&quot;&gt;
&lt;p&gt;&lt;a href=&quot;#table-power-standalone&quot; class=&quot;table_anchor&quot;&gt;Table 1&lt;&#x2F;a&gt;: Power consumption measurements (no external hard disk connected)&lt;&#x2F;p&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;I suspect that sysbench is not a great substitute for a domain-specific benchmark, but
at the moment this is the best I can easily do — just don&#x27;t read too much into the performance upticks.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;disk-connected&quot;&gt;Disk Connected&lt;a class=&quot;zola-anchor&quot; href=&quot;#disk-connected&quot; aria-label=&quot;Anchor link for: disk-connected&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I then connected the external hard drive, including its own mains-fed power supply (output rating: 12 V @ 3 A).&lt;br &#x2F;&gt;
The measurements are for both the PC itself and the external hard drive&#x27;s power supply.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I measured the power consumption with the PC off and with the disk connected (both power supplies for the PC and the disk were connected to the mains and under measurement). The disk isn&#x27;t spinning at this point.&lt;&#x2F;li&gt;
&lt;li&gt;I booted the PC and instructed the disk to spin down with &lt;code&gt;hd-idle -t &#x2F;dev&#x2F;sdb&lt;&#x2F;code&gt; and measured the power consumption of the whole system.&lt;&#x2F;li&gt;
&lt;li&gt;I then spun up the disk but otherwise left the machine idle. I measured again.&lt;&#x2F;li&gt;
&lt;li&gt;I then started a full sequential read of the disk with &lt;code&gt;dd if=&#x2F;dev&#x2F;sdb of=&#x2F;dev&#x2F;null bs=1M&lt;&#x2F;code&gt; and measured once more. Although the CPU won&#x27;t technically be idle in this situation, I assume for the sake of simplicity that it also won&#x27;t be loaded enough to matter.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;div class=&quot;table_wrapper auto-pure-tables pure-table-striped fullwidth&quot; id=&quot;table-power-disk-connected&quot;&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;Test&lt;&#x2F;th&gt;&lt;th&gt;Power consumption&lt;&#x2F;th&gt;&lt;th&gt;Relevant metric&lt;&#x2F;th&gt;&lt;th&gt;Comparison&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;Off&lt;&#x2F;td&gt;&lt;td&gt;1.0 W&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Compared to Off (Standalone):&lt;&#x2F;strong&gt; +0.7 W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;On&#x2F;Idle, Disk Spun down&lt;&#x2F;td&gt;&lt;td&gt;11.2 W&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Compared to On&#x2F;Idle (Standalone):&lt;&#x2F;strong&gt; +1.5 W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;On&#x2F;Idle, Disk Spinning&lt;&#x2F;td&gt;&lt;td&gt;16.4 W&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Compared to On&#x2F;Idle (Standalone):&lt;&#x2F;strong&gt; +6.7 W&lt;br&gt;&lt;strong&gt;Compared to Spun down:&lt;&#x2F;strong&gt; +5.2 W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;Sequential Disk Read&lt;&#x2F;td&gt;&lt;td&gt;21.4 W&lt;&#x2F;td&gt;&lt;td&gt;190 MB&#x2F;s read speed,&lt;br&gt;CPU in powersave&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Compared to Idle&#x2F;Spun down:&lt;&#x2F;strong&gt; +10.2 W&lt;br&gt;&lt;strong&gt;Compared to Idle&#x2F;Spinning:&lt;&#x2F;strong&gt; +5.0 W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
  &lt;div class=&quot;table_label&quot;&gt;
&lt;p&gt;&lt;a href=&quot;#table-power-disk-connected&quot; class=&quot;table_anchor&quot;&gt;Table 2&lt;&#x2F;a&gt;: Power consumption measurements (with external hard disk connected)&lt;&#x2F;p&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;I didn&#x27;t want to disturb the data on the disk so I didn&#x27;t perform a write test. However, I hope these numbers are illustrative enough anyway.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;rough-calculations-for-my-expected-usage-pattern&quot;&gt;Rough calculations for my expected usage pattern&lt;a class=&quot;zola-anchor&quot; href=&quot;#rough-calculations-for-my-expected-usage-pattern&quot; aria-label=&quot;Anchor link for: rough-calculations-for-my-expected-usage-pattern&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I expect my usage pattern to include, each day, 23 hours in the Off state and (up to) 1 hour receiving a backup over the network.&lt;&#x2F;p&gt;
&lt;p&gt;In the absence of measurements in real-world conditions, let&#x27;s build a somewhat pessimistic estimate:&lt;&#x2F;p&gt;
&lt;p&gt;During the active hour, disk activity will be the main type of load, perhaps with some CPU intensive cryptography to handle the encrypted TLS or SSH connection. Let&#x27;s take the 21.4 W reading from the Sequential Disk Read test (as my closest illustration of disk write) and add 10.1 W, which is the difference between Idle and the single-threaded ondemand cpu sysbench (my best pessimistic guess for an illustration of the CPU use&#x2F;cryptographic operations). This gives us 31.5 W total.&lt;&#x2F;p&gt;
&lt;p&gt;During the 23 other hours, the Off (with Disk Connected) reading for 1.0 W applies.&lt;&#x2F;p&gt;
&lt;p&gt;Approx daily consumption is therefore: 44.5 W·h (Watt-hour). &lt;br &#x2F;&gt;
Approx annual consumption is therefore: 16.3 kW·h (kiloWatt-hour)&lt;&#x2F;p&gt;
&lt;p&gt;At a rate of 22.36p&#x2F;kW·h (including VAT, taken from a &lt;a href=&quot;https:&#x2F;&#x2F;www.moneysavingexpert.com&#x2F;news&#x2F;2024&#x2F;05&#x2F;martin-lewis--energy-price-cap-to-fall-in-july&#x2F;&quot;&gt;MoneySavingExpert article about the July 2024 price cap&lt;&#x2F;a&gt; — thankfully less than the last rate I remember), this gives an annual electricity cost of about &lt;strong&gt;£3.64&lt;&#x2F;strong&gt;. I may have been working with rough numbers but I feel happy enough calling it £5 for the sake of simplicity.
It shouldn&#x27;t break the bank to bribe someone that much to adopt one of these servers into their home :-).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-bottom-line-does-this-make-sense-to-do&quot;&gt;The bottom line — does this make sense to do?&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-bottom-line-does-this-make-sense-to-do&quot; aria-label=&quot;Anchor link for: the-bottom-line-does-this-make-sense-to-do&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;It&#x27;s hard to put an exact figure on the cost of one of these systems, because hard drives fail, usually unpredictably. &lt;br &#x2F;&gt;
A pessimistic take could be that we should expect to replace the disk every 2 years since the warranty is 2 years long. &lt;br &#x2F;&gt;
On the other hand, I would have higher hopes and expectations for the M93p mini PC itself — say 5 to 10 years.&lt;&#x2F;p&gt;
&lt;div class=&quot;table_wrapper auto-pure-tables pure-table-striped &quot; id=&quot;table-cost&quot;&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;What&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: left&quot;&gt;Cost&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Expected Annual Cost&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;Replacement of 8TB HDD every warranty period&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;£144 every 2 years&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;£72 per year&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;Replacement of M93p Mini PC every 5 years&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;£50 every 5 years&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;£10 per year&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;Electricity&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;£5 per year&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;hr&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;hr&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;hr&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;Total&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: left&quot;&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;u&gt;£87 per year&lt;&#x2F;u&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
  &lt;div class=&quot;table_label&quot;&gt;
&lt;p&gt;&lt;a href=&quot;#table-cost&quot; class=&quot;table_anchor&quot;&gt;Table 3&lt;&#x2F;a&gt;: Sum of expected costs for this 8 TB M93p system&lt;&#x2F;p&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Let&#x27;s have a quick comparison against some of the other providers.&lt;&#x2F;p&gt;
&lt;div class=&quot;table_wrapper auto-pure-tables pure-table-striped fullwidth&quot; id=&quot;table-cost-comparison-with-other-services&quot;&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Storage System&#x2F;Service&lt;&#x2F;th&gt;&lt;th&gt;Annual Cost&lt;&#x2F;th&gt;&lt;th&gt;Annual Cost per TB&lt;&#x2F;th&gt;&lt;th&gt;Note&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;This 8 TB M93p System&lt;&#x2F;td&gt;&lt;td&gt;£87 [expected]&lt;&#x2F;td&gt;&lt;td&gt;£10.89 [expected]&lt;&#x2F;td&gt;&lt;td&gt;Cost will vary depending on luck&#x2F;hardware failures.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;iHostArt 7 TB Storage VPS&lt;&#x2F;td&gt;&lt;td&gt;£101&lt;&#x2F;td&gt;&lt;td&gt;£14.42&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Backblaze S3-compatible (US$6&#x2F;TB·month)&lt;&#x2F;td&gt;&lt;td&gt;varies based on usage&lt;&#x2F;td&gt;&lt;td&gt;£56.76&lt;&#x2F;td&gt;&lt;td&gt;Unlike a fixed-disk system, you don&#x27;t pay for space you don&#x27;t use!&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Wasabi S3-compatible (US$6.99&#x2F;TB·month)&lt;&#x2F;td&gt;&lt;td&gt;varies based on usage&lt;&#x2F;td&gt;&lt;td&gt;£66.12&lt;&#x2F;td&gt;&lt;td&gt;Unlike a fixed-disk system, you don&#x27;t pay for space you don&#x27;t use!&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Amazon S3 (US$0.023&#x2F;&lt;strong&gt;GB&lt;&#x2F;strong&gt;·month)&lt;&#x2F;td&gt;&lt;td&gt;varies based on usage&lt;&#x2F;td&gt;&lt;td&gt;£260.98&lt;&#x2F;td&gt;&lt;td&gt;Unlike a fixed-disk system, you don&#x27;t pay for space you don&#x27;t use! &lt;strong&gt;Price inclusive of VAT. Minimum 90 days of storage per object. Upload and download requests are chargeable. Seems like roughly £85.10 to actually transfer a TB back to your computer over the Internet.&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Amazon S3 &lt;em&gt;Glacier Deep Archive&lt;&#x2F;em&gt; (US$0.00099&#x2F;&lt;strong&gt;GB&lt;&#x2F;strong&gt;·month)&lt;&#x2F;td&gt;&lt;td&gt;varies based on usage&lt;&#x2F;td&gt;&lt;td&gt;£11.23&lt;&#x2F;td&gt;&lt;td&gt;Unlike a fixed-disk system, you don&#x27;t pay for space you don&#x27;t use! &lt;strong&gt;Price inclusive of VAT. 12 hour restoration time. Minimum 90 days of storage per object. Upload and download requests are chargeable. Seems like roughly £2.36 to retrieve a TB in &#x27;Bulk&#x27; mode, plus £85.10 to actually transfer it back to your computer over the Internet.&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
  &lt;div class=&quot;table_label&quot;&gt;
&lt;p&gt;&lt;a href=&quot;#table-cost-comparison-with-other-services&quot; class=&quot;table_anchor&quot;&gt;Table 4&lt;&#x2F;a&gt;: Cost comparison of this 8 TB M93p system against commercial services&lt;&#x2F;p&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Under this model, there isn&#x27;t that much difference between this and renting space from iHostArt (mentioned in the intro — 7 TB not 8 TB, but close enough) which is £101 per year.
It mostly comes down to which system you trust more, or whether you&#x27;re feeling lucky at dodging disk deaths.
I suspect the self-hosted system will win with larger storage volumes though; for example 16 TB disks (e.g. Seagate Exos) are readily available for less than twice the price of the 8 TB disks, you just need to find a caddy to use it as an external drive.&lt;&#x2F;p&gt;
&lt;p&gt;Compared to the bigger name commercial providers, there is quite a drastic difference.&lt;br &#x2F;&gt;
Amazon&#x27;s S3 &#x27;Glacier Deep Archive&#x27; service is the only big name I know of that appears to come close — it has a 12 hour retrieval time but almost matches my system on expected cost.&lt;br &#x2F;&gt;
That is, unless you actually want to read your data out, at which point you can expect to pay £87 per terabyte(!) to pull it out of Amazon&#x27;s deathgrip — mostly due to Amazon&#x27;s standard egress fees rather than the actual cost of retrieval from the archive. &lt;br &#x2F;&gt;
For a backup of last resort which you hope to never need to restore from, I am sure it could make sense for some, but it&#x27;s not for me.
I admittedly can&#x27;t be sure I haven&#x27;t missed some more hidden fees buried in fine print. The £85.10 egress fee was pretty non-obvious to me until I looked at online comments talking about it, only the £2.36 fee (already costly enough) was discoverable to me as a non-Amazon specialist.&lt;&#x2F;p&gt;
&lt;p&gt;Overall, I think I&#x27;m happy with the theory of my system and I&#x27;m willing to go ahead with it. What will be particularly useful to me is the ability to have physical access to the drive from time to time, meaning I can bypass stuffing my backups or restores through my narrow Internet pipe and can do bulk loads much quicker.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;scribbles&quot;&gt;Scribbles&lt;a class=&quot;zola-anchor&quot; href=&quot;#scribbles&quot; aria-label=&quot;Anchor link for: scribbles&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Some of this information may be useful to me or others for reference.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;problem-with-suspend&quot;&gt;Problem with suspend&lt;a class=&quot;zola-anchor&quot; href=&quot;#problem-with-suspend&quot; aria-label=&quot;Anchor link for: problem-with-suspend&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I was having trouble with suspending the M93p ThinkCentre: it would reboot instead of resuming from suspend.&lt;&#x2F;p&gt;
&lt;p&gt;The solution that worked for me was updating the BIOS. Without Windows installed, the option that worked for me was to do this within FreeDOS.
I can&#x27;t remember anymore if I tried the CD image on a USB stick or not&lt;&#x2F;p&gt;
&lt;p&gt;You can get the BIOS upgrade files from
&lt;a href=&quot;https:&#x2F;&#x2F;pcsupport.lenovo.com&#x2F;us&#x2F;en&#x2F;products&#x2F;desktops-and-all-in-ones&#x2F;thinkcentre-m-series-desktops&#x2F;thinkcentre-m93p&#x2F;10ab&#x2F;10ab003cuk&#x2F;pb03c2at&#x2F;downloads&#x2F;ds035753-flash-bios-update-thinkcentre-e93-m73p-m83-m93-m93p-thinkstation-e32-p300&quot;&gt;Lenovo&#x27;s site&lt;&#x2F;a&gt;
(&lt;a href=&quot;https:&#x2F;&#x2F;download.lenovo.com&#x2F;pccbbs&#x2F;thinkcentre_bios&#x2F;fbjte0usa.zip&quot;&gt;direct .zip file&lt;&#x2F;a&gt;,
&lt;a href=&quot;https:&#x2F;&#x2F;download.lenovo.com&#x2F;pccbbs&#x2F;thinkcentre_bios&#x2F;fbjte0usa.txt&quot;&gt;direct instructions .txt&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;The DOS one is called &#x27;Flash UEFI BIOS update – EFI Shell utility&#x27;.
From the name it sounds like it is supposed to be used from the EFI Shell but I am not sure how to get to the EFI Shell on this machine...
The readme calls it a DOS package though and it worked for me, so you can overlook that.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Install FreeDOS on a USB stick (I put it on my Ventoy USB stick)&lt;&#x2F;li&gt;
&lt;li&gt;Put the BIOS files on another USB stick&lt;&#x2F;li&gt;
&lt;li&gt;Plug in the BIOS files USB stick before booting FreeDOS. This is needed because the BIOS &#x27;mounts&#x27; the flash drive first and then hands it over to FreeDOS.&lt;br &#x2F;&gt;
In the short time I used FreeDOS, I did not figure out how to mount the USB stick within FreeDOS but in either case it is more effort, so don&#x27;t bother.&lt;&#x2F;li&gt;
&lt;li&gt;Switch to the drive with &lt;code&gt;D:&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;flash2 IMAGEFB.ROM&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I noticed there are other fun options like being able to edit the serial number, machine make &amp;amp; model, boot logo, etc but I didn&#x27;t bother with that.
I have read online since doing this that on some other Lenovo models, setting the serial number and machine model to &#x27;INVALID&#x27; will bypass some boot-time restrictions on which PCIe network cards you&#x27;re allowed to use,
but I suggest you search around for someone else&#x27;s blog post where they tried that if you want more information.
Other than that, there seems to be an option to clear the BIOS settings: &lt;code&gt;&#x2F;clr&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;After the BIOS upgrade was complete, it then rebooted itself... And happily resume now worked!!!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;bios-settings-to-set&quot;&gt;BIOS Settings to set&lt;a class=&quot;zola-anchor&quot; href=&quot;#bios-settings-to-set&quot; aria-label=&quot;Anchor link for: bios-settings-to-set&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Settings in bold are the ones I consider most important.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Advanced
&lt;ul&gt;
&lt;li&gt;Intel(R) Manageability Control → DISABLED (I don&#x27;t need&#x2F;want IME stuff as far as I can avoid it)&lt;&#x2F;li&gt;
&lt;li&gt;Intel(R) SIPP Support (Stable Image Platform Program) → DISABLED (I don&#x27;t want to present an old CPU&#x2F;Chipset revision ID to the operating system, that just sounds needlessly self-misleading. Maybe it makes sense on some other OSes but I trust Linux to know how to use revision IDs best.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Power
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;After Power Loss → Power On&lt;&#x2F;strong&gt; (Otherwise a power cut will knock my system offline and need manual intervention)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Enhanced Power Saving Mode → Enabled&lt;&#x2F;strong&gt; (Says it will reduce power use in the off state, at the cost of disabling any wakeup source other than the RTC Alarm. Great, that&#x27;s actually exactly what I want, particularly since it knocks off the IME in the Off state)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Security
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secure Boot → Disabled&lt;&#x2F;strong&gt; (I don&#x27;t care to have life made hard by Secure Boot; it&#x27;s mostly only there for Windows 8 Certification anyway)&lt;&#x2F;li&gt;
&lt;li&gt;TCG Security Feature → Disabled (I don&#x27;t think I want anything to do with the Trusted Platform Module; nothing I do uses it, may as well disable)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Startup
&lt;ul&gt;
&lt;li&gt;CSM → don&#x27;t care, can disable for all I want. But having it enabled is useful for booting FreeDOS etc.&lt;&#x2F;li&gt;
&lt;li&gt;Boot Priority → UEFI First&lt;&#x2F;li&gt;
&lt;li&gt;Quick Boot → Disabled (I like the splash screen because it helps you to know what buttons to push)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Keyboardless Operation → Enabled&lt;&#x2F;strong&gt; (Otherwise using it as a server will be troublesome; without switching this on, it will trigger the Error boot sequence instead when there&#x27;s no keyboard)&lt;&#x2F;li&gt;
&lt;li&gt;Startup Device Menu Prompt → Enabled&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The button to get the menu on boot is &lt;em&gt;Enter&lt;&#x2F;em&gt;, but the menu has a self-destructing timer that is paused&#x2F;resumed by the Enter key as well, so if you spam it blindly you will just keep
resuming the timer and eventually it will continue the boot sequence anyway.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;light-teardown-and-cmos-memory-reset&quot;&gt;Light Teardown and &#x27;CMOS memory&#x27; reset&lt;a class=&quot;zola-anchor&quot; href=&quot;#light-teardown-and-cmos-memory-reset&quot; aria-label=&quot;Anchor link for: light-teardown-and-cmos-memory-reset&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;As part of my suspend troubleshooting, I opened the machine and reset the &#x27;CMOS memory&#x27;, the battery-backed volatile RAM which stores the time for the real-time clock and BIOS settings.&lt;&#x2F;p&gt;
&lt;p&gt;Note this did not solve my issue, but it&#x27;s always nice to have a look inside anyway.&lt;&#x2F;p&gt;
&lt;p&gt;The case was pretty elegant, needing just one screw to come off before you could slide the top case off at an angle (this is all described in the manual).&lt;&#x2F;p&gt;
&lt;figure class=&quot;figure_wrapper &quot; id=&quot;figure-light_teardown_overview&quot;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2024-07-03-m93p-offsite-backup-server&#x2F;m93p_light_teardown_overview.webp&quot; width=&quot;1103&quot; height=&quot;925&quot; alt=&quot;M93p ThinkCentre with top of outer casing removed&quot;&gt;
  &lt;div class=&quot;figure_label&quot;&gt;
&lt;p&gt;&lt;a href=&quot;#figure-light_teardown_overview&quot; class=&quot;table_anchor&quot;&gt;Figure 2&lt;&#x2F;a&gt;: M93p ThinkCentre with top of outer casing removed. The jumpers mentioned next are at the bottom-right.&lt;&#x2F;p&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;The jumper header for resetting the &#x27;CMOS memory&#x27; was pretty fiddly as it was recessed and I had to use some fine tweezers to pull it out (being careful not to drop it...) and they would just slip against it most of the time.&lt;&#x2F;p&gt;
&lt;figure class=&quot;figure_wrapper &quot; id=&quot;figure-jumpers&quot;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2024-07-03-m93p-offsite-backup-server&#x2F;m93p_reset_jumper.webp&quot; width=&quot;1000&quot; height=&quot;499&quot; alt=&quot;Pin headers with jumper set to the reset position&quot;&gt;
  &lt;div class=&quot;figure_label&quot;&gt;
&lt;p&gt;&lt;a href=&quot;#figure-jumpers&quot; class=&quot;table_anchor&quot;&gt;Figure 3&lt;&#x2F;a&gt;: Pin headers with the jumper moved to the reset position.&lt;&#x2F;p&gt;
  &lt;&#x2F;div&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;The manual has the full procedure for this, but you just boot with the jumper in the reset position and wait until it beeps multiple times.
Then you turn the power off and move the jumper back.&lt;&#x2F;p&gt;
</content>
	</entry>
	
	<entry xml:lang="en">
		<title>My First Ludum Dare Game: Olive Harvest in Ludum Dare 52</title>
		<published>2023-01-16T00:00:00+00:00</published>
		<updated>2023-01-16T00:00:00+00:00</updated>
		<link rel="alternate" href="https://o.librepush.net/blog/2023-01-16-my-first-ludum-dare-52-olive-harvest/" type="text/html"/>
		<id>https://o.librepush.net/blog/2023-01-16-my-first-ludum-dare-52-olive-harvest/</id>
		<content type="html">&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2023-01-16-my-first-ludum-dare-52-olive-harvest&#x2F;#play-olive-harvest&quot;&gt;skip to play game&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;a class=&quot;zola-anchor&quot; href=&quot;#introduction&quot; aria-label=&quot;Anchor link for: introduction&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The weekend before last (6th–8th January 2023), I participated in Ludum Dare 52. If you haven&#x27;t come across it before, Ludum Dare is a competition in which participants write a game in a short amount of time — 48 or 72 hours (depending on entry type). You might have heard of this format being called a &#x27;game jam&#x27;.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s no prize for &#x27;winning&#x27; but of course you get to keep your game at the end of it and the experience itself has been quite fun for me.&lt;&#x2F;p&gt;
&lt;p&gt;Ludum Dare has multiple different types of entry:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&#x27;Compo&#x27;: solo developer, 48 hours, must release source code, have to make all your assets yourself&lt;&#x2F;li&gt;
&lt;li&gt;&#x27;Jam&#x27;: teams allowed, 72 hours, can use assets from elsewhere (within legal constraints) but must opt out of ranking categories if you didn&#x27;t make them yourself&lt;&#x2F;li&gt;
&lt;li&gt;&#x27;Extra&#x27;: much more relaxed with much more time (3 weeks) — I&#x27;m not sure of the details since it doesn&#x27;t really interest me, but seems like you don&#x27;t get a score at the end. &lt;em&gt;I would say that in general, the relaxed time frame is probably a negative for me, but it may appeal to some.&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This was my first time taking part in a Ludum Dare — the closest thing being a game jam at University but it was outrageously short (8 hours, if memory serves me correctly) and I had an idea that was just &lt;em&gt;way too cool and ambitious :&#x2F;&lt;&#x2F;em&gt;...&lt;&#x2F;p&gt;
&lt;p&gt;Since it was my first time (and I couldn&#x27;t arrange for any friends to join me in time), I decided to treat it as a practice run and I decided to make an entry for the &#x27;Jam&#x27; because of the 72 hour time frame. I took it a bit casually — I didn&#x27;t skip any sleep or anything like you might expect from a timed event like this.&lt;&#x2F;p&gt;
&lt;p&gt;In retrospect, I wish I had aimed for a &#x27;Compo&#x27; entry instead and taken it a bit more seriously.
The event started on Friday night in my timezone and ended on Sunday night (Compo)&#x2F;Monday night (Jam).
Since I had work on Monday, I didn&#x27;t end up getting that much extra time anyway! Not only that, but I created all the assets myself since, to me, that&#x27;s part of the fun :).&lt;&#x2F;p&gt;
&lt;p&gt;My intention was to do future rounds with friends. I would still like to do that if the chance arises next time, but if I can&#x27;t arrange anything with friends, I won&#x27;t be afraid to have a crack at a &#x27;Compo&#x27; entry :-).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ideas&quot;&gt;Ideas&lt;a class=&quot;zola-anchor&quot; href=&quot;#ideas&quot; aria-label=&quot;Anchor link for: ideas&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Every Ludum Dare has a theme, which is voted on by interested participants in the week or two leading up to the event and then announced at the start of the event. This Ludum Dare&#x27;s theme was &#x27;&lt;strong&gt;Harvest&lt;&#x2F;strong&gt;&#x27;.&lt;&#x2F;p&gt;
&lt;p&gt;When the clock struck eight and Ludum Dare started, I was somewhat worried; I couldn&#x27;t quickly think of an interesting idea. Games like Stardew Valley and Harvest Moon popped into mind but they are obviously too complex to make in a game jam (and besides, I wanted to aim for something original).
I wanted an idea for a game that wasn&#x27;t trivially basic (gameplay-wise), was fun and yet was going to be achievable in the short time (knowing full well that this event was going to flash by). Maybe you call this &#x27;simple but good&#x27; and I don&#x27;t know about you, but I find it hard to come up with ideas in that category :-).&lt;&#x2F;p&gt;
&lt;p&gt;I eventually narrowed down into a game about farming olives and processing them into olive oil, whilst under attack by forces of evil (slugs, mould, thieves, ...?). I thought it&#x27;d be interesting to put some &lt;em&gt;tower defence&lt;&#x2F;em&gt; aspects in there — I always liked games that made you think about &#x27;buying&#x27; structures and thinking of where to place them to defend you from the forces of evil. (I never actually got around to any tower defence aspects in the game but it was in the plans.)&lt;&#x2F;p&gt;
&lt;p&gt;In a sense, I was lucky, because the flow of ideas started and I soon had a handful of ideas that, together, sounded like a game I wanted to play and show to others. I was perhaps even luckier that these ideas were flexible enough to let me pivot away from the overcomplicated &#x27;tower defence&#x27; aspects into a less complicated idea that meant I could still package up a game at the end of the event.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;thoughts-on-the-godot-game-engine&quot;&gt;Thoughts on the Godot game engine&lt;a class=&quot;zola-anchor&quot; href=&quot;#thoughts-on-the-godot-game-engine&quot; aria-label=&quot;Anchor link for: thoughts-on-the-godot-game-engine&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;It was time to get cracking. The excellent open-source game engine &lt;a href=&quot;https:&#x2F;&#x2F;godotengine.org&#x2F;&quot;&gt;&lt;em&gt;Godot&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; was my tool of choice. I had already messed around with it a few years ago (for my previous &#x2F; failed game jam at University) so I had a tiny amount of familiarity, but otherwise I&#x27;d be coming at this with fairly fresh eyes. I should have learnt beforehand but I didn&#x27;t — I only skimmed documentation on the job.&lt;&#x2F;p&gt;
&lt;p&gt;Godot is very intuitive once you know that it&#x27;s &#x27;&lt;em&gt;Scenes&lt;&#x2F;em&gt;&#x27; all the way down. Its engine-specific scripting language, GDScript, can feel a bit wonky at times but it&#x27;s also very fit for purpose — as long as you don&#x27;t fall into the trap of thinking that it&#x27;s Python despite its syntactic similarity. I can see it being useful to drop down into a different language at times (C#, C++ are supported but others are available, including Rust), but GDScript was perfectly adequate for me and I&#x27;d probably advise against getting too complicated in a short event like this.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;godot-traps-and-tricks&quot;&gt;Godot Traps and Tricks&lt;a class=&quot;zola-anchor&quot; href=&quot;#godot-traps-and-tricks&quot; aria-label=&quot;Anchor link for: godot-traps-and-tricks&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Basically every tool has quirks and a good craftsperson would do well to become aware of them. Here are a few traps I fell into and a few tricks I learnt about.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;yield-and-scenetree-timers-combined-seem-dangerous&quot;&gt;&lt;code&gt;yield&lt;&#x2F;code&gt; and SceneTree timers combined seem dangerous&lt;a class=&quot;zola-anchor&quot; href=&quot;#yield-and-scenetree-timers-combined-seem-dangerous&quot; aria-label=&quot;Anchor link for: yield-and-scenetree-timers-combined-seem-dangerous&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;At some point if you dig deeply enough, you&#x27;ll run into GDScript&#x27;s &lt;code&gt;yield&lt;&#x2F;code&gt; keyword which lets you defer execution until another event triggers.&lt;&#x2F;p&gt;
&lt;p&gt;You might be tempted, as I was, to use this sort of snippet for inserting a sleep into your code to make something happen later:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;GDScript&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-GDScript &quot;&gt;&lt;code class=&quot;language-GDScript&quot; data-lang=&quot;GDScript&quot;&gt;&lt;span&gt;yield(get_tree().create_timer(5.0), &amp;quot;timeout&amp;quot;)  # wait 5 seconds before next event
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This &lt;em&gt;works&lt;&#x2F;em&gt;, but it turns out that if the current object&#x2F;Scene gets removed from the world during the sleep, the resumption of the script will lead to an error: &lt;code&gt;Resumed function after yield, but class instance is gone.&lt;&#x2F;code&gt;. I didn&#x27;t actually notice any ill effects from this — just an error in the console and everything else continued as usual, but it&#x27;s poor to have errors showing up when things are working correctly and I can&#x27;t guarantee that this wouldn&#x27;t lead to some footguns in other situations either.&lt;&#x2F;p&gt;
&lt;p&gt;The slightly unfortunate workaround for this is to use an explicit &lt;code&gt;Timer&lt;&#x2F;code&gt; node as a child of your &lt;code&gt;Scene&lt;&#x2F;code&gt;. If necessary, this means creating a Timer and &lt;strong&gt;remembering to remove it afterwards&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;GDScript&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-GDScript &quot;&gt;&lt;code class=&quot;language-GDScript&quot; data-lang=&quot;GDScript&quot;&gt;&lt;span&gt;var timer = Timer.new()
&lt;&#x2F;span&gt;&lt;span&gt;timer.one_shot = true
&lt;&#x2F;span&gt;&lt;span&gt;$Timer.add_child(timer)
&lt;&#x2F;span&gt;&lt;span&gt;timer.start(time); yield(timer, &amp;quot;timeout&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;$Timer.remove_child(timer)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s safe to use &lt;code&gt;yield&lt;&#x2F;code&gt; on this timer node because once the current &lt;code&gt;Scene&lt;&#x2F;code&gt; is destroyed, the &lt;code&gt;Timer&lt;&#x2F;code&gt; will also be destroyed &lt;strong&gt;as it is a child&lt;&#x2F;strong&gt; and the script will never be resumed as a result.
Note: a &lt;code&gt;Timer&lt;&#x2F;code&gt; can&#x27;t have multiple timeouts, so you need one per task... I&#x27;d be tempted to wrap this up in a neater package if I was writing a proper game and needed to make use of this, but for the purposes of the game jam I took the lazy way out.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;godotengine.org&#x2F;qa&#x2F;76800&#x2F;resumed-function-after-yield-but-class-instance-is-gone?show=113008#a113008&quot;&gt;Source for this trick on the Godot Engine Q&amp;amp;A site — thanks k2kra!&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h4 id=&quot;z-ordering-is-hard-at-least-in-a-game-from-this-2d-perspective&quot;&gt;z-ordering is hard, at least in a game from this 2D perspective&lt;a class=&quot;zola-anchor&quot; href=&quot;#z-ordering-is-hard-at-least-in-a-game-from-this-2d-perspective&quot; aria-label=&quot;Anchor link for: z-ordering-is-hard-at-least-in-a-game-from-this-2d-perspective&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2023-01-16-my-first-ludum-dare-52-olive-harvest&#x2F;level0b.png&quot; alt=&quot;A screenshot from Olive Harvest demonstrating the &amp;#39;top-down oblique&amp;#39; view&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;My game is 2D but set with a perspective that I don&#x27;t know the term for, but the best I&#x27;ve found is &#x27;top-down oblique&#x27;.
It&#x27;s as though you have an orthographic camera (so no vanishing point and no concept of things getting smaller as they get further away) angled so you can see the top and front of things.
It&#x27;s convenient for drawing because you can draw terrain as thought it was top-down, characters as though you are looking at them from the front and structures as a bit of both — and everything will look fairly reasonable. Structures can be built out of reusable tiles (in Godot: use a &lt;code&gt;TileMap&lt;&#x2F;code&gt;!) which greatly cuts down on time needed to produce levels.&lt;&#x2F;p&gt;
&lt;p&gt;A slight problem emerges because characters can walk in front of and behind other characters and objects in the world. Despite thinking you had a 2D game, you now need to worry about ensuring that everything renders in the right order so things that are logically in front appear in front.&lt;&#x2F;p&gt;
&lt;p&gt;I never got this exactly right during the event itself, but I at least know some tricks.&lt;&#x2F;p&gt;
&lt;p&gt;The biggest trick: use the &lt;a href=&quot;https:&#x2F;&#x2F;docs.godotengine.org&#x2F;en&#x2F;stable&#x2F;classes&#x2F;class_ysort.html&quot;&gt;&lt;code&gt;YSort&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; node. &lt;a href=&quot;https:&#x2F;&#x2F;docs.godotengine.org&#x2F;en&#x2F;stable&#x2F;classes&#x2F;class_ysort.html&quot;&gt;&lt;code&gt;YSort&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; sits in your scene tree and re-orders its children depending on their Y-position. For example, when the player walks in front (below) of a tree, they show up in front, but when they walk above, they show up behind the tree. &lt;br &#x2F;&gt;
&lt;strong&gt;For this to work properly, you absolutely must make sure the origin point of your child scenes&#x2F;entities matches where you want the ordering to be decided!&lt;&#x2F;strong&gt; Examples that would seem typically correct would be to put the origin at a character&#x27;s feet, or at the base of a tree, or the base of a wall. Essentially: wherever the entity touches the ground.&lt;&#x2F;p&gt;
&lt;p&gt;In my case, I started off my game with little regard for the origin positions of objects, using explicit z-indices to solve all my ordering problems and designing levels so that you never noticed any discrepancies.
Eventually, I ran into a case I couldn&#x27;t design away and learnt about &lt;a href=&quot;https:&#x2F;&#x2F;docs.godotengine.org&#x2F;en&#x2F;stable&#x2F;classes&#x2F;class_ysort.html&quot;&gt;&lt;code&gt;YSort&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, but then it was difficult to rework all my objects and I released the game with some broken Z behaviour (switching to &lt;code&gt;YSort&lt;&#x2F;code&gt; fixed some of my issues but broke at least one case I worked around manually beforehand).&lt;&#x2F;p&gt;
&lt;p&gt;An especially tricky case, present in my game, is that of objects which can have other objects walk or sit inside: for example, the player can walk in the back of the postal van&#x2F;truck and the olives can sit inside the wheelbarrow. It&#x27;s easy enough to split the front and back of these container objects into different sprites (if you drew them with layers in Krita, you can just export different layers&#x2F;layergroups), but z-ordering them is tricky.&lt;&#x2F;p&gt;
&lt;p&gt;I still need to research the proper way to support these &#x27;container&#x27; objects, particularly the ones in the world: maybe there&#x27;s a trick hidden by clever z-indexing or the &#x27;Show Behind Parent&#x27; option?&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d also note that I don&#x27;t know how to make TileMaps interact with the &lt;a href=&quot;https:&#x2F;&#x2F;docs.godotengine.org&#x2F;en&#x2F;stable&#x2F;classes&#x2F;class_ysort.html&quot;&gt;&lt;code&gt;YSort&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; node&#x27;s sorting such that you could have walls that occlude the character behind but do not cover the character in front: I just made it so that you couldn&#x27;t walk into any section of the walls.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;navigation-isn-t-well-documented-and-it-s-a-bit-fiddly-to-draw-a-correct-mesh&quot;&gt;Navigation isn&#x27;t well-documented and it&#x27;s a bit fiddly to draw a correct mesh&lt;a class=&quot;zola-anchor&quot; href=&quot;#navigation-isn-t-well-documented-and-it-s-a-bit-fiddly-to-draw-a-correct-mesh&quot; aria-label=&quot;Anchor link for: navigation-isn-t-well-documented-and-it-s-a-bit-fiddly-to-draw-a-correct-mesh&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;To make my game come alive a bit, I wanted at least one kind of &#x27;enemy&#x27; character: in the time available, this turned out to be slugs.
They wouldn&#x27;t add much to the game if they couldn&#x27;t walk around though, so I needed pathfinding. Godot provides the &#x27;navigation&#x27; system for this purpose.&lt;&#x2F;p&gt;
&lt;p&gt;With that said, I found documentation a bit hard to come by for this.&lt;&#x2F;p&gt;
&lt;p&gt;Most online sources point you to the &lt;code&gt;Navigation2D&lt;&#x2F;code&gt; node (you&#x27;d use it as a parent of your world). At least in Godot 3.5.1, this node is deprecated with a (somewhat misleading&#x2F;unhelpful) message pointing you to the &lt;code&gt;Navigation2DServer&lt;&#x2F;code&gt;. But the online sources I came across, even for Godot 3.5.1, still use that node claiming it is more configurable. I know it&#x27;s a game jam, but I didn&#x27;t particularly want to drop a deprecated component in my project.&lt;&#x2F;p&gt;
&lt;p&gt;I had a few cases of the navigation system randomly not working, but here are some rough notes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Put a &lt;code&gt;NavigationAgent2D&lt;&#x2F;code&gt; node in your enemy (or whatever it is that needs to walk around) scene&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;In your levels (or potentially in the tiles, but I didn&#x27;t figure this out in time), add some &lt;code&gt;NavigationPolygon&lt;&#x2F;code&gt;s that cover the area in which the enemies are allowed to walk. Keep the gap between them quite tight and also don&#x27;t overlap them too far — it &lt;em&gt;seems like&lt;&#x2F;em&gt; the lines need to be close together for them to join up.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I also set the &#x27;Travel cost&#x27; to zero in each one, because otherwise I was having slugs walking the long way around the buildings just because there were fewer mesh polygons in the path that way(!) despite being a longer distance.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Because you&#x27;re (likely) a human — who didn&#x27;t make an absolutely perfect and gapless navigation mesh — increase the &lt;em&gt;edge connection margin&lt;&#x2F;em&gt; of the navigation system:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;GDScript&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-GDScript &quot;&gt;&lt;code class=&quot;language-GDScript&quot; data-lang=&quot;GDScript&quot;&gt;&lt;span&gt;# random place to put it, but needed to get our navigation working:
&lt;&#x2F;span&gt;&lt;span&gt;var w2d: World2D = get_world_2d()
&lt;&#x2F;span&gt;&lt;span&gt;Navigation2DServer.map_set_edge_connection_margin(w2d.navigation_map, 10.0)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(In this case, this means 10 pixels is the allowed margin between two polygon&#x27;s edges for an enemy to be allowed to walk across.)&lt;&#x2F;p&gt;
&lt;p&gt;If you don&#x27;t do this, your navigation polygons need to be extremely-well (perfectly?) lined up.
This is the reason that some sources are still using &lt;code&gt;Navigation2D&lt;&#x2F;code&gt; nodes, because you can easily configure this in the editor there.
I couldn&#x27;t find anywhere to configure this on the &lt;code&gt;Navigation2DServer&lt;&#x2F;code&gt;, so I put the above in the &lt;code&gt;_ready&lt;&#x2F;code&gt; function for my UI code (which is present on every single level).
Messy, but works...&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Occasionally (on a timer) poke the &lt;code&gt;NavigationAgent2D&lt;&#x2F;code&gt; to navigate to your target (in case the target moves, like a player) and have your physics processing code &lt;a href=&quot;https:&#x2F;&#x2F;git.emunest.net&#x2F;rei-games&#x2F;ld52_olive_harvest&#x2F;src&#x2F;tag&#x2F;submitted_LD52&#x2F;characters&#x2F;pests&#x2F;Slug.gd#L100&quot;&gt;steer the enemy towards the next point&lt;&#x2F;a&gt; — crucially, you call &lt;code&gt;NavigationAgent2D.get_next_location()&lt;&#x2F;code&gt; to get the global position of where you need to go to follow the path.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;d recommend that anyone interested in this feature experiment with it before a game jam if they&#x27;re interested in not wasting an hour or two learning it on the job...&lt;&#x2F;p&gt;
&lt;h4 id=&quot;structuring-a-long-term-project-is-not-obvious&quot;&gt;Structuring a long-term project is not obvious&lt;a class=&quot;zola-anchor&quot; href=&quot;#structuring-a-long-term-project-is-not-obvious&quot; aria-label=&quot;Anchor link for: structuring-a-long-term-project-is-not-obvious&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;I don&#x27;t know enough about the right way to structure a Godot project and especially not about how to effectively reuse code&#x2F;component Scenes.&lt;&#x2F;p&gt;
&lt;p&gt;For example, if I wanted to add an upgraded wheelbarrow that can hold more olives, I&#x27;m not sure about the cleanest way of doing that.&lt;&#x2F;p&gt;
&lt;h5 id=&quot;proliferating-use-of-collision-layers&quot;&gt;Proliferating use of collision layers&lt;a class=&quot;zola-anchor&quot; href=&quot;#proliferating-use-of-collision-layers&quot; aria-label=&quot;Anchor link for: proliferating-use-of-collision-layers&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h5&gt;
&lt;p&gt;For my game, I used a lot of collision layers despite it being a short project. Although you can name them (which is a nice touch!), there are a finite number so I&#x27;d probably want to think more carefully about how to share layers for multiple uses in a proper game.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;other-tools&quot;&gt;Other tools&lt;a class=&quot;zola-anchor&quot; href=&quot;#other-tools&quot; aria-label=&quot;Anchor link for: other-tools&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Here are the other tools I used:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;krita.org&#x2F;&quot;&gt;&lt;em&gt;Krita&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; for digital painting.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;chr15m&#x2F;jsfxr&quot;&gt;&lt;em&gt;jsfxr&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; for sound effect generation (a re-implementation of &lt;em&gt;sfxr&lt;&#x2F;em&gt; usable straight from the browser).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jesseduffield&#x2F;lazygit&quot;&gt;&lt;em&gt;lazygit&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; for version control (a text user interface for Git).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;submission&quot;&gt;Submission&lt;a class=&quot;zola-anchor&quot; href=&quot;#submission&quot; aria-label=&quot;Anchor link for: submission&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s an extra hour for submission. I used it for packaging up my game and writing up a description. I did not know that I would need a cover photo, so I just took a screenshot of the game, but in future I would try to make a bit more of a polished cover photo (observing the size requirements for submission).&lt;&#x2F;p&gt;
&lt;p&gt;I used Godot&#x27;s &#x27;Export&#x27; wizard to create a HTML5 export with the main name of &lt;code&gt;index.html&lt;&#x2F;code&gt; (as needed for Ludum Dare&#x27;s embed). I also placed a copy on my personal website so people could play the game fullscreen.
Note that there&#x27;s a fairly chunky download to complete before you can export a HTML5 game for the first time; if on a slow network it&#x27;d be a good idea to have that already done in advance.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rating-other-games&quot;&gt;Rating other games&lt;a class=&quot;zola-anchor&quot; href=&quot;#rating-other-games&quot; aria-label=&quot;Anchor link for: rating-other-games&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Don&#x27;t make the mistake of thinking that the event only lasts 73 hours... because after the deadline, it&#x27;s time to play other people&#x27;s games and rate them!
By doing so, you also encourage your game to show up at the top of the list for other players to find (which then leads to getting more ratings); note that you need 20 ratings to get a rank at the end of Ludum Dare!&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d say, based on just my experience, you should expect to play and rate about fifty games in order to attract enough attention to obtain your basic 20 ratings — but I noticed other games had a much better ratio, so perhaps my game was just not very enticing (hint: maybe a better cover photo would have helped!).
There were many interesting and well-polished games with interesting takes on the theme, but of course there were also some games you might consider underdeveloped, unoriginal or buggy.&lt;&#x2F;p&gt;
&lt;p&gt;Playing the other games was also a good chance to reflect on my own game. Some rough points:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I appreciated having HTML5 builds of games. I&#x27;d propose any participant to try and provide this option if they can. It means I don&#x27;t have to download questionable executables for a start, but also saves a lot of effort.
&lt;ul&gt;
&lt;li&gt;That said, some games&#x27; HTML5 builds were said to be degraded compared to the main build, such as having rendering bugs.
&lt;ul&gt;
&lt;li&gt;I&#x27;m definitely guilty of not having done this, but it&#x27;d be a good idea to test the HTML5 build of the game earlier than the publishing hour. I didn&#x27;t do anything too specialist so I got off lucky, I guess?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;I noticed that games built with &lt;em&gt;Godot 4&lt;&#x2F;em&gt; do not work in Firefox-based browsers, which was a shame. Easy trap to fall into — I just got lucky that I used Godot 3.5.1 really!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Many games had no audio, which is a shame. The games that had audio definitely felt more alive.
&lt;ul&gt;
&lt;li&gt;Even just sound effects will help with this — but tasteful music was good in the games that had it. (My game did not have music as it&#x27;s not in my skillset and I didn&#x27;t have time to experiment.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Some games didn&#x27;t do keybindings based on physical keys, so I had to change keyboard layout to play them. Many games did this right, though — so for the others, it&#x27;s likely just a matter of finding out the trick to do it in their game engine of choice. Godot offers you the ability to map &#x27;physical&#x27; keys to an input, for example.&lt;&#x2F;li&gt;
&lt;li&gt;Most games documented their controls in the description of the game. Some games did not and this was a shame when I couldn&#x27;t figure out how to get them going. Ideally (for me) they would be within reach whilst playing the game because I can&#x27;t remember more than a couple of controls in my head when I&#x27;m not used to them.&lt;&#x2F;li&gt;
&lt;li&gt;Some games were difficult to understand from the text alone. But some games had the right idea: they had illustrated diagrams telling you what to do. Based on this, I&#x27;d suggest taking 10 minutes to throw together illustrated instructions for your game — it should really only involve pasting together a few of the existing sprites and writing text, but it could really help players.
&lt;ul&gt;
&lt;li&gt;Many games, including mine, had planned to have an in-game tutorial but didn&#x27;t get around to it. &lt;em&gt;Avoid that altogether!&lt;&#x2F;em&gt; There&#x27;s basically never going to be time to do that in a jam, so take 10 minutes to put together an illustrated diagram instead.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Leaving off the cover photo entirely is very unfortunate! At least put a screenshot on!&lt;&#x2F;li&gt;
&lt;li&gt;It&#x27;s unfortunate that the Ludum Dare site doesn&#x27;t give you the option (that I could find) to filter out Windows-only games etc. At some points during the event, the top of the board was full of games that I couldn&#x27;t even run on this machine.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;some-of-my-favourite-games-from-ludum-dare-52&quot;&gt;Some of my favourite games from Ludum Dare 52&lt;a class=&quot;zola-anchor&quot; href=&quot;#some-of-my-favourite-games-from-ludum-dare-52&quot; aria-label=&quot;Anchor link for: some-of-my-favourite-games-from-ludum-dare-52&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ldjam.com&#x2F;events&#x2F;ludum-dare&#x2F;52&#x2F;poppy-defense&quot;&gt;Poppy Defence&lt;&#x2F;a&gt; — a tower defence-style game where you fight off waves of enemies by strategically planting various plants. Exceptionally well-polished and I had fun playing this one for hours!&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ldjam.com&#x2F;events&#x2F;ludum-dare&#x2F;52&#x2F;agricultural-ascension&quot;&gt;Agricultural Ascension&lt;&#x2F;a&gt; — an excellent puzzle game. The programmer in me liked how you would easily be able to generate challenges for this game programatically if you wished.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ldjam.com&#x2F;events&#x2F;ludum-dare&#x2F;52&#x2F;harvest-hell&quot;&gt;Harvest Hell&lt;&#x2F;a&gt; — harvest melons. If you&#x27;re too slow, they turn into monsters that need shooting down. Simple, but especially well-themed music that really brings it alive. I like how some subtle constraints of the game were put together.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ldjam.com&#x2F;events&#x2F;ludum-dare&#x2F;52&#x2F;horticulture&quot;&gt;Horticulture&lt;&#x2F;a&gt; — buy seeds, then plant and harvest plants for customers. The customers give you paper slips with their orders on and you need to cut the right parts of the plants whilst trying to be efficient. I&#x27;m afraid I am much too slow for this game — I get drowned in paper notes — but it was a good idea and I found it fun!&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ldjam.com&#x2F;events&#x2F;ludum-dare&#x2F;52&#x2F;no-harvest&quot;&gt;No Harvest&lt;&#x2F;a&gt; — rebelling against the theme in quite a fashionable way, this is a puzzle game (I guess) where you try and avoid nasty plants building up on a field, by casting spells. I liked how the games challenges you and forces you to consider your choices, as some spells will actually &lt;em&gt;help&lt;&#x2F;em&gt; some of the pesky plants &lt;em&gt;grow&lt;&#x2F;em&gt;!&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;ldjam.com&#x2F;events&#x2F;ludum-dare&#x2F;52&#x2F;revengetables&quot;&gt;Revengetables&lt;&#x2F;a&gt; — this is a game where you control the vegetables (and fruit!) in their uprising against the farmers who want to eat them. I&#x27;m not usually a fan of voiceovers but the voiceovers in this game were hilarious (and the game would be worth it for that reason alone). But even besides that, I found it thoughtful how the different characters had different benefits and you could switch between them during the fight.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;after-the-jam&quot;&gt;After the jam&lt;a class=&quot;zola-anchor&quot; href=&quot;#after-the-jam&quot; aria-label=&quot;Anchor link for: after-the-jam&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;It seems that some people will continue working on their games after the jam. I know I was intending to, but reality gets in the way.
Ludum Dare is a special time. My recommendation (mostly to myself) is to make time for it, then make the most of it — because that special time is not forever. I&#x27;d suggest trying to get your game to a state you are content with, before the jam ends and real life resumes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;play-olive-harvest&quot;&gt;Play Olive Harvest&lt;a class=&quot;zola-anchor&quot; href=&quot;#play-olive-harvest&quot; aria-label=&quot;Anchor link for: play-olive-harvest&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;!--* **Play updated post-jam game in your browser** [on my website](...)--&gt;
&lt;ul&gt;
&lt;li&gt;Play original jam game &lt;a href=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;aux&#x2F;ld52_olive_harvest&#x2F;&quot;&gt;on my website (recommended option due to window size)&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;See the listing &lt;a href=&quot;https:&#x2F;&#x2F;ldjam.com&#x2F;events&#x2F;ludum-dare&#x2F;52&#x2F;olive-harvest&quot;&gt;on Ludum Dare&lt;&#x2F;a&gt; (you can also play there, but the embed size is likely too small for the game)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;git.emunest.net&#x2F;rei-games&#x2F;ld52_olive_harvest&quot;&gt;Get the source code&lt;&#x2F;a&gt; (or: &lt;a href=&quot;https:&#x2F;&#x2F;git.emunest.net&#x2F;rei-games&#x2F;ld52_olive_harvest&#x2F;src&#x2F;tag&#x2F;submitted_LD52&quot;&gt;permalink to the version submitted to LD52&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
	</entry>
	
	
	<entry xml:lang="en">
		<title>QuickPeep: My work-in-progress small-scale web search engine</title>
		<published>2022-07-02T00:00:00+00:00</published>
		<updated>2022-07-02T00:00:00+00:00</updated>
		<link rel="alternate" href="https://o.librepush.net/blog/2022-07-02-quickpeep-small-scale-web-search-engine/" type="text/html"/>
		<id>https://o.librepush.net/blog/2022-07-02-quickpeep-small-scale-web-search-engine/</id>
		<content type="html">&lt;h2 id=&quot;frustration&quot;&gt;Frustration&lt;a class=&quot;zola-anchor&quot; href=&quot;#frustration&quot; aria-label=&quot;Anchor link for: frustration&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I have a few points of frustration with the common modern web search engines.
For one, they tend to get gamed by &lt;em&gt;content mill&lt;&#x2F;em&gt; sites that specialise in &lt;em&gt;Search Engine Optimisation&lt;&#x2F;em&gt; (SEO).
These very sites tend to be the ones that don&#x27;t respect the reader, hammering them with nagging pop-ups, adverts, trackers and other distractions.
I&#x27;m not even sure that many of these sites are actually written by a thoughtful human as opposed to being chucked together in a rush, or perhaps even by machine.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s not that sites authored by individual people have gone away. If you know where to look (which is difficult), they still exist and I much prefer those.
I suppose those people don&#x27;t care about chasing all the latest SEO tricks and so they fall to the bottom of the results.
That&#x27;s understandable; it&#x27;s enough effort to put a site together in the first place, why should they have to be bothered with the SEO games as well?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-m-up-to&quot;&gt;What I&#x27;m up to&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-m-up-to&quot; aria-label=&quot;Anchor link for: what-i-m-up-to&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I thought to myself something along the lines of &#x27;it&#x27;s about time that someone makes a search engine which is specifically targeted at small websites and doesn&#x27;t give you any rubbish&#x27;.
Unfortunately, I then had to take personal responsibility for that statement and start such a project myself. :-)&lt;&#x2F;p&gt;
&lt;p&gt;I call it QuickPeep — &lt;em&gt;&#x27;quick&#x27; because it should be fast to get to desirable content and &#x27;peep&#x27; because &#x27;peep&#x27; means &#x27;a quick look&#x27;. &#x27;Peep&#x27; also describes one of the sounds made by some birds, so it&#x27;s a perfect excuse to have a cute bird on the logo.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A few guiding principles of the project are:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Modest hardware requirements.&lt;&#x2F;strong&gt; The search engine should be operable with modest hardware requirements.
Storage space is a particular concern. We obviously can&#x27;t index the whole web, but even individual pages should be &#x27;shrunk&#x27; down as much as possible so we can fit many pages into our search engine.
Time is also quite limited: I simply don&#x27;t have enough computers, or enough bandwidth, to trawl the entire web!
I may be quite extreme about this goal. I won&#x27;t turn my nose up at a 5% space improvement, for example — just because it&#x27;s not an order of magnitude doesn&#x27;t mean it&#x27;s not worth doing (to me)!&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No rubbish sites.&lt;&#x2F;strong&gt; Rubbish sites are not welcome.
To minimise resource wastage, the best way to keep them out is to &#x27;simply&#x27; not index them.
This obviously comes with the implicit problem of somehow being able to know which websites are rubbish.
Maybe someone clever could come up with a machine learning approach, but for now, I manually collect a set of sites that are allowed to be in the index. This is obviously quite &#x27;human-intensive&#x27;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Does what it&#x27;s told.&lt;&#x2F;strong&gt; Some search engines will ignore your attempt to exclude terms, or they may disregard &lt;code&gt;&quot;multiple word phrases&quot;&lt;&#x2F;code&gt; if they think they know better than you.
QuickPeep doesn&#x27;t do this. It should do what you say.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flexibility.&lt;&#x2F;strong&gt; This is quite difficult to provide when trying to cut down storage space, so it&#x27;s a bit of a &#x27;best-effort&#x27; point.
QuickPeep should provide convenient features to help the reader in filtering out unwanted results.
This may take the shape of being able to exclude terms within a query, or being able to exclude results for sites that don&#x27;t respect them.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Open and free.&lt;&#x2F;strong&gt; It should be possible for more or less anyone to operate their own search engine based on this software.
Ideally, they should be able to obtain some kind of data dump to avoid the hassle of crawling everything again if that&#x27;s not what they want to do.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;quickpeep-s-pipeline-and-what-it-looks-like-to-operate-and-use&quot;&gt;QuickPeep&#x27;s pipeline and what it looks like to operate and use&lt;a class=&quot;zola-anchor&quot; href=&quot;#quickpeep-s-pipeline-and-what-it-looks-like-to-operate-and-use&quot; aria-label=&quot;Anchor link for: quickpeep-s-pipeline-and-what-it-looks-like-to-operate-and-use&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;seeding-the-search-engine&quot;&gt;Seeding the search engine&lt;a class=&quot;zola-anchor&quot; href=&quot;#seeding-the-search-engine&quot; aria-label=&quot;Anchor link for: seeding-the-search-engine&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;To start this project off, I assembled a collection of &lt;em&gt;seeds&lt;&#x2F;em&gt; — URLs that point to pages that I want in my search engine. Each seed implicitly vouches for (allows the crawling of) the same domain (or, in some cases, only some URL prefixes). I also came up with a handful of &lt;em&gt;weeds&lt;&#x2F;em&gt; — these are the opposite of seeds and correspond to URLs that we don&#x27;t care about tracking, either because the site is too big to be practically indexable or the site doesn&#x27;t belong in our focussed search engine.&lt;&#x2F;p&gt;
&lt;p&gt;Early on, I added a seed collection webpage to QuickPeep&#x27;s web interface so that I could easily enter seeds (giving them tags) and even collect a few from friends and colleagues (thanks folks!).
I only have about forty right now, but it&#x27;s still enough to be interesting.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;raking-the-web&quot;&gt;Raking the web&lt;a class=&quot;zola-anchor&quot; href=&quot;#raking-the-web&quot; aria-label=&quot;Anchor link for: raking-the-web&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Obviously, we need to get the content of interesting webpages into our search index so we can answer search queries.
The first half of this — retrieving content from webpages — is what the QuickPeep &#x27;raker&#x27; does.&lt;&#x2F;p&gt;
&lt;p&gt;The raker maintains its state as a libMDBX database and emits Zstd-compressed &#x27;rake packs&#x27; which contain the page contents, references, site icons and errors.&lt;&#x2F;p&gt;
&lt;p&gt;The operator first imports a collection of seeds and weeds into the database using &lt;code&gt;qp-seedrake&lt;&#x2F;code&gt;.
Each seed becomes an entry in the rake queue and also generates an entry in the set of allowed domains.
Each weed creates an entry in the set of weed domains.&lt;&#x2F;p&gt;
&lt;p&gt;The operator can then start the raker itself (&lt;code&gt;qp-raker&lt;&#x2F;code&gt;). The raker splits off into separate tasks and will shut down when there is no more work to be done.&lt;&#x2F;p&gt;
&lt;p&gt;Each task selects a rakeable domain at random from the database and then processes queue items for that domain.
A queue item is an instruction to download a URL, perhaps with an &#x27;intent&#x27; for that URL (which determines what kind of document we&#x27;re expecting).
The raker is capable of understanding HTML webpages, RSS and Atom feeds and XML Sitemaps. Common image formats are also supported, but only if the raker is downloading a site&#x27;s icon (becoming an image search engine is outside the scope of this project).&lt;&#x2F;p&gt;
&lt;p&gt;If we run into trouble whilst downloading from a domain, the raker can either generate a &#x27;rejection&#x27; entry for that URL or, for temporary failures, we can back off from that domain until a later time.&lt;&#x2F;p&gt;
&lt;p&gt;For feeds, sitemaps and webpages, the raker extracts references to other URLs.
These are emitted in the &#x27;references&#x27; rake pack and:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;inserted into the queue (if the domain is allowed);&lt;&#x2F;li&gt;
&lt;li&gt;inserted into the &#x27;on-hold&#x27; queue (if the domain isn&#x27;t one of the allowed domains); or&lt;&#x2F;li&gt;
&lt;li&gt;discarded (for weeds).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For HTML webpages, the raker applies a few steps of pre-processing:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Advert (and other troublesome content) blocking and detection.&lt;&#x2F;strong&gt;
The adblock engine from the &lt;em&gt;Brave&lt;&#x2F;em&gt; web browser is written in Rust and so was relatively easy to embed. &lt;br &#x2F;&gt;
We use it for a couple of reasons: First of all, we can detect disrespectful (to a reader) &#x27;nasties&#x27; such as adverts, trackers and &#x27;annoyances&#x27; (including stupid cookie pop-ups&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;). &lt;br &#x2F;&gt;
Second of all, we can trim extraneous bloat from the webpage content that we&#x27;re aiming to index.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Cookie pop-ups are almost always a sign that the site is doing something disrespectful. They&#x27;re not needed for legitimate use cases. &lt;br &#x2F;&gt;
The vast majority of them are also infuriatingly difficult to dismiss without accepting; very few give easy rejection options. &lt;br &#x2F;&gt;
In any case, they take your focus off the content (quite often covering a large portion of the screen) and so we will count them all as &#x27;evil&#x27; for the pursuit of a pleasant experience.
I&#x27;m relying on existing adblock rulesets here and definitely don&#x27;t have the time to maintain one myself, anyway.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Readability extraction.&lt;&#x2F;strong&gt;
If you&#x27;re aware of Firefox&#x27;s &lt;em&gt;Reader Mode&lt;&#x2F;em&gt;, this is basically that. In fact, it&#x27;s more or less exactly that — I use a (tweaked) Rust &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;hipstermojo&#x2F;paperoni&#x2F;blob&#x2F;796a34a34c365bc06191bf634918b71eaee7bd5d&#x2F;src&#x2F;moz_readability&#x2F;mod.rs&quot;&gt;fork&lt;&#x2F;a&gt; of Mozilla&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mozilla&#x2F;readability&quot;&gt;readability.js&lt;&#x2F;a&gt; project (thanks &lt;em&gt;paperoni&lt;&#x2F;em&gt;). &lt;br &#x2F;&gt;
If you haven&#x27;t heard of Reader Mode, then just know that it&#x27;s a heuristic algorithm for extracting an article&#x27;s main content from its web page.&lt;&#x2F;p&gt;
&lt;p&gt;This could be a very useful technique for cutting down the size of the content that we need to index: we have no interest in being able to search for what&#x27;s visible on the site&#x27;s navigation menu, so why store it? &lt;br &#x2F;&gt;
However, as this technique is more or less educated guesswork, I haven&#x27;t entirely stripped away the content that isn&#x27;t extracted as readable content, out of paranoia that some sites may not extract well.
Instead, I&#x27;m considering ranking words in the &#x27;readable&#x27; section more favourably than words elsewhere. &lt;br &#x2F;&gt;
It&#x27;s also possible to alter the ranking so that webpages with a relevant readable section are preferred over others, which could be nice for people that much prefer reading pages in Reader Mode.&lt;&#x2F;p&gt;
&lt;p&gt;I still haven&#x27;t decided exactly what I&#x27;m going to do with this; I&#x27;m not sure how I feel about being &#x27;unfair&#x27; to webpages that don&#x27;t fit in well with this non-standardised technique. &lt;br &#x2F;&gt;
If it turns out that my concerns are unfounded, I may just strip out the non-readability-extracted content altogether and save some space!&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Conversion from HTML to a denser, less structured, format.&lt;&#x2F;strong&gt;
Many of the bytes in a webpage are used to represent HTML tags that don&#x27;t have any useful meaning for a search index. &lt;br &#x2F;&gt;
For example, we don&#x27;t care about the CSS on the page or most of the attributes on the tags.
Similarly, tags like &lt;code&gt;&amp;lt;div&amp;gt;&lt;&#x2F;code&gt; often don&#x27;t mean anything to us (especially not if they&#x27;re nested); they&#x27;re basically just there to make styling rules useful.&lt;&#x2F;p&gt;
&lt;p&gt;This conversion pass reduces the structure of the page to text nodes, headings and links, plus some metadata (e.g. page title, page description and URL to the icon).
(Headings could be useful to bias ranking. I haven&#x27;t entirely decided about whether I should scrap links yet.) &lt;br &#x2F;&gt;
This pass usually achieves a 80%+ reduction in document size and is a big part of making it cheap enough to store the content of so many web pages.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Language detection.&lt;&#x2F;strong&gt;
Many (maybe even most?) webpages don&#x27;t seem to declare their language. On these, we sniff the language using &lt;a href=&quot;https:&#x2F;&#x2F;lib.rs&#x2F;crates&#x2F;lingua&quot;&gt;Lingua&lt;&#x2F;a&gt;.
We don&#x27;t actually discard non-English pages here (but we could...?). &lt;br &#x2F;&gt;
Most likely, it might be useful to discard non-English pages when indexing, or to put them in a different language-specific index using the correct stemming rules for the language.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The document is then emitted into the &#x27;pages&#x27; rake pack.&lt;&#x2F;p&gt;
&lt;p&gt;For icons, we decode the image, convert it to a lossy WebP with an unreasonably-low quality value (&lt;em&gt;5%&lt;&#x2F;em&gt;) and then emit it into the &#x27;icons&#x27; pack.
I&#x27;m sure I could afford to be more generous with the icons, but I&#x27;ve been impressed enough with the quality for the tiny size that I haven&#x27;t bothered to.&lt;&#x2F;p&gt;
&lt;p&gt;The rake packs are compressed with Zstd (using a high compression level). Keeping the types of documents separated into different rake packs is good for two reasons; it improves the compression ratio as e.g.
the &lt;em&gt;pages&lt;&#x2F;em&gt; rake pack contains mostly human-language text and doesn&#x27;t have WebP icons randomly interspersed. The separation also means that you don&#x27;t have to download all the rake packs if you don&#x27;t need them;
someone operating an indexer likely doesn&#x27;t care about the &lt;em&gt;references&lt;&#x2F;em&gt; rake pack, but someone operating a raker may do if they want to rebuild their raker&#x27;s state without reraking everything.&lt;&#x2F;p&gt;
&lt;p&gt;The raker can export Prometheus-compatible (or &lt;em&gt;BARE Metrics&lt;&#x2F;em&gt;-compatible, though I prefer Prometheus &amp;amp; Grafana for this situation) metrics.
This gives you a way to see that it&#x27;s making progress, as well as an idea of the sizes of various database tables (both in numbers of rows and in bytes&#x2F;disk usage).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2022-07-02-quickpeep-small-scale-web-search-engine&#x2F;Screenshot_grafana_dashboard.png&quot; alt=&quot;Screenshot of a Grafana dashboard for QuickPeep Raker&quot; &#x2F;&gt;
&lt;em&gt;Figure: Part of a Grafana Dashboard for QuickPeep Raker. This is showing the slowest part of raking: the end, where only a couple of sites have not been finished.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h4 id=&quot;a-quick-aside-about-anti-features&quot;&gt;A quick aside about anti-features&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-quick-aside-about-anti-features&quot; aria-label=&quot;Anchor link for: a-quick-aside-about-anti-features&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;The raker identifies and tags pages with &#x27;anti-features&#x27;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
These are aspects of a page or its site which may be undesirable for a potential reader.&lt;&#x2F;p&gt;
&lt;p&gt;For example, the presence of pop-ups (e.g. cookies, e-mail subscription nags, etc) or adverts may be reasons to avoid a site.&lt;&#x2F;p&gt;
&lt;p&gt;The raker also tries to identify sites that are sitting behind CloudFlare.
This is because sites behind CloudFlare are often unbearable or even totally unusable for users of Tor; many sites impose at least a series of CAPTCHAs but some deny access to the content outright. &lt;br &#x2F;&gt;
This can be extremely frustrating and seems to fit into the &#x27;disrespectful to the reader&#x27; category. &lt;br &#x2F;&gt;
Tor users should be able to use QuickPeep and exclude or down-rank these results if desired, to avoid wasting their time.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;The inspiration for the term &#x27;anti-features&#x27; comes from F-Droid, which applies this to Android applications.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;h3 id=&quot;indexing-web-pages&quot;&gt;Indexing web pages&lt;a class=&quot;zola-anchor&quot; href=&quot;#indexing-web-pages&quot; aria-label=&quot;Anchor link for: indexing-web-pages&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;If someone wants to operate a search engine, they first need to set up an index.&lt;&#x2F;p&gt;
&lt;p&gt;QuickPeep is intended to support multiple index&#x2F;search backends; currently &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;quickwit-oss&#x2F;tantivy&quot;&gt;Tantivy&lt;&#x2F;a&gt; (an embedded search engine) is implemented but I&#x27;m interested in trying &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;meilisearch&#x2F;MeiliSearch&quot;&gt;MeiliSearch&lt;&#x2F;a&gt; (a search engine server) to see how it compares.&lt;&#x2F;p&gt;
&lt;p&gt;The operator configures their index of choice (Tantivy is easy as there&#x27;s no need to set up a separate server). &lt;br &#x2F;&gt;
The operator can then download and import the rake packs, either from their own raker or from a public repository of rake packs (e.g. mine! — once things are a bit more stable). &lt;br &#x2F;&gt;
The operator uses &lt;code&gt;qp-indexer&lt;&#x2F;code&gt; to index the entries from the rake pack (this also imports icons into a small key-value store so that the web interface can find them). &lt;br &#x2F;&gt;
There is a command-line tool, &lt;code&gt;qp-index-search&lt;&#x2F;code&gt;, to search the index, which can show that things are working.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2022-07-02-quickpeep-small-scale-web-search-engine&#x2F;Screenshot_cli_search_bee_counting.png&quot; alt=&quot;Screenshot for a search of &amp;#39;bee counting&amp;#39; at the command line&quot; &#x2F;&gt;
&lt;em&gt;Figure: A search for &#x27;bee counting&#x27; using &lt;code&gt;qp-index-search&lt;&#x2F;code&gt;. This tool is also interesting because it shows the relevance score of each result.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;searching-the-index&quot;&gt;Searching the index&lt;a class=&quot;zola-anchor&quot; href=&quot;#searching-the-index&quot; aria-label=&quot;Anchor link for: searching-the-index&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The &lt;code&gt;quickpeep&lt;&#x2F;code&gt; binary serves a web frontend to the index, offering a simple &lt;em&gt;text box and button&lt;&#x2F;em&gt;-based interface for performing searches. &lt;br &#x2F;&gt;
The layout is quite minimal, which I like here (but it currently looks rubbish on light mode, I&#x27;m afraid. If your browser doesn&#x27;t advertise a preference for dark mode, be warned :-)).&lt;&#x2F;p&gt;
&lt;p&gt;Right now, I just pass my query in to Tantivy fairly directly and don&#x27;t try anything too intelligent or sophisticated to refine the results.
Luckily, Tantivy also supports common search operators (such as &lt;code&gt;+word&lt;&#x2F;code&gt;, &lt;code&gt;-word&lt;&#x2F;code&gt;, &lt;code&gt;&quot;multiple word phrase&quot;&lt;&#x2F;code&gt;, etc), so we get these &#x27;for free&#x27;.
I also take Tantivy&#x27;s excerpts&#x2F;snippets directly.&lt;&#x2F;p&gt;
&lt;p&gt;There is no &lt;em&gt;PageRank&lt;&#x2F;em&gt; algorithm or anything, it&#x27;s all just plain full-text search ranking.&lt;&#x2F;p&gt;
&lt;p&gt;A quick play with the search engine reveals that it doesn&#x27;t feel like it&#x27;s giving me as much &#x27;relevance&#x27; as other search engines do.
I imagine some refinement could go a long way to improving this.&lt;&#x2F;p&gt;
&lt;p&gt;That said, I&#x27;ve also compared a few queries to DuckDuckGo and some of them aren&#x27;t really any different. Naturally, only having about forty sites in the index rather than ...millions(?) is bound to mean that some queries struggle to find much relevance, though.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve also found some interesting articles through QuickPeep and I&#x27;ve enjoyed reading them, so it&#x27;s not &lt;em&gt;that much&lt;&#x2F;em&gt; of a flop!&lt;&#x2F;p&gt;
&lt;p&gt;Once I can think of how I can improve this, I&#x27;ll give it a go. Trying MeiliSearch may be interesting as a point of comparison and I may get some ideas along the way.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2022-07-02-quickpeep-small-scale-web-search-engine&#x2F;Screenshot_search_crdt.png&quot; alt=&quot;Screenshot of a search for &amp;#39;CRDT&amp;#39; in QuickPeep&quot; &#x2F;&gt;
&lt;em&gt;Figure: A search for &#x27;CRDT&#x27; in QuickPeep&#x27;s web frontend&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2022-07-02-quickpeep-small-scale-web-search-engine&#x2F;Screenshot_search_rant.png&quot; alt=&quot;Screenshot of a search for &amp;#39;rant&amp;#39; in QuickPeep&quot; &#x2F;&gt;
&lt;em&gt;Figure: A search for &#x27;rant&#x27; in QuickPeep&#x27;s web frontend&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;brief-comment-on-disk-usage&quot;&gt;Brief comment on disk usage&lt;a class=&quot;zola-anchor&quot; href=&quot;#brief-comment-on-disk-usage&quot; aria-label=&quot;Anchor link for: brief-comment-on-disk-usage&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Ideally I should try and get at least thousands of sites into the index before thinking too much about this, but again, disk usage is an obvious concern when operating a search index.
How much space am I using for these ~forty sites?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Search Index
&lt;ul&gt;
&lt;li&gt;Tantivy index: 150 MiB (I don&#x27;t know how this will scale and how much of this is overhead that is exaggerated on a small database.)&lt;&#x2F;li&gt;
&lt;li&gt;Icons store: 76 kiB (Much of this will be overhead since the database is so small and it uses 4 kiB pages.) &lt;br &#x2F;&gt;
Icons themselves are typically about 200 bytes.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Rake packs: 28 MiB (note that these can be deleted after they&#x27;ve been indexed; they&#x27;re just a data transfer format. But it&#x27;d be useful to be able to host them for others to operate their own search engines.)&lt;&#x2F;li&gt;
&lt;li&gt;Raker database: 17 MiB (Much of this is &#x27;on-hold&#x27; URLs. This could be reduced by marking some of them as weeds if we&#x27;re not interested in indexing them.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The size of the Tantivy index seems to be the most worrying, especially since you can&#x27;t really delete it if you still want to search for things! &lt;br &#x2F;&gt;
It may be worth adding Zstd support to Tantivy&#x27;s document store (currently it uses LZ4, which is fast but has a much lower compression ratio) and using it to compress entries.
Training and using a Zstd dictionary should give us a good compression ratio. Decompression will be slower, of course, but it&#x27;s still likely good enough.&lt;&#x2F;p&gt;
&lt;p&gt;Alternatively, I believe it&#x27;s possible to not store document text itself in Tantivy&#x27;s datastore.&lt;&#x2F;p&gt;
&lt;p&gt;The biggest files in Tantivy&#x27;s index seem to be &lt;code&gt;.pos&lt;&#x2F;code&gt; and &lt;code&gt;.idx&lt;&#x2F;code&gt; files, so I&#x27;m not sure improvements in the document store are going to buy much anyway.
I note that &lt;code&gt;zstd&lt;&#x2F;code&gt; can cut one of the &lt;code&gt;.idx&lt;&#x2F;code&gt; files down by 50% and one of the &lt;code&gt;.pos&lt;&#x2F;code&gt; files down by 20%. Perhaps it&#x27;s worth using a compressed filesystem?&lt;&#x2F;p&gt;
&lt;p&gt;I presume the large &lt;code&gt;.pos&lt;&#x2F;code&gt; files are indices of the positions of all the terms in the document (primarily useful for &lt;code&gt;&quot;multiple word phrase&quot;&lt;&#x2F;code&gt; search terms, as far as I know). &lt;br &#x2F;&gt;
I suppose those could be turned off altogether at the cost of making &lt;code&gt;&quot;multiple word phrase&quot;&lt;&#x2F;code&gt; queries slower (as they&#x27;d have to find the positions in the document itself). &lt;br &#x2F;&gt;
According to &lt;a href=&quot;https:&#x2F;&#x2F;fulmicoton.com&#x2F;posts&#x2F;behold-tantivy-part2&#x2F;&quot;&gt;Paul Masurel&#x27;s blog post about Tantivy&#x27;s indexing&lt;&#x2F;a&gt; (which I just submitted as a QuickPeep seed :^)), it should be possible to make the index itself smaller than the document, if term positions are disabled.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;barely-usable&quot;&gt;Barely usable&lt;a class=&quot;zola-anchor&quot; href=&quot;#barely-usable&quot; aria-label=&quot;Anchor link for: barely-usable&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;...but open! QuickPeep is open-source: &lt;a href=&quot;https:&#x2F;&#x2F;bics.ga&#x2F;reivilibre&#x2F;quickpeep&#x2F;&quot;&gt;quickpeep&lt;&#x2F;a&gt;. My collection of seeds and weeds is open data: &lt;a href=&quot;https:&#x2F;&#x2F;bics.ga&#x2F;reivilibre&#x2F;quickpeep_seeds&#x2F;&quot;&gt;quickpeep_seeds&lt;&#x2F;a&gt;. &lt;br &#x2F;&gt;
If you&#x27;d like to give it a try, you can visit &lt;a href=&quot;https:&#x2F;&#x2F;quickpeep.net&quot;&gt;https:&#x2F;&#x2F;quickpeep.net&lt;&#x2F;a&gt;, but don&#x27;t get your hopes up :-)!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;known-problems&quot;&gt;Known problems&lt;a class=&quot;zola-anchor&quot; href=&quot;#known-problems&quot; aria-label=&quot;Anchor link for: known-problems&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;Quality of excerpts is usually poor.&lt;&#x2F;li&gt;
&lt;li&gt;Ranking can be a bit poor.&lt;&#x2F;li&gt;
&lt;li&gt;Pages without favicons show an ugly broken image.&lt;&#x2F;li&gt;
&lt;li&gt;Duplicate webpages show up as duplicate results — QuickPeep should try and canonicalise URLs a bit and look for similar pages.
&lt;ul&gt;
&lt;li&gt;It&#x27;s probably quite easy to deal with the most common culprits: canonicalise a URL (for deduplication purposes only) by removing trailing &lt;code&gt;&#x2F;&lt;&#x2F;code&gt;, ignoring the protocol scheme (&lt;code&gt;https:&#x2F;&#x2F;&lt;&#x2F;code&gt;) and ignoring a &lt;code&gt;www.&lt;&#x2F;code&gt; subdomain if present.&lt;&#x2F;li&gt;
&lt;li&gt;I have some vague plans to use &lt;a href=&quot;https:&#x2F;&#x2F;matpalm.com&#x2F;resemblance&#x2F;simhash&#x2F;&quot;&gt;SimHash&lt;&#x2F;a&gt; to try and tackle the duplicate page problem more generally.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Dynamic &#x27;category&#x27; or &#x27;home&#x27; pages should be favoured less than the pages that they contain excerpts for.
&lt;ul&gt;
&lt;li&gt;Some simple heuristics will probably help a lot here, e.g. favouring the longer URL.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Antifeatures are not used at all. They&#x27;re not even displayed.&lt;&#x2F;li&gt;
&lt;li&gt;The size of the Tantivy index is slightly worrying, but there are ways to improve it.
&lt;ul&gt;
&lt;li&gt;Try and add in Zstd support; use Zstd with a dictionary?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;URL fragments (&lt;code&gt;#blah&lt;&#x2F;code&gt;) should be stripped when raking.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;aside-other-uses-for-the-references-rake-packs&quot;&gt;Aside: other uses for the &#x27;references&#x27; rake packs&lt;a class=&quot;zola-anchor&quot; href=&quot;#aside-other-uses-for-the-references-rake-packs&quot; aria-label=&quot;Anchor link for: aside-other-uses-for-the-references-rake-packs&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;When a lot of sites have been crawled, it&#x27;d be fun to load the &#x27;references&#x27; rake packs into some kind of graph database and be able to answer queries like &#x27;which pages refer to my page&#x27;. &lt;br &#x2F;&gt;
Reverse references like this could be quite interesting for finding extensions or different points of view to an article you are already aware of. &lt;br &#x2F;&gt;
(This would be gamed for SEO purposes in a heartbeat on a conventional search engine!)&lt;&#x2F;p&gt;
&lt;p&gt;Because these rake packs will be available for download, it should be quite possible for someone to do this if they like.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;thanks&quot;&gt;Thanks&lt;a class=&quot;zola-anchor&quot; href=&quot;#thanks&quot; aria-label=&quot;Anchor link for: thanks&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;QuickPeep is built on top of many great quality, open-source libraries. &lt;br &#x2F;&gt;
Many thanks to all their authors for putting their work out into the open for fools like me to enjoy!&lt;&#x2F;p&gt;
</content>
	</entry>
	
	<entry xml:lang="en">
		<title>Forgot10: My application for note-taking and general information tracking</title>
		<published>2022-05-25T00:00:00+00:00</published>
		<updated>2022-05-25T00:00:00+00:00</updated>
		<link rel="alternate" href="https://o.librepush.net/blog/2022-05-25-forgot10-devlog/" type="text/html"/>
		<id>https://o.librepush.net/blog/2022-05-25-forgot10-devlog/</id>
		<content type="html">&lt;p&gt;I&#x27;ve been working on a &#x27;note-taking&#x27; system for a few months now.
I think I have some interesting ideas and I thought it&#x27;d be worth a blog post so that I can look back and remember what I did these past few months...&lt;&#x2F;p&gt;
&lt;h2 id=&quot;motivation-and-rough-idea&quot;&gt;Motivation and rough idea&lt;a class=&quot;zola-anchor&quot; href=&quot;#motivation-and-rough-idea&quot; aria-label=&quot;Anchor link for: motivation-and-rough-idea&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;d like to do a better job of making notes of things and being able to retrieve information that I&#x27;ve come across.
All too often, I&#x27;ve been stuck trying to remember something that I &lt;em&gt;know&lt;&#x2F;em&gt; I&#x27;ve done before, or that I &lt;em&gt;know&lt;&#x2F;em&gt; I&#x27;ve seen somewhere (probably on the Internet).
It&#x27;d be useful to me to have a system that lets me take notes in a natural way and then lets me search those notes and &lt;em&gt;anything else&lt;&#x2F;em&gt; that might be useful.&lt;&#x2F;p&gt;
&lt;p&gt;I currently use &lt;a href=&quot;https:&#x2F;&#x2F;zim-wiki.org&#x2F;&quot;&gt;Zim&lt;&#x2F;a&gt; for my notes. It&#x27;s a cracking tool that calls itself a &#x27;Desktop Wiki&#x27;. The pages themselves are stored in a &lt;a href=&quot;https:&#x2F;&#x2F;zim-wiki.org&#x2F;manual&#x2F;Help&#x2F;Wiki_Syntax.html&quot;&gt;textual format&lt;&#x2F;a&gt;.
With that said, it has a few limitations and I have a few itches for extra functionality which tempted me to roll my own tool...&lt;&#x2F;p&gt;
&lt;h3 id=&quot;peer-to-peer-sync&quot;&gt;Peer-to-peer sync&lt;a class=&quot;zola-anchor&quot; href=&quot;#peer-to-peer-sync&quot; aria-label=&quot;Anchor link for: peer-to-peer-sync&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I use &lt;a href=&quot;https:&#x2F;&#x2F;syncthing.net&#x2F;&quot;&gt;Syncthing&lt;&#x2F;a&gt; to synchronise my Zim notebooks. It&#x27;s peer-to-peer and files synchronise directly between my computers (and &#x27;smart&#x27; &#x27;phone&#x27;).
I really like it, but I have occasionally ran into sync conflicts when I modify my Zim notebooks on two different machines concurrently (e.g. because often only one of them is on at once).
Sharing a Zim notebook with a friend can also lead you to the same problem if you try to concurrently update the same page.&lt;&#x2F;p&gt;
&lt;p&gt;As a result, a wish for my tool is to not only allow near-flawless offline use (since I&#x27;m often offline), but also to synchronise my different computers when they have the opportunity.
To this end, I want to use Conflict-free Replicated Data Types (CRDTs).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;streams-pages-and-inline-actions&quot;&gt;Streams, Pages and inline actions&lt;a class=&quot;zola-anchor&quot; href=&quot;#streams-pages-and-inline-actions&quot; aria-label=&quot;Anchor link for: streams-pages-and-inline-actions&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;My tool should be able to adapt to at least two frames of mind: sometimes, there&#x27;s a need to quickly get a note down or &#x27;stream&#x27; thoughts from mind to page.
In this situation, the tool should ideally get out of the way and pose as little interruption as possible to getting those thoughts on the page!&lt;&#x2F;p&gt;
&lt;p&gt;At other times, collecting nicely-polished notes together is desirable and I wouldn&#x27;t mind putting some time into organisation.
In this situation, the tool should give options to &#x27;cultivate&#x27; a structured repository of information.&lt;&#x2F;p&gt;
&lt;p&gt;For the first case, I came up with the concept of a &lt;em&gt;&#x27;Stream&#x27;&lt;&#x2F;em&gt; (although if it seems sensible, it would be possible to have multiple of them).
A Stream is just a chronologically-ordered list of entries (small rich text documents). It&#x27;s intended for off-the-cuff journalling.
The application should let you enter commands as you write an entry, so that e.g. you can easily schedule a reminder for yourself, without needing to even take your hands off the keyboard or leave the Stream at all.&lt;&#x2F;p&gt;
&lt;p&gt;So far, I have &lt;code&gt;&#x2F;reminder&lt;&#x2F;code&gt; (aliased as &lt;code&gt;&#x2F;r&lt;&#x2F;code&gt;) which lets you schedule a reminder, potentially recurring, using plain English &#x27;syntax&#x27;. See:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Video demonstrating its use:&lt;&#x2F;strong&gt;
&lt;video width=&quot;920&quot; height=&quot;400&quot; controls class=&quot;blog_video&quot;&gt;
    
        &lt;source src=&quot;&amp;#x2F;blog&amp;#x2F;2022-05-25-forgot10-devlog&amp;#x2F;&#x2F;f10_reminders.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
    
    Video unsupported in your browser? Do you want a direct link?
    
        &lt;br&gt;&lt;a href=&quot;&amp;#x2F;blog&amp;#x2F;2022-05-25-forgot10-devlog&amp;#x2F;&#x2F;f10_reminders.mp4&quot;&gt;MP4 here&lt;&#x2F;a&gt;
    
&lt;&#x2F;video&gt;
&lt;&#x2F;p&gt;
&lt;p&gt;When you&#x27;re not making rapid-fire notes, the tool should let you write to a hierarchical tree of note &lt;em&gt;Pages&lt;&#x2F;em&gt;. These can be navigated to like Zim&#x27;s pages.
They have a (required!) slot for a page title and they are intended for me to come back to time and time again.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Screenshot of a Page:&lt;&#x2F;strong&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2022-05-25-forgot10-devlog&#x2F;f10_pages.png&quot; alt=&quot;Screenshot of a page about the btrfs filesystem&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;search-everything&quot;&gt;Search... everything!&lt;a class=&quot;zola-anchor&quot; href=&quot;#search-everything&quot; aria-label=&quot;Anchor link for: search-everything&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;A tool like this would benefit from good search to be useful.
It&#x27;s tempting to take this one step further and pull in extra sources of information to be indexed.&lt;&#x2F;p&gt;
&lt;p&gt;For example, it may well be interesting to dig up browser history and archive the contents of the pages that I&#x27;ve been reading.
It might be useful to archive e-mails and Matrix messages and index those, too.&lt;&#x2F;p&gt;
&lt;p&gt;I haven&#x27;t gotten anywhere with this yet, but it&#x27;s a wish for the future.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;multiple-libraries&quot;&gt;Multiple libraries&lt;a class=&quot;zola-anchor&quot; href=&quot;#multiple-libraries&quot; aria-label=&quot;Anchor link for: multiple-libraries&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I want to support multiple &#x27;repositories of information&#x27;; I decided to call them &#x27;libraries&#x27;.&lt;&#x2F;p&gt;
&lt;p&gt;Rather than being completely separate notebooks, though, they should all be merged into one unified view when desired.
The tool should let you enable&#x2F;disable them at will.&lt;&#x2F;p&gt;
&lt;p&gt;A crucial point is that it should be possible to support different devices having different libraries (e.g. my work machine may only have relevant notes on there) and they should be able to go &#x27;missing&#x27; without causing any harm (so that you can use an encrypted filesystem to store private notes and only unlock it when you need access).&lt;&#x2F;p&gt;
&lt;p&gt;I note that it may be interesting to support a device having a partial&#x2F;limited portion of a library, especially once it&#x27;s possible to embed media like photos&#x2F;videos, so that e.g. a PinePhone with limited storage can be used to some extent, even if it means not having access to bulky media (or having to pull it over the network from a &#x27;full&#x27; device).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-began-6-months-ago&quot;&gt;How it began (6 months ago)&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-it-began-6-months-ago&quot; aria-label=&quot;Anchor link for: how-it-began-6-months-ago&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;!-- 453c06d24b4aff0396c058954c9f4c7ae6987434 --&gt;
&lt;h3 id=&quot;causal-trees-as-a-crdt-conflict-free-replicated-data-type&quot;&gt;Causal trees as a CRDT (Conflict-free Replicated Data Type)&lt;a class=&quot;zola-anchor&quot; href=&quot;#causal-trees-as-a-crdt-conflict-free-replicated-data-type&quot; aria-label=&quot;Anchor link for: causal-trees-as-a-crdt-conflict-free-replicated-data-type&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I finally started this project after reading &lt;a href=&quot;http:&#x2F;&#x2F;archagon.net&#x2F;blog&#x2F;2018&#x2F;03&#x2F;24&#x2F;data-laced-with-history&#x2F;&quot;&gt;&lt;em&gt;Data Laced with History: Causal Trees &amp;amp; Operational CRDTs&lt;&#x2F;em&gt; by &lt;em&gt;Archagon&lt;&#x2F;em&gt; (blog post)&lt;&#x2F;a&gt;.
I had been keeping an eye on CRDTs for a while and saw this blog post as a way to finally get my hands dirty — by implementing one!
It&#x27;s a little bit lengthy and requires a bit of focus to get through, but I do highly recommend it. (This section may not make much sense if you (understandably) don&#x27;t read the blog post, so skip if desired.)&lt;&#x2F;p&gt;
&lt;p&gt;I learnt a lot by actually writing some code; some of the trickier parts in the blog post started to click into place.
Once I had something basic that looked plausible, I took a bit of time to make it generic so that the causal tree logic itself was able to accept any &#x27;atom&#x27; type (and they could have their own rules about priority, so that it was possible to support the special ordering rules for atoms like &#x27;Delete&#x27; and some others that would be useful for a rich text schema later on).&lt;&#x2F;p&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Too much detail about causal trees and the types of atoms I used. (Click to expand)&lt;&#x2F;b&gt;&lt;&#x2F;summary&gt;
&lt;p&gt;It was highly satisfying to have the basics working (insert, delete and the merge of two documents), even just in plain text at a command-line prompt.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;PlainTextCausalTreeValue &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; The root of the document.
&lt;&#x2F;span&gt;&lt;span&gt;    Null&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; A text character; 1 unit wide. Comes after the atom that it points to.
&lt;&#x2F;span&gt;&lt;span&gt;    Character&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;char&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; Delete the unit to which this atom points.
&lt;&#x2F;span&gt;&lt;span&gt;    Delete&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Illustration of plain-text causal tree atoms and how they fit together:&lt;&#x2F;strong&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2022-05-25-forgot10-devlog&#x2F;plaintext_cts.png&quot; alt=&quot;Diagram of plain text causal tree atoms&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Extending this to rich text required a bit of thought.
I decided to have &#x27;descriptors&#x27; for marks and embeds; these descriptors precede the content of the document and only store attributes (e.g. the target of a link). This allows you to move the embeds or start and end of a mark, without having to copy all the attributes and duplicate the data.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;EmbedHere&lt;&#x2F;code&gt;, &lt;code&gt;MarkStart&lt;&#x2F;code&gt; and &lt;code&gt;MarkEnd&lt;&#x2F;code&gt; are added as extra kinds of atoms that can be used within the content of the document, along with &lt;code&gt;Character&lt;&#x2F;code&gt; (and &lt;code&gt;Delete&lt;&#x2F;code&gt;!).
They have an additional reference to their descriptor.&lt;&#x2F;p&gt;
&lt;p&gt;The priority rules are extended so that the descriptors come before the content of the document and so that &lt;code&gt;Attribute&lt;&#x2F;code&gt;s cling tightly to their parent descriptors (but no tighter than the &lt;code&gt;Delete&lt;&#x2F;code&gt;s). This allows you to read the document&#x27;s atoms sequentially and populate a map of all the descriptors and their attributes easily.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;RichTextCausalTreeValue &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; The root of the document.
&lt;&#x2F;span&gt;&lt;span&gt;    Null&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; A text character; 1 unit wide. Comes after the atom that it points to.
&lt;&#x2F;span&gt;&lt;span&gt;    Character&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;char&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; An anchor positioning an embed, with reference to an embed descriptor.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; Embeds are 1 unit wide. Comes after the atom that it points to.
&lt;&#x2F;span&gt;&lt;span&gt;    EmbedHere&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;StorageAtomLocator&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; Delete the unit to which this atom points.
&lt;&#x2F;span&gt;&lt;span&gt;    Delete&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; Start a mark.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; 1 unit wide. Comes after the atom that it points to.
&lt;&#x2F;span&gt;&lt;span&gt;    MarkStart&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;StorageAtomLocator&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; End a mark.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; 1 unit wide. Comes after the atom that it points to.
&lt;&#x2F;span&gt;&lt;span&gt;    MarkEnd&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;StorageAtomLocator&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; Out-of-band descriptor for a Mark. Points to Null.
&lt;&#x2F;span&gt;&lt;span&gt;    MarkDescriptor&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;MarkType&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; Out-of-band descriptor for an Embed. Points to Null.
&lt;&#x2F;span&gt;&lt;span&gt;    EmbedDescriptor&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;EmbedType&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; An attribute on a Mark or an Embed. Points to a MarkDescriptor or EmbedDescriptor.
&lt;&#x2F;span&gt;&lt;span&gt;    Attribute&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;Attribute&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; AttributeValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;#[&lt;&#x2F;span&gt;&lt;span&gt;derive&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;Copy&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; Clone&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; Debug&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; PartialOrd&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; PartialEq&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; Ord&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; Eq&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;RichTextPriority &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    NormalText&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    NormalMark&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    HugAsChildValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    HugToDeleteParent&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AtomBody &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;RichTextCausalTreeValue &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Priority &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; RichTextPriority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;priority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;Priority &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            RichTextCausalTreeValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;Null &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;RichTextPriority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;NormalText&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            RichTextCausalTreeValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;Character&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(_) =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;RichTextPriority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;NormalText&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            RichTextCausalTreeValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;EmbedHere&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(_) =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;RichTextPriority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;NormalText&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            RichTextCausalTreeValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;Delete &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;RichTextPriority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;HugToDeleteParent&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            RichTextCausalTreeValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;MarkStart&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(_) =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;RichTextPriority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;NormalText&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            RichTextCausalTreeValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;MarkEnd&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(_) =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;RichTextPriority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;NormalText&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            RichTextCausalTreeValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;MarkDescriptor&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(_) =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;RichTextPriority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;NormalMark&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            RichTextCausalTreeValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;EmbedDescriptor&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(_) =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;RichTextPriority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;NormalMark&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            RichTextCausalTreeValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;Attribute&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(_, _) =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;RichTextPriority&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;HugAsChildValue&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Illustration of rich text causal tree atoms and how they fit together:&lt;&#x2F;strong&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2022-05-25-forgot10-devlog&#x2F;richtext_cts.png&quot; alt=&quot;Diagram of rich text causal tree atoms&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Thoughts about supporting undo&#x2F;redo made me also allow cancelling a deletion by deleting the &lt;code&gt;Delete&lt;&#x2F;code&gt; atom. (In this case, to undo some operation, you just need to track which atoms are created for that operation and create &lt;code&gt;Delete&lt;&#x2F;code&gt; atoms for them. Redo is the same as undoing the undo!)
Because &lt;code&gt;Delete&lt;&#x2F;code&gt; clings the tightest to its cause&#x2F;parent atom, you can easily check whether a particular atom is &#x27;not deleted&#x27;, &#x27;deleted&#x27;, &#x27;deleted but the delete was deleted&#x27; and so on — merely by more or less scanning ahead and counting the number of &lt;code&gt;Delete&lt;&#x2F;code&gt;s that follow (with the main exception that you need to be careful not to double-count concurrent &lt;code&gt;Delete&lt;&#x2F;code&gt;s: two people deleting the same thing doesn&#x27;t mean it should come back!).
I&#x27;m not sure whether the idea of being able to un-delete is overall sensible or not (I haven&#x27;t used this enough in practice), but I thought it was interesting nonetheless.&lt;&#x2F;p&gt;
&lt;&#x2F;details&gt;
&lt;h3 id=&quot;rolling-my-own-rich-text-editor&quot;&gt;Rolling my own rich text editor&lt;a class=&quot;zola-anchor&quot; href=&quot;#rolling-my-own-rich-text-editor&quot; aria-label=&quot;Anchor link for: rolling-my-own-rich-text-editor&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I was keen on using the &lt;a href=&quot;https:&#x2F;&#x2F;www.egui.rs&#x2F;&quot;&gt;egui&lt;&#x2F;a&gt; GUI library. I had used it for a couple of projects and it felt pleasant to write code with it.&lt;&#x2F;p&gt;
&lt;p&gt;This lead to weeks of rolling my own rich text editor in egui (using the causal trees as a backing representation). It was a fun exercise and I got a half-decent starting point working!
That said, it was quite tricky to come up with a simple enough plan to efficiently update the rendering state for incremental changes.&lt;&#x2F;p&gt;
&lt;p&gt;I got something decent working for a line-based editor — you can just follow the causes of atoms until you find the start of the line and then reflow only one or two lines at a time!
On the other hand, I really struggled to come up with an efficient method to lay out and render rich, nested structures (with incremental updates) without shedloads of effort or fiddly code and I had quite a few dead ends.&lt;&#x2F;p&gt;
&lt;p&gt;Eventually, I realised that, if I wasn&#x27;t careful, this would be an uphill battle for the entire project, especially as this is for a media-rich application and that &lt;em&gt;just maybe&lt;&#x2F;em&gt; it wasn&#x27;t the best idea to write my own layout engine when you get one for free in a modern web browser. I also knew that supporting photos and videos would likely involve quite a bit of fiddly code to interface with decoders and manage GPU textures, whereas modern HTML is able to handle media with relative ease.&lt;&#x2F;p&gt;
&lt;p&gt;In short: egui is great, but HTML and a web browser engine really wins for this use case, at least if roll-it-yourself complexity is on the list of concerns.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-new-plan-3-months-ago&quot;&gt;A new plan (3 months ago)&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-new-plan-3-months-ago&quot; aria-label=&quot;Anchor link for: a-new-plan-3-months-ago&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;!-- 11d7a67ec565c382a78ea416007087078d6b478f --&gt;
&lt;p&gt;Realising how much I&#x27;d gotten myself into and wishing for faster progress, I made the decision to abandon the hopes and dreams of a native application and switch over to doing something in the browser.&lt;&#x2F;p&gt;
&lt;p&gt;I learnt how to use Vue.js and how to embed my web application into a webview using &lt;a href=&quot;https:&#x2F;&#x2F;tauri.studio&quot;&gt;Tauri&lt;&#x2F;a&gt; (like Electron, but lighter weight) so that it could eventually become &#x27;close enough&#x27; to a plain desktop application.
I learnt how to write TypeScript because I thought it&#x27;d be sensible to have a little bit more rigidity and safety than JavaScript typically imposes.&lt;&#x2F;p&gt;
&lt;p&gt;On the frontend, I picked up the &lt;a href=&quot;https:&#x2F;&#x2F;tiptap.dev&quot;&gt;Tiptap&lt;&#x2F;a&gt; rich text editor and used its built-in support for the &lt;a href=&quot;https:&#x2F;&#x2F;docs.yjs.dev&quot;&gt;Yjs CRDT&lt;&#x2F;a&gt;. The frontend connects to a daemon over a web socket in order to load and store data; in the future, it may be possible to use Tauri&#x27;s messaging interface, but I wanted to leave the doors open to using Forgot10 outside of Tauri as well in order to support some future ideas.&lt;&#x2F;p&gt;
&lt;!--
The Forgot10 daemon is responsible for notifying any connected clients (frontends) about document updates, storing data in SQLite databases and synchronising with other peers.
Other peers are discovered using mDNS (by publishing and listening for advertisements with Avahi). Peers are identified by the hash of their TLS certificate, which they also advertise in their mDNS advertisement (along with a single-letter &#x27;priority indicator&#x27;).

To prevent the chance for peers to try to connect to each other at the same time, a simple rule determines which peer should initiate the connection: the peer with the highest `(priority indicator, TLS certificate hash)` tuple (lexicographically compared) is the one to connect.
The purpose of the priority indicator is to create a simple mechanism for e.g. a mobile phone to favour being responsible for outbound connections and e.g. a server to favour accepting inbound connections.
The connections themselves are made through QUIC. There&#x27;s no particular reason for that, other than that I had some useful code lying around for this purpose and QUIC&#x27;s support for multiple streams was useful as it meant that I didn&#x27;t have to multiplex a TCP channel myself.
--&gt;
&lt;p&gt;The Forgot10 daemon is responsible for notifying any connected clients (frontends) about document updates, storing data in SQLite databases and synchronising with other peers.
Other peers are discovered using mDNS (by publishing and listening for advertisements with Avahi). Peers are identified by the hash of their TLS certificate, which they also advertise in their mDNS advertisement (so they can be identified before trying to connect).
All changes to documents are pushed through an event bus (which allows listeners, such as syncing peers and connected clients, to receive updates &lt;strong&gt;in real time&lt;&#x2F;strong&gt;) and into a buffer that will occasionally flush them to disk. (The disk flushing operation just stores new fragments of the document and will occasionally coalesce them together if there are too many.)&lt;&#x2F;p&gt;
&lt;p&gt;All of the documents&#x27; metadata are stored in the Yjs documents themselves, but a separate &#x27;derivation&#x27; pass extracts or &lt;em&gt;derives&lt;&#x2F;em&gt; metadata (such as the title of a Page, or the timestamp of a Stream entry) from the Yjs document and caches this in the SQLite database. Caching the metadata in the SQLite database is crucial as it prevents us from needing to load all the Yjs documents in order to display a list of pages or in order to sort Stream entries chronologically. The derivation pass is also the time at which special nodes like reminders can be tracked in the database so we actually get notified in the future, or (one day) when the search index could be updated with the document&#x27;s content.&lt;&#x2F;p&gt;
&lt;p&gt;As the Rust port of Yjs is not ready enough yet, I resorted to running a Node.js process to load documents into Yjs on the daemon and perform various operations on them. (I tried several embeddable JavaScript interpreters but alas, this was the best solution I could get to work for now. At least it doesn&#x27;t segfault :)) Once the Rust port of Yjs is ready, I&#x27;m looking forward to ripping this out.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Rough architecture of Forgot10 at the moment:&lt;&#x2F;strong&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2022-05-25-forgot10-devlog&#x2F;f10_rough_architecture.png&quot; alt=&quot;Diagram of Forgot10&amp;#39;s architecture.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;it-s-a-start&quot;&gt;It&#x27;s a start...&lt;a class=&quot;zola-anchor&quot; href=&quot;#it-s-a-start&quot; aria-label=&quot;Anchor link for: it-s-a-start&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;What I have at the moment is a multi-document rich-text editor, that works entirely offline but then will also work as a peer-to-peer collaborative editor, with impressively-live updates.&lt;&#x2F;p&gt;
&lt;p&gt;Seeing it work for the first time and then having the chance to use it with someone else was very satisfying. Yet, frankly, most of what I&#x27;ve written so far has just been glue!
It just goes to show what high-quality libraries are around these days and the cool things you can do by gluing them together.&lt;&#x2F;p&gt;
&lt;p&gt;The cherries on top for my project have yet to come, but the foundation is there. What I&#x27;ll be taking a look at doing soon is:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Using it in practice and trying to get a feel for what&#x27;s nice and what&#x27;s awkward to use.&lt;&#x2F;li&gt;
&lt;li&gt;A LOT of cleaning up...
&lt;ul&gt;
&lt;li&gt;The document store could be a little more self-contained and have an easier interface to use. It&#x27;s tempting to pull it out into a library that can be reused for other projects, if that could work without making it too difficult to track the extras.&lt;&#x2F;li&gt;
&lt;li&gt;The GUI frontend needs some love. There are a few bugs I&#x27;m aware of that I&#x27;ve left until now, in trying to focus on getting &lt;em&gt;something&lt;&#x2F;em&gt; working.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Implementing reminders properly: pull them out of the document during derivation; show an overview of upcoming reminders; and actually notify when it&#x27;s time.&lt;&#x2F;li&gt;
&lt;li&gt;Search.&lt;&#x2F;li&gt;
&lt;li&gt;Media (photographs etc).&lt;&#x2F;li&gt;
&lt;li&gt;Export to HTML, to PDF and to Markdown. (I have experience with this from a prior project where I used ProseMirror, the library Tiptap is based on, and exported to HTML &amp;amp; PDF when necessary.)&lt;&#x2F;li&gt;
&lt;li&gt;Improvements to the build scripts and packaging so that it&#x27;s easy to build and install. (There are a few manual steps at the moment, which arguably Cargo should be doing for you.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There&#x27;s a lot to sort out before I&#x27;ll feel comfortable opening it up for others to use (or even to read the code). That said, this is a serious project that I intend to keep around for the long term (it&#x27;s not just an experiment or a proof of concept), so given time, I will get there.&lt;&#x2F;p&gt;
</content>
	</entry>
	
	<entry xml:lang="en">
		<title>An analogy between Rust&#x27;s closures and structs</title>
		<published>2022-04-11T00:00:00+00:00</published>
		<updated>2022-04-11T00:00:00+00:00</updated>
		<link rel="alternate" href="https://o.librepush.net/blog/2022-04-11-analogy-closures-structs/" type="text/html"/>
		<id>https://o.librepush.net/blog/2022-04-11-analogy-closures-structs/</id>
		<content type="html">&lt;p&gt;Rust&#x27;s closures may use funny syntax and may seem a bit magical, but they&#x27;re actually little more than syntax sugar for a struct with a method.
This conceptual correspondence might help you to transfer your intuition about structs to closures.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-simple-analogy&quot;&gt;A simple analogy&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-simple-analogy&quot; aria-label=&quot;Anchor link for: a-simple-analogy&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;A closure such as...&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;42&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;closure &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= |&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;| -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; x &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; z &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;84&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;could be approximated as...&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyClosure1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    z&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;MyClosure1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; x &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;* &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;z &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;42&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; closure &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; MyClosure1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; z &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;84&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Notably, we create fields for the &#x27;captured&#x27; local variables, using &lt;code&gt;self&lt;&#x2F;code&gt; to access them within the &lt;code&gt;call&lt;&#x2F;code&gt; method that represents the closure&#x27;s body.&lt;&#x2F;p&gt;
&lt;p&gt;Although we now need to write &lt;code&gt;closure.call(2)&lt;&#x2F;code&gt; instead of &lt;code&gt;closure(2)&lt;&#x2F;code&gt; to call our &#x27;closure&#x27;, this starts to paint a picture. It does, however, leave some details behind.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;capturing-local-scope&quot;&gt;Capturing local scope&lt;a class=&quot;zola-anchor&quot; href=&quot;#capturing-local-scope&quot; aria-label=&quot;Anchor link for: capturing-local-scope&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Using a &lt;code&gt;Copy&lt;&#x2F;code&gt; type such as &lt;code&gt;i32&lt;&#x2F;code&gt; was perhaps not the best example.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s take a look at some closures that actually hold on to the local &#x27;scope&#x27;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;immutable-references&quot;&gt;Immutable references&lt;a class=&quot;zola-anchor&quot; href=&quot;#immutable-references&quot; aria-label=&quot;Anchor link for: immutable-references&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;From the land of contrived examples, I pluck out this closure which will return the sum of some numbers, multiplied by the provided factor.
The closure needs to borrow the &lt;code&gt;Vec&lt;&#x2F;code&gt; of &lt;code&gt;numbers&lt;&#x2F;code&gt; so that it can sum them on demand.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; = &lt;&#x2F;span&gt;&lt;span&gt;vec!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;];
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;sum_times &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= |&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;factor&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;| -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span&gt;sum&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;() *&lt;&#x2F;span&gt;&lt;span&gt; factor
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;sum_times&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Expressed in our struct analogy, this would look something like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyClosure2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;MyClosure2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;factor&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span&gt;sum&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;() *&lt;&#x2F;span&gt;&lt;span&gt; factor &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; = &lt;&#x2F;span&gt;&lt;span&gt;vec!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;];
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; sum_times &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; MyClosure2 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;numbers &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;sum_times&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that the struct is now parameterised with a lifetime (because it holds a reference to &lt;code&gt;numbers&lt;&#x2F;code&gt;), but is otherwise much the same.
This boilerplate really makes you grateful for the syntax sugar that is the &lt;code&gt;|| {}&lt;&#x2F;code&gt; syntax for closures...&lt;&#x2F;p&gt;
&lt;h3 id=&quot;mutable-references&quot;&gt;Mutable references&lt;a class=&quot;zola-anchor&quot; href=&quot;#mutable-references&quot; aria-label=&quot;Anchor link for: mutable-references&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Much the same, except you sprinkle &lt;code&gt;mut&lt;&#x2F;code&gt; in the places that make sense. Also note that &lt;code&gt;call&lt;&#x2F;code&gt; now takes &lt;code&gt;&amp;amp;mut self&lt;&#x2F;code&gt;, so in effect would need to be represented by our equivalent of &lt;code&gt;FnMut&lt;&#x2F;code&gt; (let&#x27;s get to that later).&lt;&#x2F;p&gt;
&lt;p&gt;Before:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;new&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;push_if_even &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= |&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;| {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; x &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;% &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;push_if_even&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;push_if_even&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;push_if_even&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span&gt;vec!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;]);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyClosure3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;MyClosure3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; x &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;% &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;new&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; push_if_even &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; MyClosure3 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; numbers &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;push_if_even&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;push_if_even&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;push_if_even&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span&gt;vec!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;]);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that this example requires non-lexical lifetimes: &lt;code&gt;push_if_even&lt;&#x2F;code&gt; has to die before we get to the assertion, since otherwise &lt;code&gt;push_if_even&lt;&#x2F;code&gt;&#x27;s mutable reference would prevent us from being able to read &lt;code&gt;numbers&lt;&#x2F;code&gt; (due to borrow checking rules).
If you wish, try adding &lt;code&gt;push_if_even.call(5);&lt;&#x2F;code&gt; after the assertion; in either case (closure-style or struct-style), the example will no longer compile :-).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-move-keyword&quot;&gt;The move keyword&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-move-keyword&quot; aria-label=&quot;Anchor link for: the-move-keyword&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;So far, the examples haven&#x27;t &lt;em&gt;moved&lt;&#x2F;em&gt; anything into the closure (&lt;code&gt;i32&lt;&#x2F;code&gt; is &lt;code&gt;Copy&lt;&#x2F;code&gt; and doesn&#x27;t need moving; the other examples have shown references).
Moving things into the closure is a necessity when we want the closure to outlive its scope — often I find myself needing that when spawning threads or async tasks.&lt;&#x2F;p&gt;
&lt;p&gt;For completeness, let&#x27;s show an example here — using the example of returning the closure to justify moving things into it.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;make_closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; impl Fn&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;move &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;| {
&lt;&#x2F;span&gt;&lt;span&gt;        numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; closure &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;make_closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;vec!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;40&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;90&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;]);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;40&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(Please forgive the pointlessness of the closure body!)&lt;&#x2F;p&gt;
&lt;p&gt;Note how we have to use &lt;code&gt;impl Fn(usize) -&amp;gt; i32&lt;&#x2F;code&gt; as the return type — closures don&#x27;t have a type that you can explicitly write, so we either have to do this or resort to dynamic dispatch with a &lt;code&gt;Box&amp;lt;dyn Fn(usize) -&amp;gt; i32&amp;gt;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Carrying this example over to our analogy, we have:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyClosure4 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;MyClosure4 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;make_closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; MyClosure4 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    MyClosure4 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; numbers &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; closure &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;make_closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;vec!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;40&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;90&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;]);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;40&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In short: things being moved into the &#x27;closure&#x27; is the same as a &lt;code&gt;struct&lt;&#x2F;code&gt; field that &lt;strong&gt;isn&#x27;t&lt;&#x2F;strong&gt; a reference!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-fn-fnmut-and-fnonce-traits&quot;&gt;The &lt;code&gt;Fn&lt;&#x2F;code&gt;, &lt;code&gt;FnMut&lt;&#x2F;code&gt; and &lt;code&gt;FnOnce&lt;&#x2F;code&gt; traits&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-fn-fnmut-and-fnonce-traits&quot; aria-label=&quot;Anchor link for: the-fn-fnmut-and-fnonce-traits&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;This analogy is perhaps most useful once you consider the &lt;code&gt;Fn&lt;&#x2F;code&gt;, &lt;code&gt;FnMut&lt;&#x2F;code&gt; and &lt;code&gt;FnOnce&lt;&#x2F;code&gt; traits. What exactly do they mean?&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s make our own de-sugared traits for those. Note that, for simplicity, we&#x27;ll restrict ourselves to single-parameter functions.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;MyFn&amp;lt;A, R&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;arg&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; A&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;MyFnMut&amp;lt;A, R&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;arg&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; A&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;MyFnOnce&amp;lt;A, R&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;arg&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; A&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The key message to take home here is that the only thing that changes is whether the self-parameter is &lt;code&gt;&amp;amp;self&lt;&#x2F;code&gt;, &lt;code&gt;&amp;amp;mut self&lt;&#x2F;code&gt; or &lt;code&gt;self&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;We then would want to implement these traits for our ad-hoc struct-closures — for example, using the last example:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;MyFn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MyClosure4 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can now use &lt;code&gt;impl MyFn&amp;lt;usize, i32&amp;gt;&lt;&#x2F;code&gt; in lieu of &lt;code&gt;impl Fn(usize) -&amp;gt; i32&lt;&#x2F;code&gt; in cases where we want to accept an arbitrary kind of closure.
We can also use &lt;code&gt;Box&amp;lt;MyFn&amp;lt;usize, i32&amp;gt;&amp;gt;&lt;&#x2F;code&gt; in lieu of &lt;code&gt;Box&amp;lt;Fn(usize) -&amp;gt; i32&amp;gt;&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;implied-traits&quot;&gt;Implied traits&lt;a class=&quot;zola-anchor&quot; href=&quot;#implied-traits&quot; aria-label=&quot;Anchor link for: implied-traits&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;One thing worth noting is that, in plain Rust, an &lt;code&gt;Fn&lt;&#x2F;code&gt; is also an &lt;code&gt;FnMut&lt;&#x2F;code&gt; and an &lt;code&gt;FnOnce&lt;&#x2F;code&gt;. (Further, an &lt;code&gt;FnMut&lt;&#x2F;code&gt; is also an &lt;code&gt;FnOnce&lt;&#x2F;code&gt;.)
We &lt;em&gt;could&lt;&#x2F;em&gt; make this easier in our system by automatically implementing those, as follows...&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;A, R, T&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span&gt;MyFn&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;A, R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;MyFnMut&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;A, R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;T &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;arg&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; A&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; R &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        MyFn&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;A, R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;::&lt;&#x2F;span&gt;&lt;span&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; arg&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;A, R, T&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span&gt;MyFnMut&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;A, R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;MyFnOnce&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;A, R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;T &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;arg&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; A&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; R &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        MyFnMut&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;A, R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;::&lt;&#x2F;span&gt;&lt;span&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; arg&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(In practice, this might get annoying since all the methods have the same name, leading to the unfortunate need to disambiguate which &lt;code&gt;call&lt;&#x2F;code&gt; we mean (as you can even see in the implementation!). Perhaps they should be called &lt;code&gt;call&lt;&#x2F;code&gt;, &lt;code&gt;call_ref&lt;&#x2F;code&gt; and &lt;code&gt;call_mut&lt;&#x2F;code&gt; or something?)&lt;&#x2F;p&gt;
&lt;h3 id=&quot;under-the-bonnet-plain-rust-s-traits&quot;&gt;Under the bonnet: plain Rust&#x27;s traits&lt;a class=&quot;zola-anchor&quot; href=&quot;#under-the-bonnet-plain-rust-s-traits&quot; aria-label=&quot;Anchor link for: under-the-bonnet-plain-rust-s-traits&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Out of interest, if you went to look at &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;blob&#x2F;43998d544108b613c4631ed60249880167e8aafc&#x2F;library&#x2F;core&#x2F;src&#x2F;ops&#x2F;function.rs#L74-L78=&quot;&gt;the definition of &lt;code&gt;Fn&lt;&#x2F;code&gt;, &lt;code&gt;FnMut&lt;&#x2F;code&gt; and &lt;code&gt;FnOnce&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, you&#x27;d find something not &lt;em&gt;entirely&lt;&#x2F;em&gt; dissimilar to what we&#x27;ve written above:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;Fn&amp;lt;Args&amp;gt;: FnMut&amp;lt;Args&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; Performs the call operation.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;#[&lt;&#x2F;span&gt;&lt;span&gt;unstable&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;feature &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;fn_traits&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;span&gt; issue &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;29625&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;)]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;extern &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;rust-call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;args&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Args&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;Output&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;FnMut&amp;lt;Args&amp;gt;: FnOnce&amp;lt;Args&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; Performs the call operation.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;#[&lt;&#x2F;span&gt;&lt;span&gt;unstable&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;feature &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;fn_traits&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;span&gt; issue &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;29625&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;)]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;extern &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;rust-call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call_mut&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;args&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Args&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;Output&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;FnOnce&amp;lt;Args&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; The returned type after the call operator is used.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;#[&lt;&#x2F;span&gt;&lt;span&gt;lang &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;fn_once_output&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;#[&lt;&#x2F;span&gt;&lt;span&gt;stable&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;feature &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;fn_once_output&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;span&gt; since &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;1.12.0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;)]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Output&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5f6364;&quot;&gt;&#x2F;&#x2F;&#x2F; Performs the call operation.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;#[&lt;&#x2F;span&gt;&lt;span&gt;unstable&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;feature &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;fn_traits&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;span&gt; issue &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;29625&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;)]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;extern &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99c794;&quot;&gt;rust-call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call_once&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;args&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Args&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;Output&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Some of that is magic; magic which is out of scope for this document which was intended to make things &lt;em&gt;simpler&lt;&#x2F;em&gt;, but this is just to show that the approximation we&#x27;re talking about in this post isn&#x27;t that poor.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;a class=&quot;zola-anchor&quot; href=&quot;#summary&quot; aria-label=&quot;Anchor link for: summary&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;If you imagine closures as just being structs with a trait implementation, you won&#x27;t go far wrong.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;d appreciate a source file with tests demonstrating the code samples included in this post, &lt;a href=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;2022-04-11-analogy-closures-structs&#x2F;main.rs&quot;&gt;click here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Otherwise, this table summarises the analogy:&lt;&#x2F;p&gt;
&lt;div class=&quot;table_wrapper&quot;&gt;&lt;table class=&quot;pure-table pure-table-bordered&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plain Rust closures&lt;&#x2F;th&gt;
&lt;th&gt;&#x27;Struct-closures&#x27;&lt;&#x2F;th&gt;
&lt;&#x2F;tr&gt;
&lt;&#x2F;thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;code&gt;Fn(A) -&amp;gt; R&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;FnMut(A) -&amp;gt; R&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;FnOnce(A) -&amp;gt; R&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;td&gt;
&lt;td&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;MyFn&amp;lt;A, R&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;arg&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; A&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;MyFnMut&amp;lt;A, R&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;arg&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; A&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;MyFnOnce&amp;lt;A, R&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;arg&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; A&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; R&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;42&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;td&gt;
&lt;td&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;42&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; = &lt;&#x2F;span&gt;&lt;span&gt;vec!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;];
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;sum_times &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= |&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;factor&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;| -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span&gt;sum&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;() *&lt;&#x2F;span&gt;&lt;span&gt; factor
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;td&gt;
&lt;td&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyClosure2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;MyClosure2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;factor&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span&gt;sum&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;() *&lt;&#x2F;span&gt;&lt;span&gt; factor
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; = &lt;&#x2F;span&gt;&lt;span&gt;vec!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;];
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; sum_times &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; MyClosure2 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;numbers &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;new&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;push_if_even &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;= |&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;| {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; x &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;% &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;td&gt;
&lt;td&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyClosure3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; MyFnMut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MyClosure3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; x &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;% &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;2 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt; = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;new&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; push_if_even &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; MyClosure3 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; numbers &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;make_closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; impl Fn&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;move &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;| {
&lt;&#x2F;span&gt;&lt;span&gt;        numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;td&gt;
&lt;td&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b2c2f;color:#cccece;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyClosure4 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;MyFnOnce &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MyClosure4 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ec5f67;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#6699cc;&quot;&gt;make_closure&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f99157;&quot;&gt;numbers&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fac863;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c594c5;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;&amp;gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; MyClosure4 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    MyClosure4 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; numbers &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5fb3b3;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;
&lt;&#x2F;table&gt;&lt;&#x2F;div&gt;
</content>
	</entry>
	
	<entry xml:lang="en">
		<title>RSVR: Experimental Virtual Reality in Rust</title>
		<published>2018-12-21T00:00:00+00:00</published>
		<updated>2018-12-21T00:00:00+00:00</updated>
		<link rel="alternate" href="https://o.librepush.net/blog/20181221-rsvr/" type="text/html"/>
		<id>https://o.librepush.net/blog/20181221-rsvr/</id>
		<content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;a class=&quot;zola-anchor&quot; href=&quot;#introduction&quot; aria-label=&quot;Anchor link for: introduction&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Virtual_reality&quot;&gt;Virtual Reality&lt;&#x2F;a&gt; is an interesting concept but all current implementations (at least the ones that I found) are inaccessible, either due to their dependency on non-free software (e.g. Google Cardboard) or price (e.g. HTC Vive).&lt;&#x2F;p&gt;
&lt;p&gt;I decided to start my own project, called &lt;strong&gt;RSVR&lt;&#x2F;strong&gt; (Rust VR), with the following goals in mind:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Uses inexpensive or already-owned hardware&lt;&#x2F;li&gt;
&lt;li&gt;No dependencies on non-free software&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;With these restrictions in mind, it seemed quite clear that the most accessible route would be to make use of existing head-mounts for mobile phones, such as those used with Google Cardboard. I went for an &lt;em&gt;Intempo&lt;&#x2F;em&gt;-branded VR head-mount that was available for about 10 pounds at a relatively-local bargain shop.&lt;&#x2F;p&gt;
&lt;p&gt;Because these head-mounts require mobile phones, I will eventually need to get visuals on an Android mobile phone. I should note that Android development is not something that I have done in about 4 years and it is also something I would like to avoid, because I hope to cease usage of Android in the next year or so! (After all, I want to clear RSVR&#x27;s dependency on non-free software, which all Android phones I am aware of use. I also find the platform lacking in flexibility.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;proof-of-concept&quot;&gt;Proof of Concept&lt;a class=&quot;zola-anchor&quot; href=&quot;#proof-of-concept&quot; aria-label=&quot;Anchor link for: proof-of-concept&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;To test the feasibility of the concept, I first modified one of my university graphics course&#x27;s practicals to render stereoscopically, yielding this visual:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;20181221-rsvr&#x2F;gfx_practical_stereo.png&quot; alt=&quot;Proof of Concept Visual&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When viewed in a gallery application on a phone, my eyes were able to focus on it, though I could feel that tweaks would need to be made for comfort. Still, this was a good sign, so I decided it would be fine to start the RSVR project.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;research&quot;&gt;Research&lt;a class=&quot;zola-anchor&quot; href=&quot;#research&quot; aria-label=&quot;Anchor link for: research&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The head-mount I purchased did not come with any parametric information, but I did find a QR code for a different Google Cardboard viewer online.&lt;&#x2F;p&gt;
&lt;p&gt;It turns out that these QR codes contain a &lt;code&gt;goo.gl&lt;&#x2F;code&gt; URL shortener URL, which then redirects to a Google URL which contains a base64-encoded parameter &lt;code&gt;p&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Knowing Google, I assumed they would be using Google Protocol Buffers to store this information, which would make it non-trivial to read without the protobuf definition file. Luckily, I was able to find &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jetwarp&#x2F;cardboard-qr-to-json&#x2F;blob&#x2F;master&#x2F;CardboardDevice.proto&quot;&gt;CardboardDevice.proto&lt;&#x2F;a&gt; in a GitHub repository. It even contains descriptions of the fields!&lt;&#x2F;p&gt;
&lt;p&gt;I used the &lt;code&gt;protoc&lt;&#x2F;code&gt; tool on the &lt;code&gt;CardboardDevice.proto&lt;&#x2F;code&gt; file to generate Python code to decode the viewer profile, then dumped the information into my notebook:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b2c2f;color:#cccece;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;tmp$ mkdir pyout
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;tmp$ protoc --python_out=pyout CardboardDevice.proto 
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;tmp$ cd pyout&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;tmp&#x2F;pyout$ python3
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; import CardboardDevice_pb2
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; profile = base64.decodebytes(b&amp;quot;[… insert base64-encoded profile here …]&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; devparams = CardboardDevice_pb2.DeviceParams()
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; devparams.ParseFromString(profile)
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; devparams
&lt;&#x2F;span&gt;&lt;span&gt;[… profile values were dumped here …]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;development&quot;&gt;Development&lt;a class=&quot;zola-anchor&quot; href=&quot;#development&quot; aria-label=&quot;Anchor link for: development&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;At this point, I had an inkling that I would need to write a few crates:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rsvr_profile&lt;&#x2F;code&gt;: Crate to parse profile definitions, which I decided to keep in RON (Rusty Object Notation) for now.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;rsvr_opengl&lt;&#x2F;code&gt;: Crate to be used by OpenGL applications for stereoscopic rendering.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;rsvr_tweak&lt;&#x2F;code&gt;: Interactive tool to tweak the profile definition in real-time until it feels &#x27;right&#x27;. (I would need this because my viewer did not come with a profile of its own.)&lt;&#x2F;li&gt;
&lt;li&gt;(potentially in the future) &lt;code&gt;rsvr_vulkan&lt;&#x2F;code&gt;: Crate to be used by Vulkan applications.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I decided that I would first start by writing &lt;code&gt;rsvr_tweak&lt;&#x2F;code&gt; to load and render Wavefront OBJ model files monoscopically. I found &lt;a href=&quot;https:&#x2F;&#x2F;opengameart.org&#x2F;content&#x2F;covered-well&quot;&gt;this well&lt;&#x2F;a&gt; on &lt;em&gt;OpenGameArt.org&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;20181221-rsvr&#x2F;mono_well.png&quot; alt=&quot;rsvr_tweak rendering a well, monoscopically&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;one-more-eye&quot;&gt;One More Eye&lt;a class=&quot;zola-anchor&quot; href=&quot;#one-more-eye&quot; aria-label=&quot;Anchor link for: one-more-eye&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I implemented stereoscopic rendering and also added keybindings to change the viewer parameters on the fly.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;o.librepush.net&#x2F;blog&#x2F;20181221-rsvr&#x2F;stereo_well.png&quot; alt=&quot;rsvr_tweak rendering a well, stereoscopically using rsvr_opengl&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;now-just-get-it-on-the-phone&quot;&gt;Now, just get it on the phone...&lt;a class=&quot;zola-anchor&quot; href=&quot;#now-just-get-it-on-the-phone&quot; aria-label=&quot;Anchor link for: now-just-get-it-on-the-phone&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;With rendering to a local window working, I now had to come up with some method of streaming the video to the phone (granted, it could be possible to run the VR applications on the phone, but I would expect a desktop GPU to far outperform that of a mobile phone!).&lt;&#x2F;p&gt;
&lt;p&gt;Taking into account my reluctance to write anything Android-specific, I decided it may be worth a shot to try an in-browser solution using WebRTC (and VP8). Luckily for me, &lt;a href=&quot;https:&#x2F;&#x2F;blog.nirbheek.in&#x2F;2018&#x2F;02&#x2F;gstreamer-webrtc.html&quot;&gt;GStreamer has grown a WebRTC implementation&lt;&#x2F;a&gt; and I heard, from a FOSDEM 2018 talk, that GStreamer had some nice Rust bindings.&lt;&#x2F;p&gt;
&lt;p&gt;I was pleasantly surprised by GStreamer; the bindings are as well supported as they are made out to be! A few days later and I had something &#x27;working&#x27; but frankly unusable. I only managed 32 fps (or 6.2 fps in debug mode), &lt;em&gt;as measured on the computer&lt;&#x2F;em&gt;. Unfortunately, frames were being dropped more than they were transmitted and the latency would be on the order of 10 seconds or more... Not a good result!&lt;&#x2F;p&gt;
&lt;p&gt;Using the excellent &lt;code&gt;gst-launch-1.0&lt;&#x2F;code&gt; tool, I played around and found that hardware encoding gave a much lower latency. My graphics chip doesn&#x27;t support hardware VP8 encoding but does support hardware H.264 encoding, so this may be an avenue that I need to explore. Still, I&#x27;m not sure if this will solve the issues because even the software &lt;code&gt;vp8enc&lt;&#x2F;code&gt; encoder (with &lt;code&gt;deadline=1&lt;&#x2F;code&gt;) performs markedly better than the results I&#x27;ve been getting!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;for-the-future&quot;&gt;For the future&lt;a class=&quot;zola-anchor&quot; href=&quot;#for-the-future&quot; aria-label=&quot;Anchor link for: for-the-future&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;This project is far from its conclusion; I would definitely like to see it become usable. When I get time to work on it again, I will potentially look into the following:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Having GStreamer perform the GL texture download using the &lt;code&gt;gldownload&lt;&#x2F;code&gt; element
&lt;ul&gt;
&lt;li&gt;or otherwise optimising the texture download process.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Hardware video encoding for lower latency.&lt;&#x2F;li&gt;
&lt;li&gt;Replacing WebRTC with GStreamer&#x27;s UDP sink &amp;amp; source elements, if this proves advantageous.&lt;&#x2F;li&gt;
&lt;li&gt;Cleaning up the Rust crates – right now, they are very messy!
&lt;ul&gt;
&lt;li&gt;I find it hard to build abstractions in Rust where lifetimes get involved.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Running RSVR applications directly on a mobile phone.
&lt;ul&gt;
&lt;li&gt;Under Android&lt;&#x2F;li&gt;
&lt;li&gt;Under Halium (as long as GPU hardware acceleration is available)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I hope to follow up this blog post in the future.&lt;&#x2F;p&gt;
</content>
	</entry>
</feed>
