<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>boringcactus</title>
  <subtitle></subtitle>
  <link href="https://www.boringcactus.com/feed.xml" rel="self" />
  <link href="https://www.boringcactus.com/" />
  <updated>2025-10-04T00:00:00Z</updated>
  <id>https://www.boringcactus.com/</id>
  <author>
    <name>boringcactus / Melody Horn</name>
  </author>
  <entry>
    <title>I’m Job Hunting</title>
    <link href="https://www.boringcactus.com/2025/10/04/job-hunt.html" />
    <updated>2025-10-04T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2025/10/04/job-hunt.html</id>
    <content type="html">&lt;p&gt;My employer’s remote work policies are changing, and Salt Lake City isn’t “commutable distance” from Boston, so I’m looking for a new job. If you’re hiring software developers (remote in the US, or hybrid in Salt Lake City) to work on something that’s prosocial and technically ambitious, &lt;a href=&quot;mailto:melody@boringcactus.com&quot;&gt;let me know&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’ve done backend, frontend, mobile, devops, internal tooling, release engineering, CI, and more. I’m incredibly flexible, and I learn fast, so if you have a mission I can believe in, I can be effective and comfortable in any role and any language and tech stack.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A 2025 Survey of Rust GUI Libraries</title>
    <link href="https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html" />
    <updated>2025-04-13T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html</id>
    <content type="html">&lt;p&gt;I did this &lt;a href=&quot;https://www.boringcactus.com/2020/08/21/survey-of-rust-gui-libraries.html&quot;&gt;in 2020&lt;/a&gt; and then again &lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html&quot;&gt;in 2021&lt;/a&gt;, but I’m in the mood to look around again.
Let’s look through &lt;a href=&quot;https://www.areweguiyet.com/&quot;&gt;Are We GUI Yet?&lt;/a&gt; and see what’s up these days.&lt;/p&gt;
&lt;p&gt;The task today is to have a text label and an input field that can change the text in the label.
In React, for example, this is basically free:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Demo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setState&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onInput&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;evt&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;evt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Choosing a task this simple means I can actually have a shot at completing this at a reasonable pace (although it took me two weeks), but it also means frameworks that prioritize scaling over initial setup will be at a disadvantage here.
If you’ve found this post looking for specific guidance for a project that’s going to be substantially more complicated than this, don’t assume my conclusions are valid in your context.&lt;/p&gt;
&lt;p&gt;A few other reasons my context may not match yours:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I’m developing this, like nearly all my personal projects, on Windows.
I’m in good company there — per the &lt;a href=&quot;https://survey.stackoverflow.co/2024/technology/#1-operating-system&quot;&gt;2024 Stack Overflow developer survey&lt;/a&gt;, “Windows is the most popular operating system for developers, across both personal and professional use” — but for a handful of reasons Windows is an afterthought in a lot of open source development.
I find some of those reasons more compelling than others, but for GUI libraries in particular I think avoiding Windows is avoiding success, and if Windows support is lower on your roadmap than trend chasing AI bullshit, you are not serious.&lt;/li&gt;
&lt;li&gt;I am checking that the text label can be read out from Windows Narrator.
Screen reader accessibility is another frequent afterthought, and it’s not load-bearing for me personally but it’s a lot more important as a matter of principle (and potentially a matter of law, depending on the project).&lt;/li&gt;
&lt;li&gt;New in the 2025 version of this exercise: I will be using the &lt;a href=&quot;https://go.microsoft.com/fwlink/?linkid=2007440&quot;&gt;Windows Japanese IME&lt;/a&gt; to type in the kanji for Tokyo, &lt;span lang=&quot;ja&quot;&gt;&lt;code&gt;東京&lt;/code&gt;&lt;/span&gt; (which on my US-layout-emulating keyboard I do with &lt;code&gt;toukyou&amp;lt;Tab&amp;gt;&amp;lt;Return&amp;gt;&lt;/code&gt;).
I don’t speak Japanese (although there are &lt;a href=&quot;https://github.com/dec32/Ajemi&quot;&gt;more obscure IMEs&lt;/a&gt; that I’m more interested in), and there are a lot of internationalization pieces that a minimal GUI library can reasonably decide to ignore, but if you’ve implemented text fields from scratch and you’re just appending keystrokes to a buffer then you have rejected compatibility with a lot of languages that are more complicated than that.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m feeling very slightly more patient this time around, so I’m not going to give up instantly if something takes very slightly more setup than just &lt;code&gt;cargo add&lt;/code&gt;, but I’ve got a lot of things to check, so that’s gotta be enough preamble.&lt;/p&gt;
&lt;p&gt;I’m writing this in linear order in parallel with my development, so it’s more of a journal than a reference and it probably reads best top to bottom, but if you want a TL;DR or you’re coming back for reference you can skip right to &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#conclusion&quot;&gt;the conclusion&lt;/a&gt; or &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#the-table&quot;&gt;the table&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;azul&quot; tabindex=&quot;-1&quot;&gt;Azul&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://azul.rs/&quot;&gt;Azul&lt;/a&gt; is the first beneficiary of my newfound patience: you have to manually download the prebuilt &lt;code&gt;.dll&lt;/code&gt; (via a link that doesn’t quite work, or directly off the GitHub release), and the last time I was here I balked at that request.
It’s not a great sign that the &lt;a href=&quot;https://azul.rs/guide/1.0.0-alpha1/GettingStarted&quot;&gt;getting started guide&lt;/a&gt; has samples for C++ and Python but not Rust, but there are &lt;a href=&quot;https://github.com/fschutt/azul/tree/master/examples/rust&quot;&gt;examples in the repo&lt;/a&gt; that aren’t too hard to follow.
However, the hello world sample doesn’t actually work if I copy and paste it into my &lt;code&gt;main.rs&lt;/code&gt; - it looks like the API has changed somewhat since the latest release.&lt;/p&gt;
&lt;p&gt;There’s a bit of a theme of release versioning issues with Azul - following the guide appears to give me version &lt;code&gt;1.0.0-alpha4&lt;/code&gt;, but the only Git tag is &lt;code&gt;1.0.0-alpha1&lt;/code&gt; and it’s not clear what may have changed between alpha1 and alpha4.&lt;/p&gt;
&lt;p&gt;The broader issue, though, is that even if I download the &lt;code&gt;1.0.0-alpha1&lt;/code&gt; examples off the GitHub release and try to run the same code myself, I am beset with &lt;code&gt;error LNK2019: unresolved external symbol __imp_AzCallbackInfo_getNodeIdOfRootDataset&lt;/code&gt; and 47 other unresolved symbols.
I made an honest attempt to get Azul working, but it still doesn’t work.
I’ll see you in a couple years, Azul.&lt;/p&gt;
&lt;h2 id=&quot;cacao&quot; tabindex=&quot;-1&quot;&gt;cacao&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cocoa_(API)&quot;&gt;Cocoa&lt;/a&gt; is some subset of the macOS API; it has &lt;a href=&quot;https://github.com/ryanmcgrath/cacao&quot;&gt;Rust bindings&lt;/a&gt; named Cacao.
I could have &lt;em&gt;sworn&lt;/em&gt; that the cocoa/cacao wordplay had been done forever ago in the macOS space, but maybe I just couldn’t fucking read, because the only things I’m able to find are this crate.
Unsurprisingly, this does not work on Windows.&lt;/p&gt;
&lt;h2 id=&quot;core-foundation&quot; tabindex=&quot;-1&quot;&gt;core-foundation&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Core_Foundation&quot;&gt;Core Foundation&lt;/a&gt; is a different subset of the macOS API; it has &lt;a href=&quot;https://github.com/servo/core-foundation-rs&quot;&gt;Rust bindings&lt;/a&gt;.
Also not useful from Windows.&lt;/p&gt;
&lt;h2 id=&quot;crux&quot; tabindex=&quot;-1&quot;&gt;Crux&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/redbadger/crux/&quot;&gt;Crux&lt;/a&gt; is new, and I find it really intriguing.
The idea of writing a shared library with business logic and then writing an ideally minimal native UI shell around it is also how Kotlin Multiplatform works (if you adopted it before Compose Multiplatform on iOS was out of alpha, at least), and I’ve been using that at my day job for a year and a half with only a handful of complaints.
The &lt;a href=&quot;https://redbadger.github.io/crux/getting_started/core.html&quot;&gt;initial project setup&lt;/a&gt; accurately describes itself as a sharp edge that needs better tooling, but it’s not miserable.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://redbadger.github.io/crux/getting_started/core.html#the-interface-definitions&quot;&gt;Manually defining&lt;/a&gt; the entire shared library interface feels like it would get old fast, but this task is faster.
Unfortunately, though, I’m just now processing that Crux doesn’t actually support desktop GUI development, only mobile and web!
It’s very interesting that this exists, though, and if my aim today was actually mobile development I’d be very curious if actual Swift bindings solve my Kotlin Multiplatform woes (I suspect that they might).&lt;/p&gt;
&lt;p&gt;Hang on, though, this isn’t even a GUI library, the whole point is that you still use the native GUI library directly from each platform, and in fact several of the web examples use libraries that will be coming back later in this very list.
I’m not quite certain I agree with listing it on Are We GUI Yet? in the first place, although I guess the current design doesn’t have space for a separate section for non-GUI frameworks that would be useful for GUI applications.&lt;/p&gt;
&lt;h2 id=&quot;cushy&quot; tabindex=&quot;-1&quot;&gt;Cushy&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://crates.io/crates/cushy&quot;&gt;Cushy&lt;/a&gt; is another new entrant - apparently there’s been a lot of movement in the space in the last four years.
The README code snippet has a funny but inconsequential mistake:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Create a dynamic usize.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Dynamic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0_isize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maybe one day clippy will be able to detect comments that don’t actually match the code.&lt;/p&gt;
&lt;p&gt;The README example actually works (it’s a Christmas miracle!) but it spams stderr with a ton of eyebrow-raising Vulkan/DirectX 12 errors:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2025-04-03T04:20:03.810927Z ERROR wgpu_hal::auxil::dxgi::exception: ID3D12CommandQueue::ExecuteCommandLists: Using ClearRenderTargetView on Command List (0x0000026C511CF250:&#39;Unnamed ID3D12GraphicsCommandList Object&#39;): Resource state (0x0: D3D12_RESOURCE_STATE_[COMMON|PRESENT]) of resource (0x0000026C510E4EB0:&#39;Unnamed ID3D12Resource Object&#39;) (subresource: 0) is invalid for use as a render target.  Expected State Bits (all): 0x4: D3D12_RESOURCE_STATE_RENDER_TARGET, Actual State: 0x0: D3D12_RESOURCE_STATE_[COMMON|PRESENT], Missing State: 0x4: D3D12_RESOURCE_STATE_RENDER_TARGET. [ EXECUTION ERROR #538: INVALID_SUBRESOURCE_STATE]
2025-04-03T04:20:03.812586Z ERROR wgpu_hal::auxil::dxgi::exception: ID3D12CommandQueue::ExecuteCommandLists: Using IDXGISwapChain::Present on Command List (0x0000026C51057910:&#39;Internal DXGI CommandList&#39;): Resource state (0x4: D3D12_RESOURCE_STATE_RENDER_TARGET) of resource (0x0000026C510E4EB0:&#39;Unnamed ID3D12Resource Object&#39;) (subresource: 0) is invalid for use as a PRESENT_SOURCE.  Expected State Bits (all): 0x0: D3D12_RESOURCE_STATE_[COMMON|PRESENT], Actual State: 0x4: D3D12_RESOURCE_STATE_RENDER_TARGET, Missing State: 0x0: D3D12_RESOURCE_STATE_[COMMON|PRESENT]. [ EXECUTION ERROR #538: INVALID_SUBRESOURCE_STATE]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s probably fine, though.
The actual application is about as simple as you’d hope it’d be:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Dynamic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map_each&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;text&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; text_input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;into_input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text_input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;into_rows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-cushy.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, Windows Narrator has no idea what’s inside this window.
Kanji input with the IME sorta works, though; I don’t get to see the &lt;span lang=&quot;ja&quot;&gt;&lt;code&gt;とうきょう&lt;/code&gt;&lt;/span&gt; that my &lt;code&gt;toukyou&lt;/code&gt; becomes as I type it, but when I press Return I do in fact get the kanji I was expecting.&lt;/p&gt;
&lt;p&gt;In IME jargon, turning &lt;code&gt;toukyou&lt;/code&gt; into &lt;span lang=&quot;ja&quot;&gt;&lt;code&gt;とうきょう&lt;/code&gt;&lt;/span&gt; is the job of the &lt;em&gt;composer&lt;/em&gt; and turning &lt;span lang=&quot;ja&quot;&gt;&lt;code&gt;とうきょう&lt;/code&gt;&lt;/span&gt; into &lt;span lang=&quot;ja&quot;&gt;&lt;code&gt;東京&lt;/code&gt;&lt;/span&gt; is the job of the &lt;em&gt;converter&lt;/em&gt;, so the composer step is hidden but the converter step works fine.&lt;/p&gt;
&lt;p&gt;I’m not sure whether Cushy has done anything in particular to accept IME results in its text input widgets or if the IME just dispatches the selected kanji as though they were typed directly, but I suspect it’s the latter.
I know Windows is pretty flexible with how it treats keyboard input — every curly quote in this blog post was hand-curled with &lt;a href=&quot;https://wincompose.info/&quot;&gt;WinCompose&lt;/a&gt; despite my Markdown renderer almost certainly doing smart quotes automatically — so maybe the IME result is just dispatched as though it’s a direct input of U+6771 CJK Unified Ideograph and then U+4EAC CJK Unified Ideograph.
(I had forgotten about Han unification; I’m curious how if at all using the Japanese IME causes the Japanese kanji to be displayed instead of the theoretically-equivalent hanzi/hanja, but I suspect the answer is that it doesn’t, and that scares me.)&lt;/p&gt;
&lt;h2 id=&quot;cxx-qt&quot; tabindex=&quot;-1&quot;&gt;CXX-Qt&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://kdab.github.io/cxx-qt/book/&quot;&gt;CXX-Qt&lt;/a&gt; is a framework for using the well-established Qt C++ GUI library from Rust.
I have been avoiding Qt for years, but it seems like it’s time to stop.&lt;/p&gt;
&lt;p&gt;It’s annoying that I have to make an account to install Qt, and it’s very annoying that they want my name and location before they’ll let me download it.
I was right to hate this the whole time.&lt;/p&gt;
&lt;p&gt;Their sample code will not run due to 1058 linker errors:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;   Creating library C:&#92;Users&#92;Melody&#92;Projects&#92;misc&#92;2025&#92;rust-gui-survey&#92;target&#92;debug&#92;deps&#92;cxx_qt_demo.lib and object C:&#92;Users&#92;Melody&#92;Projects&#92;misc&#92;2025&#92;rust-gui-survey&#92;target&#92;debug&#92;deps&#92;cxx_qt_demo.exp␍
0245cd17b2ba3548-com_kdab_cxx_qt_demo_plugin_init.o : error LNK2019: unresolved external symbol &quot;__declspec(dllimport) void __cdecl qRegisterStaticPluginFunction(struct QStaticPlugin)&quot; (__imp_?qRegisterStaticPluginFunction@@YAXUQStaticPlugin@@@Z) referenced in function &quot;public: __cdecl Staticcom_kdab_cxx_qt_demo_pluginPluginInstance::Staticcom_kdab_cxx_qt_demo_pluginPluginInstance(void)&quot; (??0Staticcom_kdab_cxx_qt_demo_pluginPluginInstance@@QEAA@XZ)␍
libcxx_qt_lib-c91f193d907e83ae.rlib(badec7f11aadc5df-qcoreapplication.o) : error LNK2001: unresolved external symbol &quot;__declspec(dllimport) void __cdecl qt_assert(char const *,char const *,int)&quot; (__imp_?qt_assert@@YAXPEBD0H@Z)␍
&lt;...&gt;
libcxx_qt-464ae71fe424547a.rlib(0602fb52cb66f316-connection.o) : error LNK2019: unresolved external symbol &quot;__declspec(dllimport) public: __cdecl QMetaObject::Connection::Connection(void)&quot; (__imp_??0Connection@QMetaObject@@QEAA@XZ) referenced in function &quot;class QMetaObject::Connection __cdecl rust::cxxqt1::qmetaobjectconnectionDefault(void)&quot; (?qmetaobjectconnectionDefault@cxxqt1@rust@@YA?AVConnection@QMetaObject@@XZ)␍
libcxx_qt-464ae71fe424547a.rlib(0602fb52cb66f316-connection.o) : error LNK2019: unresolved external symbol &quot;__declspec(dllimport) public: static bool __cdecl QObject::disconnect(class QMetaObject::Connection const &amp;)&quot; (__imp_?disconnect@QObject@@SA_NAEBVConnection@QMetaObject@@@Z) referenced in function &quot;bool __cdecl rust::cxxqt1::qmetaobjectconnectionDisconnect(class QMetaObject::Connection const &amp;)&quot; (?qmetaobjectconnectionDisconnect@cxxqt1@rust@@YA_NAEBVConnection@QMetaObject@@@Z)␍
C:&#92;Users&#92;Melody&#92;Projects&#92;misc&#92;2025&#92;rust-gui-survey&#92;target&#92;debug&#92;deps&#92;cxx_qt_demo.exe : fatal error LNK1120: 1058 unresolved externals␍&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I suspect it’s the entire Qt standard library that’s missing.
The linker args all look reasonable, though — it’s looking for &lt;code&gt;C:/Qt/6.9.0/mingw_64/lib&#92;libQt6Qml.a&lt;/code&gt;, which aside from the mixed slashes is a real path that exists — so I’m not sure what the problem is.&lt;/p&gt;
&lt;p&gt;This is a complete bust.
There’s a section in the docs about building projects with CMake instead of cargo, but it says it’s optional, and it’s not like I’m desperate for more opportunities to use CMake, so I’m not going to try it.&lt;/p&gt;
&lt;h2 id=&quot;dioxus&quot; tabindex=&quot;-1&quot;&gt;Dioxus&lt;/h2&gt;
&lt;p&gt;I think I remember &lt;a href=&quot;https://dioxuslabs.com/&quot;&gt;Dioxus&lt;/a&gt; as being one of the Rust frontend web dev frameworks; apparently they’ve branched out.&lt;/p&gt;
&lt;p&gt;Their tutorial involves building “&lt;em&gt;HotDog&lt;/em&gt; - basically Tinder, but for dogs!”, and by that they mean the app lets you swipe through a pile of dog photos and then view the ones you’ve swiped whichever direction is good on.
That is not what I would expect “Tinder, but for dogs” to be, but maybe I don’t know what Tinder is.&lt;/p&gt;
&lt;p&gt;Apparently the way Dioxus supports desktop development is through WebView2/WebKitGTK, so they haven’t branched very far out.
I’m a little bit skeptical that Diet Electron is really the future, but given that &lt;a href=&quot;https://en.wikipedia.org/wiki/Electron_(software_framework)&quot;&gt;Electron&lt;/a&gt; is the present, maybe I need to take what I can get.
I also have some concerns about leaning this hard into cloning React — React hooks are a fascinating hack to almost build algebraic effects in JS, and it’s not like Rust really has algebraic effects, either, but maybe the real compilation step means they can do a little more magic and make it actually work reasonably.
At this scale, though, it’s hard to argue with the results:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;use_signal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token macro property&quot;&gt;rsx!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    p &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{text}&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    input &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        oninput&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;event&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        value&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{text}&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-dioxus.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Windows Narrator can even see what the text is, although it feels a little clumsy (it’s saying “Web content region” on its way into the body of the frame).
The IME works perfectly, too.
I guess there are benefits to letting Chrome-via-Edge-via-WebView2 be responsible for all the UI machinery.&lt;/p&gt;
&lt;p&gt;Dioxus did not invent the Diet Electron approach (that was Tauri, whose WebView2/WebKitGTK/macOS things library Dioxus builds its desktop support on), but the Rust-all-the-way-through approach feels like a way better idea than how Tauri works, which I’ll get into once I make it that far through my list.
If Diet Electron is really the best thing out there, I may be a little bit sad, but it’s probably possible to use Dioxus for real work without constantly being miserable, and that’s a new high water mark for this blog series.&lt;/p&gt;
&lt;h2 id=&quot;dominator&quot; tabindex=&quot;-1&quot;&gt;Dominator&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Pauan/rust-dominator&quot;&gt;Dominator&lt;/a&gt; is a Web-only UI crate, and unlike Dioxus it does not also offer a blessed desktop stack.&lt;/p&gt;
&lt;h2 id=&quot;egui&quot; tabindex=&quot;-1&quot;&gt;egui&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.egui.rs/&quot;&gt;egui&lt;/a&gt;, and its framework &lt;code&gt;eframe&lt;/code&gt;, have been around for a while.
The setup process has always been pretty straightforward, which is nice.
It’s pretty simple to use, too:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;egui&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CentralPanel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;ui&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    ui&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    ui&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text_edit_singleline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-egui.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Windows Narrator can even see this text!
It feels a little janky trying to get Narrator into the text field, though, but maybe that’s just the ceiling of how well Windows Narrator can work, or maybe I’m just holding it wrong and there’s a way to get behavior that feels more intuitive that I just can’t think of.&lt;/p&gt;
&lt;p&gt;The default font doesn’t have hiragana or kanji coverage, though, and if I manually load a system font (which requires loading the bytes directly instead of just specifying a system font name, which is suboptimal but probably common), my Tab press to select &lt;span lang=&quot;ja&quot;&gt;&lt;code&gt;東京&lt;/code&gt;&lt;/span&gt; as the kanji for &lt;span lang=&quot;ja&quot;&gt;&lt;code&gt;とうきょう&lt;/code&gt;&lt;/span&gt; gets eaten by egui and I’m stuck with the hiragana forever.&lt;/p&gt;
&lt;p&gt;I prefer this default appearance to Cushy’s or Dioxus’s, although it’s probably possible to make anything look like anything if you try hard and believe in yourself.
I’m not sure I love immediate mode on principle, although at this scale it extremely doesn’t matter.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Digression: “Immediate mode” and “retained mode”&lt;/summary&gt;
&lt;aside&gt;
&lt;p&gt;In an “immediate mode” GUI, your code that builds your widgets runs every frame, and the widgets don’t really &lt;em&gt;exist&lt;/em&gt; per se.
This can be simpler to work with — I can create the label with &lt;code&gt;&amp;amp;self.label&lt;/code&gt; and the text edit widget with &lt;code&gt;&amp;amp;mut self.label&lt;/code&gt; and there’s no issue because the label and the text field don’t actually exist at the same time.
I think it’s also easier to integrate into a game engine, since the GUI library doesn’t need as much control over communication with the GPU; that doesn’t matter for me today, but it may matter for you.&lt;/p&gt;
&lt;p&gt;“Retained mode” is the opposite of that, where the code that builds the widgets only runs when it has something to do, and the widgets keep existing after they’re created.
My understanding is that this can have better performance since you aren’t rebuilding your entire UI from scratch every frame, although presumably serious immediate mode libraries do some amount of caching under the hood so you aren’t actually rebuilding your entire UI from scratch every frame.
I’m not sure how much of a difference it actually makes; it would be impossible to tell with anything this small.&lt;/p&gt;
&lt;/aside&gt;
&lt;/details&gt;
&lt;p&gt;The fact that it’s possible to render your UI yourself and still have real accessibility support is definitely a good thing, and if it weren’t for the weird IME issues this would be perfect.
If you don’t need IME support and you want better styles out of the box or an immediate mode library you can plug into your existing game engine, egui seems like a perfectly reasonable choice, and that IME issue will probably get fixed eventually.&lt;/p&gt;
&lt;h2 id=&quot;floem&quot; tabindex=&quot;-1&quot;&gt;Floem&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://lap.dev/floem/&quot;&gt;Floem&lt;/a&gt; is the UI framework developed for &lt;a href=&quot;https://lap.dev/lapce/&quot;&gt;Lapce&lt;/a&gt;, the cooler VSCode-but-in-Rust IDE.
I haven’t used Lapce in a while — several years ago when I last checked, its support for non-Rust languages was pretty weak, and I don’t actually prefer VSCode-style lightweight IDEs anyway (I pay for the JetBrains suite despite not doing enough personal development to justify that expense) — but everything that exists and is good enough for me once existed and was not.
It’s cool that it exists; let’s see if their UI framework is any good.&lt;/p&gt;
&lt;p&gt;Getting from zero to today’s sample is pretty straightforward:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create_rw_signal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_owned&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;dyn_view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;text_input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;s&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flex_col&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-floem.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Windows Narrator can’t see any of this text, and the IME won’t even start, I’m stuck with “toukyou” forever.&lt;/p&gt;
&lt;p&gt;Building layouts out of tuples feels a little bit weird — you can only have &lt;a href=&quot;https://docs.rs/floem/0.2.0/floem/trait.IntoView.html#impl-IntoView-for-(A,+B,+C,+D,+E,+F,+G,+H,+I,+J,+K,+L,+M,+N,+O,+P)&quot;&gt;up to 16&lt;/a&gt; widgets directly within a container at once, due to tuple generics in Rust being &lt;a href=&quot;https://github.com/rust-lang/rfcs/issues/376&quot;&gt;still obviously incomplete after 11 years and counting&lt;/a&gt; — but it’s probably better than Cushy’s &lt;code&gt;.and()&lt;/code&gt;.
The complete lack of accessibility or IME support is the real issue, though.
Maybe one day they’ll fix it, but for now, this is no good.&lt;/p&gt;
&lt;h2 id=&quot;fltk&quot; tabindex=&quot;-1&quot;&gt;fltk&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.fltk.org/&quot;&gt;FLTK&lt;/a&gt; is a C++ library with &lt;a href=&quot;https://github.com/fltk-rs/fltk-rs&quot;&gt;Rust bindings&lt;/a&gt;.
Conveniently, the Rust bindings offer a &lt;code&gt;bundled&lt;/code&gt; feature so I don’t have to figure out how to build FLTK from source on Windows.
Unfortunately, FLTK doesn’t appear to have an idea of widgets having an inherent size, and its whole layout subsystem leaves something to be desired:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; wind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello from rust&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; pack &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Pack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default_fill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PackType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Vertical&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Frame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;input&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_trigger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CallbackTrigger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Changed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
pack&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
pack&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;auto_layout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
wind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
wind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-fltk.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Windows Narrator has no idea what’s going on in here, but I did notice a two-year-stale &lt;a href=&quot;https://github.com/fltk-rs/fltk-accesskit&quot;&gt;fltk-accesskit&lt;/a&gt; repo under the &lt;code&gt;fltk-rs&lt;/code&gt; org, and that works but it requires its own ugly setup (you have to pass it a redundant list of all your widgets, which is tough because I moved one of them into the callback for the other).
The IME works perfectly, which I wasn’t expecting.&lt;/p&gt;
&lt;p&gt;The main issue here is the layout subsystem, which appears to have no concept of widgets having intrinsic sizes.
You can manually position and size everything, or you can use one of the clumsy automatic layouts, but neither of those is particularly satisfying.
The fact that adding widgets to containers happens in implicit global state and you have to &lt;code&gt;.end()&lt;/code&gt; a container to stop adding items to it is a little bit horrifying, I can’t lie.
I don’t like this API design one bit.&lt;/p&gt;
&lt;h2 id=&quot;flutter_rust_bridge&quot; tabindex=&quot;-1&quot;&gt;flutter_rust_bridge&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://flutter.dev/&quot;&gt;Flutter&lt;/a&gt; is a Google framework for cross-platform UI development, but you use it from &lt;a href=&quot;https://dart.dev/&quot;&gt;Dart&lt;/a&gt;.
Dart has &lt;a href=&quot;https://dart.dev/language/branches#switch-statements&quot;&gt;&lt;code&gt;switch&lt;/code&gt; statements&lt;/a&gt; and &lt;a href=&quot;https://dart.dev/language/branches#switch-expressions&quot;&gt;&lt;code&gt;switch&lt;/code&gt; expressions&lt;/a&gt; with completely different syntax.
Dart sucks.
Maybe using Flutter from Rust doesn’t suck?&lt;/p&gt;
&lt;p&gt;It’s very funny to me that Flutter for Windows &lt;a href=&quot;https://docs.flutter.dev/get-started/install/windows/desktop#hardware-requirements&quot;&gt;claims&lt;/a&gt; you absolutely need a 1366x768 display; as an act of spite I will be disabling my primary display and only using my 1024x768 secondary display for the remainder of this section.&lt;/p&gt;
&lt;p&gt;Oh god this is cramped, I regret this already.&lt;/p&gt;
&lt;p&gt;Flutter hates my MSVC toolchain for some reason, and the Visual Studio installer does not want to be this narrow, but if I tab offscreen or move the window to the side I can still add the right components.
Apparently “MSVC v143 - VS 2022 C++ x64/x86 build tools” and “Windows 11 SDK” aren’t good enough, and Flutter absolutely insists on having specifically “MSVC v142 - VS 2019 C++ x64/x86 build tools” and “Windows 10 SDK”.&lt;/p&gt;
&lt;p&gt;If I install those, though, &lt;code&gt;flutter doctor&lt;/code&gt; still doesn’t think I have them for some reason.
It seems like my issue, which &lt;code&gt;flutter doctor&lt;/code&gt; failed to detect for some reason, was that I didn’t have the “Desktop development with C++” workload installed in Visual Studio.&lt;/p&gt;
&lt;p&gt;Getting flutter_rust_bridge set up is easy once Flutter itself is working, although having to run &lt;code&gt;flutter_rust_bridge_codegen generate&lt;/code&gt; explicitly is no good; it can’t hook into the Flutter/Dart build process because &lt;a href=&quot;https://dart.dev/tools/build_runner&quot;&gt;the only piece that anything can hook into&lt;/a&gt; runs completely outside the Flutter/Dart build process.
However, what you actually get with flutter_rust_bridge is the opportunity to write your business logic in Rust and your UI in Dart still.
You can write your UI state in Rust if you want, but you still have to define your widgets in Dart:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[frb(ui_state)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;RustState&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RustState&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_owned&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            base_state&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[frb(ui_mutation)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;set_label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-dart&quot;&gt;&lt;code class=&quot;language-dart&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;runRustApp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RustState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Widget&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RustState&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    children&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;TextField&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        controller&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TextEditingController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        onChanged&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setLabel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-flutter-rust-bridge.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;This doesn’t even actually work, though: typing happens in reverse in the input field, because the &lt;code&gt;TextEditingController&lt;/code&gt; contains the caret position but keeps getting reset.
In pure Flutter, you’d probably solve this by moving the controller into the widget state, but it’s not obvious how to do that here since our widget state is being defined in Rust instead.
Windows Narrator appears to be able to see this text, at least, although it feels janky trying to move between the label and the input.
The IME sort of works, but for presumably the same &lt;code&gt;TextEditingController&lt;/code&gt; reasons, the intermediate states aren’t being cleared as I type in the IME, so the actual value that I’ve entered with &lt;code&gt;toukyou&amp;lt;Tab&amp;gt;&amp;lt;Return&amp;gt;&lt;/code&gt; is &lt;code&gt;東京東京東京東京とうきょうとうきょうとうきょとうきょとうｋｙとうｋｙとうｋとうｋとうとうととｔ&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I’d get slightly better functionality if I moved this widget state to Flutter, I’m sure, but then there’d be no Rust code at all.
If I wanted to write my UIs in Flutter, I’d just do that.
If you want to write your UIs in Flutter and just some business logic in Rust, this might work alright for you, but that is not what I want.&lt;/p&gt;
&lt;h2 id=&quot;freya&quot; tabindex=&quot;-1&quot;&gt;Freya&lt;/h2&gt;
&lt;p&gt;Per the README, &lt;a href=&quot;https://freyaui.dev/&quot;&gt;Freya&lt;/a&gt; is “a cross-platform GUI library for Rust powered by 🧬 Dioxus and 🎨 Skia.”
&lt;a href=&quot;https://book.freyaui.dev/differences_with_dioxus.html&quot;&gt;Evidently&lt;/a&gt;, it takes the logic and structure of Dioxus and but renders everything itself instead of using Diet Electron.
I did grumble about the Diet Electron-hood of Dioxus, so maybe this is the exact thing I was hoping for all along.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Edit 2025-04-15&lt;/em&gt;: I’ve been asked to run this again with the week-old release candidate rather than the year-old latest stable release, and I’ve ignored other year-old latest stable releases so it’s fair to do that here too.
Next time, unless I forget to, I’m deciding up front when to use a stable release vs an unstable release vs the main branch.&lt;/p&gt;
&lt;p&gt;Freya’s README example uses &lt;code&gt;rsx!()&lt;/code&gt; rather than the Dioxus docs’ &lt;code&gt;rsx! {}&lt;/code&gt;, and it still feels weird to me that those are interchangeable, but aside from that, the only difference from the Dioxus code is some component names:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;use_signal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token macro property&quot;&gt;rsx!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    label &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{text}&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Input&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        value&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        onchange&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;value&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-freya.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Narrator sees the text label fine, but something’s gone wacky with the text input: Narrator can see that there’s a text input with those bounds on the screen, but it doesn’t seem like it actually knows the contents of the text input.
The IME shows up in the corner of the screen, and the provisional states aren’t displayed at all, but the final kanji land correctly in the text input once I accept them from the converter.&lt;/p&gt;
&lt;p&gt;There are some drawbacks to rendering things yourself instead of letting Chrome-via-Edge-via-WebView2 do it for you, it seems.
Regardless, it’s extremely cool that Dioxus is set up in a way that makes this possible, and it’s extremely cool that someone’s trying to do it.
I’m not sure that Freya is quite ready for serious use yet, but it’s definitely promising.&lt;/p&gt;
&lt;h2 id=&quot;fui&quot; tabindex=&quot;-1&quot;&gt;fui&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/marek-g/rust-fui&quot;&gt;FUI&lt;/a&gt; does not have a lot of high-level documentation I can use to figure out what to put here.&lt;/p&gt;
&lt;p&gt;The example in the README has drifted from the examples in the code, which I suppose is natural but which is rarely auspicious.&lt;/p&gt;
&lt;p&gt;Even less auspicious is that I can’t build &lt;code&gt;fui_system&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;qmake.stderr: Project ERROR: Cannot run compiler &#39;g++&#39;. Output:
===================
===================
Maybe you forgot to setup the environment?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can find no documentation about how to set up my environment, and if I really need &lt;code&gt;g++&lt;/code&gt; then that’s not great.&lt;/p&gt;
&lt;h2 id=&quot;gemgui&quot; tabindex=&quot;-1&quot;&gt;GemGui&lt;/h2&gt;
&lt;p&gt;The last commit to &lt;a href=&quot;https://github.com/mmertama/gemgui-rs&quot;&gt;GemGui&lt;/a&gt; was two years ago; that’s rarely a good sign.&lt;/p&gt;
&lt;p&gt;GemGui leans even further into Diet Electron by actually running your frontend on an HTTP server and by default just opening it in your regular browser.
There’s a setting to run it in its own application frame instead, but that appears to have a load-bearing dependency on Python being installed in a way that I currently don’t have it installed.
It also looks like you’re only really intended to define the UI elements in HTML and wire up the business logic in Rust.&lt;/p&gt;
&lt;p&gt;If I give this a venv and then manually ensure &lt;code&gt;python3&lt;/code&gt; will do the right thing, that isn’t enough, because the Python dependency doesn’t actually work or something.
“Embed an HTTP server and then use a Web framework and open your server in the system default browser” is a very boring way to technically claim that you’re doing GUI development.&lt;/p&gt;
&lt;p&gt;This repo has four stars on GitHub.
Why is it even listed in Are We GUI Yet?&lt;/p&gt;
&lt;h2 id=&quot;gpui&quot; tabindex=&quot;-1&quot;&gt;GPUI&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://gpui.rs/&quot;&gt;GPUI&lt;/a&gt; is the UI framework developed for &lt;a href=&quot;https://zed.dev/&quot;&gt;Zed&lt;/a&gt;, the other VSCode-but-in-Rust IDE.
Are We GUI Yet? links to &lt;a href=&quot;https://crates.io/crates/gpui&quot;&gt;someone squatting it on crates.io&lt;/a&gt;, which is silly.&lt;/p&gt;
&lt;p&gt;Remember this piece from the intro?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;if Windows support is lower on your roadmap than trend chasing AI bullshit, you are not serious.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, that’s Zed.
It’s a heavily-LLM-focused IDE with &lt;a href=&quot;https://github.com/zed-industries/zed/issues/5391&quot;&gt;no Windows support&lt;/a&gt;.
GPUI appears to work alright on Windows, though.&lt;/p&gt;
&lt;p&gt;It looks like GPUI doesn’t have a basic text input widget, though; their &lt;a href=&quot;https://github.com/zed-industries/zed/blob/a2fbe82c42601221482e8422d7f8db5fee649b8e/crates/gpui/examples/input.rs&quot;&gt;text input example&lt;/a&gt; is over 700 lines of code.
It’s possible to shuffle that example around to at least get something that’ll meet the task I’m working on, though.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// this isn’t even the bad part!&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;key_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TextInput&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;track_focus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;focus_handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CursorStyle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IBeam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;backspace&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;delete&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;select_left&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;select_right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;select_all&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;home&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;show_character_palette&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;paste&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;cut&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;copy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_mouse_down&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MouseButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;on_mouse_down&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_mouse_up&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MouseButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;on_mouse_up&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_mouse_up_out&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MouseButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;on_mouse_up&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_mouse_move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;on_mouse_move&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0xeeeeee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;line_height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text_size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;w_full&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextElement&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                input&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-gpui.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Narrator has no idea what’s going on inside this window (and it’s in good company there).
The IME works fine, though.&lt;/p&gt;
&lt;p&gt;I’m not sure you’re actually supposed to use GPUI at this stage; the documentation is spotty, the installation is janky, and the standard library is woefully inadequate.
But at least you can generate bad code way faster, and clearly that’s enough to get your Series A in.
Was inflicting Electron on us all by way of Atom not enough?&lt;/p&gt;
&lt;h2 id=&quot;gtk-3&quot; tabindex=&quot;-1&quot;&gt;GTK 3&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;UNMAINTAINED Rust bindings for the GTK+ 3 library (use gtk4 instead).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OK then.&lt;/p&gt;
&lt;h2 id=&quot;gtk-4&quot; tabindex=&quot;-1&quot;&gt;GTK 4&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://gtk.org/&quot;&gt;GTK&lt;/a&gt; is the GNOME toolkit (although apparently that’s not actually what it stands for); it’s got &lt;a href=&quot;https://gtk-rs.org/&quot;&gt;Rust bindings&lt;/a&gt;.
Conveniently, there are &lt;a href=&quot;https://gtk-rs.org/gtk4-rs/stable/latest/book/installation_windows.html&quot;&gt;specific installation instructions for Windows&lt;/a&gt;.
It’s not clear whether or not just downloading the prebuilt binaries would work, and it’d certainly save a lot of time if they would, but I’m going to assume building the binaries myself will be more likely to work.
That only took five minutes, astonishingly.&lt;/p&gt;
&lt;p&gt;I find it a little bit counterintuitive that the single-line text widget is named &lt;code&gt;Entry&lt;/code&gt;, but it does kinda rule that GTK’s property bindings mean I don’t have to keep any state at all:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; entry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Entry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entry
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind_property&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;label&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; r#&lt;span class=&quot;token keyword&quot;&gt;box&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orientation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Orientation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Vertical&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
r#&lt;span class=&quot;token keyword&quot;&gt;box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
r#&lt;span class=&quot;token keyword&quot;&gt;box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationWindow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;My GTK App&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;r#&lt;span class=&quot;token keyword&quot;&gt;box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;present&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-gtk4.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Not only can Narrator not see this text, it can’t even see the minimize/maximize/close buttons, and that’s not a failure state I had realized was possible.
The IME works fine.&lt;/p&gt;
&lt;p&gt;You may have noticed that this is not the idiomatic Windows window decoration; these minimize/maximize/close buttons are very GNOMEy, which makes sense but isn’t really what I want.
Maybe Adwaita will solve this?
For some reason, &lt;code&gt;gvsbuild build libadwaita librsvg&lt;/code&gt; (as &lt;a href=&quot;https://gtk-rs.org/gtk4-rs/stable/latest/book/libadwaita.html#if-using-gvsbuild&quot;&gt;recommended&lt;/a&gt; by the &lt;code&gt;gtk-rs&lt;/code&gt; book) is building fucking libsass; not the actually maintained stop-trying-to-make-Dart-happen rewrite dart-sass, but the old and busted last-commit-two-years-ago libsass.
rsvg also appears to depend on some yanked crate versions, which is concerning but not my problem today.
Well, Adwaita certainly makes it look different:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-gtk4-adwaita.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;I’ve got way more drop shadow or something that’s extending the ShareX window capture way beyond the actual boundary of the window, and I’ve got dark mode and a purple emphasis color (which I’m not sure if they’re reading from somewhere or if the default was just picked out by someone with good taste).
It still doesn’t look like a Windows window, though, and Adwaita has not magically fixed the accessibility.&lt;/p&gt;
&lt;p&gt;Using GTK4 from Rust on Windows works, I guess, but I would have to lower my standards a lot to call this good enough.&lt;/p&gt;
&lt;h2 id=&quot;iced&quot; tabindex=&quot;-1&quot;&gt;Iced&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://iced.rs/&quot;&gt;Iced&lt;/a&gt; says it’s inspired by Elm, and that’s cool.
&lt;a href=&quot;https://elm-lang.org/&quot;&gt;Elm&lt;/a&gt; was your favorite programming language’s favorite programming language.
I miss it sometimes.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;State&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_owned&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; message &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SetLabel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; label
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token macro property&quot;&gt;column!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;text_input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SetLabel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(Clone, Debug)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;SetLabel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-iced.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Windows Narrator can’t see into this window, and the IME won’t even switch into active mode when I try to switch it into active mode, which may actually be a better failure state than having it just not work.&lt;/p&gt;
&lt;p&gt;Apparently System76 is all in on Iced for Pop!_OS’s COSMIC shell, so for their users’ sake I hope accessibility and IME support are actually happening at some point.&lt;/p&gt;
&lt;h2 id=&quot;imgui&quot; tabindex=&quot;-1&quot;&gt;imgui&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ocornut/imgui&quot;&gt;Dear ImGui&lt;/a&gt; is a minimalist C++ GUI library with &lt;a href=&quot;https://github.com/imgui-rs/imgui-rs&quot;&gt;Rust bindings&lt;/a&gt;.
I will always think of it as being called &lt;code&gt;dear imgui,&lt;/code&gt;, even though it hasn’t been canonically spelled that way since &lt;a href=&quot;https://github.com/ocornut/imgui/commit/84d1ce39584161dfd027613b0defe305637f3867&quot;&gt;2018&lt;/a&gt;; the trailing comma is delightful in an &lt;a href=&quot;https://www.poetryfoundation.org/poetrymagazine/poems/49493/i-carry-your-heart-with-mei-carry-it-in&quot;&gt;e e cummings&lt;/a&gt; sort of way, and they should bring it back.&lt;/p&gt;
&lt;p&gt;Unfortunately, since Dear ImGui is designed to be plugged into an existing game engine, starting with it from scratch is a little bit annoying.
There’s a downright Linux-desktop-environment number of different ways to use imgui-rs, but apparently “The most tested platform/renderer combination is &lt;code&gt;imgui-glow-renderer&lt;/code&gt; + &lt;code&gt;imgui-winit-support&lt;/code&gt; + &lt;code&gt;winit&lt;/code&gt;”, so that’s what I’ll use.
Or at least it would be what I’d use if the &lt;a href=&quot;https://github.com/imgui-rs/imgui-examples&quot;&gt;examples&lt;/a&gt; didn’t all use &lt;code&gt;imgui-glium-renderer&lt;/code&gt; instead; that one’s &lt;a href=&quot;https://github.com/imgui-rs/imgui-glium-renderer/issues/1&quot;&gt;deprecated&lt;/a&gt; and it doesn’t appear to work with the latest version of &lt;code&gt;glium&lt;/code&gt; but I can’t figure out how to get &lt;code&gt;glow&lt;/code&gt; working instead.
Open source!&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// eliding the 160 lines of glue i copied and pasted without understanding&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token namespace&quot;&gt;support&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;simple_init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token macro property&quot;&gt;file!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ui&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    ui&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello world&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;110.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Condition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FirstUseEver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            ui&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text_wrapped&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            ui&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;input_text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-imgui.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world! but the text field is labeled “Text”&quot;&gt;&lt;/p&gt;
&lt;p&gt;Windows Narrator can’t see this text, and the IME refuses to activate.&lt;/p&gt;
&lt;p&gt;The tiny window within the huge window is hilarious, and it makes sense if you’re actually doing game dev and there’s a game in the rest of the window, but I am not, and the massive white void is not something I would tolerate even in an application I was building solely for myself.
If I had a graphics stack picked out already because I was doing game dev, I might not mind the flexibility of supporting what feels like hundreds of different renderers and backends, but since that is not my current situation I very much do mind the flexibility.
This is what Sartre meant by being “condemned to freedom”: you have innumerable options available to you, but nobody can rescue you from the responsibility of deciding between them.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;digression: the irony you may have noticed&lt;/summary&gt;
&lt;aside&gt;
&lt;p&gt;This blog post offers you, in some sense, an escape from the responsibility of deciding between the innumerable (43) options available to people trying to develop desktop GUIs in Rust.
However, deciding whether or not to listen to me is as much an exercise of that responsibility as it is a surrender of it, and part of the reason I frontloaded so much context is that I want you to take that responsibility seriously.
If you decide for yourself that you value Windows support, screen reader accessibility, and IME handling like I do, then you have done the important part already.&lt;/p&gt;
&lt;/aside&gt;
&lt;/details&gt;
&lt;h2 id=&quot;kas&quot; tabindex=&quot;-1&quot;&gt;KAS&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kas-gui/kas&quot;&gt;KAS&lt;/a&gt;, the toolKit Abstraction System, is written from scratch in Rust.
The tutorials are a bit out of date — some things appear to have been moved around between when the tutorials were written and the most recent stable release — but the examples in the actual repo appear to work.
I’m not sure I quite understand how the state management is designed, but after a bit of fumbling I can at least complete the task:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; tree &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;column!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token macro property&quot;&gt;format_value!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;EditBox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;instant_parser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SetLabel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Adapt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tree&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SetLabel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-kas.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Narrator can’t see this text, and the IME won’t activate.&lt;/p&gt;
&lt;p&gt;The part that confuses me the most is why the &lt;code&gt;EditBox&lt;/code&gt; needs to be explicitly told that it’s a &lt;code&gt;String&lt;/code&gt; that’s being edited; that seems like it shouldn’t require an explicit declaration.
Maybe if the tutorial were up to date it’d be easier to understand.
Regardless, it seems like this isn’t really ready for prime time yet.&lt;/p&gt;
&lt;h2 id=&quot;kittest&quot; tabindex=&quot;-1&quot;&gt;kittest&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rerun-io/kittest&quot;&gt;kittest&lt;/a&gt; is an AccessKit-driven testing library that only supports egui.
This is cool, but it’s not in the same category as the sort of thing I’m actually looking for.
If I maintained Are We GUI Yet? I’d probably split the list up into separate categories like &lt;a href=&quot;https://www.arewewebyet.org/&quot;&gt;Are We Web Yet?&lt;/a&gt; has.&lt;/p&gt;
&lt;h2 id=&quot;leptos&quot; tabindex=&quot;-1&quot;&gt;Leptos&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/leptos-rs/leptos&quot;&gt;Leptos&lt;/a&gt; is a Web frontend framework that is for some reason on the Are We GUI Yet? list.
The README &lt;a href=&quot;https://github.com/leptos-rs/leptos/tree/v0.7.8?tab=readme-ov-file#can-i-use-this-for-native-gui&quot;&gt;has an FAQ about native GUIs&lt;/a&gt; that says it’d be possible to build native GUIs with Leptos but it’s not actually supported because it sent the whole codebase into generics hell when they tried it.&lt;/p&gt;
&lt;h2 id=&quot;lvgl&quot; tabindex=&quot;-1&quot;&gt;lvgl&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://lvgl.io/&quot;&gt;LVGL&lt;/a&gt;, the Lightweight and Versatile Graphics Library, is a C GUI library designed for embedded use; it has &lt;a href=&quot;https://github.com/lvgl/lv_binding_rust&quot;&gt;Rust bindings&lt;/a&gt; that are &lt;code&gt;#![no_std]&lt;/code&gt; compatible by default, which is neat if you need that.
Unfortunately, after copying around the C header files that define the configuration, I’m getting C compiler errors:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  C:&#92;Users&#92;Melody&#92;.cargo&#92;registry&#92;src&#92;index.crates.io-1949cf8c6b5b557f&#92;lvgl-sys-0.6.2&#92;vendor&#92;lv_drivers&#92;display&#92;fbdev.c(13): fatal error C1083: Cannot open include file: &#39;unistd.h&#39;: No such file or directory&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It seems like the LVGL configuration in the Rust binding samples is not designed to work on Windows, and if I figure out the flag to disable the framebuffer driver I get similar errors about not finding SDL, and if I disable SDL I get bindgen not being able to find libclang, and if I come back after fixing bindgen for a later library I get errors about the linker not finding SDL even though I already turned off SDL.
I wouldn’t be surprised if doing desktop development with LVGL is missing the point, though, and I’m holding it wrong by not cross compiling to some slightly cursed embedded Linux target.&lt;/p&gt;
&lt;h2 id=&quot;makepad&quot; tabindex=&quot;-1&quot;&gt;Makepad&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/makepad/makepad&quot;&gt;Makepad&lt;/a&gt; is another novel Rust GUI framework.
They’re publishing versions to crates.io but not creating Git tags to match those versions, so it’s hard to find the examples that are supposed to work with the published crates, and it’s easier to just point the dependency right at the Git repo so the examples from their main branch will work.
They’ve got a macro DSL with no documentation I can find, but a bit of persistence is all it takes to turn the simplest example into something that works:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token macro property&quot;&gt;live_design!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    import &lt;span class=&quot;token namespace&quot;&gt;makepad_widgets&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;base&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    import &lt;span class=&quot;token namespace&quot;&gt;makepad_widgets&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;theme_desktop_dark&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        ui&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Root&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            main_window &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Window&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScrollXYView&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    flow&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Down&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    spacing&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    align&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    label1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                    input1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextInput&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MatchEvent&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;handle_actions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cx&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cx&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; actions&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; input1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ui&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text_input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token macro property&quot;&gt;id!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;new_text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; input1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;changed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;actions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; label1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ui&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token macro property&quot;&gt;id!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            label1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;new_text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            label1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redraw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-makepad.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Narrator can’t even see the minimize/maximize/close buttons, much less the content of the window.
The IME opens in the corner of the display and has its own wrapper to show the kana while I’m typing, but when I press Enter the kanji get correctly entered into the text field.&lt;/p&gt;
&lt;p&gt;I kinda like the Blender aesthetic they’ve got for their stock widgets, but the lack of accessibility support is a downer, and the lack of documentation around the load-bearing DSL may be worse.
Maybe the documentation exists and I just can’t find it (they’ve got a Discord server that I’m not joining), but I suspect that Makepad is built for the Makepad team right now, and any utility anyone else can get out of it is coincidental.
A lot of projects start in that stage, and most of mine never leave, so that’s not a bad thing, but I do kinda wish Are We GUI Yet? was doing a little more pruning.
I’ve been working on this post for almost a week already, and I’m only 58% of the way through the list.&lt;/p&gt;
&lt;h2 id=&quot;masonry&quot; tabindex=&quot;-1&quot;&gt;masonry&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/linebender/xilem/tree/main/masonry&quot;&gt;Masonry&lt;/a&gt; is a pure-Rust GUI library that’s the successor to the discontinued Druid framework that I found really impressive early on.
It primarily serves as the foundation for Xilem, a kinda Elm-ish kinda SwiftUI-ish framework I’m looking forward to trying once I get all the way down the list.&lt;/p&gt;
&lt;p&gt;The last release of Masonry was 11 months ago, and it looks like a lot has changed since then, so I’m going to just point to the &lt;code&gt;main&lt;/code&gt; branch directly.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;on_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DriverCtx&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token lifetime-annotation symbol&quot;&gt;&#39;_&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _widget_id&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WidgetId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; action &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextChanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;new_text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render_root&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;edit_widget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; label&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;downcast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; new_text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        _ &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; main_widget &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Portal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Flex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_child&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_child&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Textbox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_spacer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VERTICAL_WIDGET_SPACING&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-masonry.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Confusingly, Narrator can tell that there’s a text field in here, but it’s wrong about where in the window it is.
Kanji input via the IME works, but the default font appears to not support the fullwidth Latin characters that show up in provisional states before the hiragana replace them, so there’s a bit of &lt;a href=&quot;https://en.wikipedia.org/wiki/Unicode_input#Availability&quot;&gt;tofu&lt;/a&gt; in the intermediate states &lt;code&gt;☐&lt;/code&gt;, &lt;code&gt;とう☐&lt;/code&gt;, and &lt;code&gt;とう☐☐&lt;/code&gt; (after the &lt;code&gt;t&lt;/code&gt;, &lt;code&gt;k&lt;/code&gt;, and &lt;code&gt;y&lt;/code&gt; in &lt;code&gt;toukyou&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;I think it’s fair to say that you aren’t really supposed to use Masonry directly, the intent is to build an architecture on top of Masonry.
The API ergonomics feel like they match that: you probably could build a nontrivial application directly in Masonry, but I wouldn’t recommend it.&lt;/p&gt;
&lt;h2 id=&quot;maycoon&quot; tabindex=&quot;-1&quot;&gt;Maycoon&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://maycoon-ui.github.io/&quot;&gt;Maycoon&lt;/a&gt; is another new from scratch pure Rust framework.
It looks like it’s still pretty new.
The quick start guide is out of date, but the in-repo examples are still good.
(It’s very nice that Cargo defaults to compiling examples as part of running tests.)&lt;/p&gt;
&lt;p&gt;Maycoon doesn’t appear to have a text input widget.
GPUI didn’t, either, but at least they had an example that involved building one from scratch that I could just copy.
Maycoon is too new to be usable for this task.&lt;/p&gt;
&lt;h2 id=&quot;pax&quot; tabindex=&quot;-1&quot;&gt;Pax&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.pax.dev/&quot;&gt;Pax&lt;/a&gt; is “a revolutionary new canvas for building apps &amp;amp; websites with AI.”
🤮🤮🤮
Shipping a real visual editor for your GUI DSL is a good idea, though, although I think I broke the fancy editor somehow because it stopped responding to anything and started logging a bunch of index out of bounds errors.&lt;/p&gt;
&lt;p&gt;Alas, the only desktop target Pax supports &lt;a href=&quot;https://docs.pax.dev/installation/app-targets/desktop/&quot;&gt;is macOS&lt;/a&gt;, which is useless to me today.
Maybe one day an AI bro who pays for Twitter will contribute something positive to society, but this is not that day.&lt;/p&gt;
&lt;h2 id=&quot;qmetaobject&quot; tabindex=&quot;-1&quot;&gt;qmetaobject&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/woboq/qmetaobject-rs%F3%B1%A6%98&quot;&gt;QMetaObject&lt;/a&gt; is a different approach to Qt bindings than &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#cxx-qt&quot;&gt;CXX-Qt&lt;/a&gt; that appears to try to put even more code into Rust.
Unfortunately, it appears to not work nicely with the &lt;code&gt;windows-msvc&lt;/code&gt; target, and I don’t appear to have a gcc toolchain installed in a place that it likes.&lt;/p&gt;
&lt;h2 id=&quot;relm&quot; tabindex=&quot;-1&quot;&gt;relm&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/antoyo/relm&quot;&gt;relm&lt;/a&gt; is named after Elm, which is neat.
Unfortunately, relm is built on the unmaintained GTK 3 bindings, and even if I did install GTK 3 I’m not certain it would’ve worked, because some load-bearing components are making incorrect UNIX-centric assumptions about how lists of paths work:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  pkg-config exited with status code 1
  &gt; PKG_CONFIG_PATH=C:&#92;Users&#92;Melody&#92;Projects&#92;_resources&#92;gtk-build&#92;gtk&#92;x64&#92;release&#92;lib&#92;pkgconfig PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config --libs --cflags gdk-3.0 &#39;gdk-3.0 &gt;= 3.22&#39;
  The system library `gdk-3.0` required by crate `gdk-sys` was not found.
  The file `gdk-3.0.pc` needs to be installed and the PKG_CONFIG_PATH environment variable must contain its parent directory.
  PKG_CONFIG_PATH contains the following:
      - C
      - &#92;Users&#92;Melody&#92;Projects&#92;_resources&#92;gtk-build&#92;gtk&#92;x64&#92;release&#92;lib&#92;pkgconfig&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I wonder if there’s a library that’s like relm but built on GTK 4?&lt;/p&gt;
&lt;h2 id=&quot;relm4&quot; tabindex=&quot;-1&quot;&gt;Relm4&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://relm4.org/&quot;&gt;Relm4&lt;/a&gt; is like relm but built on GTK 4.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token macro property&quot;&gt;view!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token namespace&quot;&gt;gtk&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Window&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        set_title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Simple app&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        set_default_size&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

        &lt;span class=&quot;token namespace&quot;&gt;gtk&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            set_orientation&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;gtk&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Orientation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Vertical&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            set_spacing&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            set_margin_all&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

            &lt;span class=&quot;token namespace&quot;&gt;gtk&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token attribute attr-name&quot;&gt;#[watch]&lt;/span&gt;
                set_label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                set_margin_all&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

            &lt;span class=&quot;token namespace&quot;&gt;gtk&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Entry&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                set_text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                connect_changed&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sender&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;x&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; sender&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Msg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SetLabel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;into&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-relm4.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;As was the case with the GTK 4 test, Narrator can’t even see the window chrome, and the IME works fine.&lt;/p&gt;
&lt;p&gt;This architecture is probably easier to work with at scale than throwing GTK 4 widgets around directly would be, but it also inherits all of the problems of GTK 4 on Windows, of which there are many.
Also, that &lt;code&gt;view!&lt;/code&gt; macro is (like all Rust macros) really annoying to try to debug if your input is slightly incorrect, which mine was while I tried to figure out how to get &lt;code&gt;connect_changed&lt;/code&gt; working.
If you love the widgets of GTK 4 but want a nicer architecture, you might like Relm4, but if you are not in that situation, this library has nothing to offer you.&lt;/p&gt;
&lt;h2 id=&quot;ribir&quot; tabindex=&quot;-1&quot;&gt;Ribir&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ribir.org/&quot;&gt;Ribir&lt;/a&gt; is a bespoke Rust GUI framework that markets itself as being “non-intrusive”, &lt;a href=&quot;https://ribir.org/docs/introduction#non-intrusive-programming-model&quot;&gt;meaning&lt;/a&gt; that your GUI components read and write your model objects directly, with no intermediate state layers or constraints on model objects.
I’m not sure that this is actually a problem with other frameworks, and I feel like there might be a clearer way to pitch that, but it’s in principle good to be trying to stand out in what I had not quite realized was this crowded of a landscape.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://ribir.org/docs/introduction&quot;&gt;website docs&lt;/a&gt; only have options for 0.2.x and main despite 0.3.0 coming out last August.&lt;/p&gt;
&lt;p&gt;Well, I can’t get the pile of macro magic to work correctly, and I’m not sure why. This code looks like it should work, but the text I type into the input field doesn’t actually appear in the label:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token macro property&quot;&gt;fn_widget!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Input&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; auto_focus&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;$input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Column&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;pipe!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$input&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            on_key_up&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;_&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-ribir.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Windows Narrator can’t see any of the text inside this window.
The IME opens in a random corner of my monitor, the composer state is hidden, and the default font doesn’t have kanji, but there are two missing-glyph symbols after I confirm from the IME converter, so it seems like the IME technically a little bit works.&lt;/p&gt;
&lt;p&gt;If the pitch was that you don’t need to think about the mechanisms of state going into and out of the UI, then the pitch was overstated, because I have an app that looks like it should work but doesn’t because that glue has gone wrong.&lt;/p&gt;
&lt;h2 id=&quot;rinf&quot; tabindex=&quot;-1&quot;&gt;Rinf&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/cunarist/rinf&quot;&gt;Rinf&lt;/a&gt; is another framework for using Rust for business logic and Flutter for UIs.
I want that even less now than I did a few days ago when I wrote the &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#flutter_rust_bridge&quot;&gt;flutter_rust_bridge&lt;/a&gt; section.&lt;/p&gt;
&lt;h2 id=&quot;rui&quot; tabindex=&quot;-1&quot;&gt;rui&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/audulus/rui&quot;&gt;rui&lt;/a&gt; is a Rust GUI library “inspired by SwiftUI”; as someone who’s worked with SwiftUI a lot at my day job, I’m reading that as meaning that this will be almost good but its state management will be clumsy and annoying to work with.&lt;/p&gt;
&lt;p&gt;It’s pretty alright at scales this small, though, so that may not be fair:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token function&quot;&gt;rui&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cx&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;vstack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Auto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;text_editor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Auto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-rui.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Narrator can’t see this text, and the IME won’t even activate.&lt;/p&gt;
&lt;p&gt;I think I kinda like the aesthetic — a strong &lt;a href=&quot;https://www.brumale.xyz/anodina/&quot;&gt;default font&lt;/a&gt; goes a long way towards giving a GUI toolkit a cohesive look — and I also like the lack of load-bearing macros, but I’m of course less wild about the lack of accessibility or IME support.&lt;/p&gt;
&lt;h2 id=&quot;slint&quot; tabindex=&quot;-1&quot;&gt;Slint&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://slint.dev/&quot;&gt;Slint&lt;/a&gt; is like if Qt had been invented 30 years later.
Like Qt, it’s got its own bespoke DSL, and like Qt, the business model is to be GPL and sell exceptions, but unlike Qt, desktop and web exceptions are free and it’s only embedded development that they want you to pay for.
There’s no inherent guarantee that that’ll always be the case, but also, doing this stuff right is hard and I am in favor of people getting paid for the work that they do.&lt;/p&gt;
&lt;p&gt;This DSL kinda rules, I can’t lie:&lt;/p&gt;
&lt;pre class=&quot;language-slint&quot;&gt;&lt;code class=&quot;language-slint&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppWindow&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;inherits&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Window&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;VerticalBox&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;Text&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;LineEdit&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-slint.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Narrator works perfectly.
The IME renders all its provisional states as just missing-character glyphs &lt;code&gt;☐&lt;/code&gt;, which makes me think the default font just doesn’t have fullwidth Latin or hiragana or kanji, but once I accept the kanji from the converter I get the actual kanji displaying correctly, so that may actually be a bug and not just a font selection issue.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt; as the operator to create a &lt;a href=&quot;https://docs.slint.dev/latest/docs/slint/guide/language/concepts/reactivity/#two-way-bindings&quot;&gt;two-way data binding&lt;/a&gt; is really clever, I hope they’re proud of that.
Also, since this DSL is a standalone language and not just a pile of Rust macros, the ceiling on error message quality is way higher.
I don’t think they’re actually hitting that ceiling yet, though: before I stumbled into the two-way data binding operator I had a callback with a syntax error and the Slint compiler wasn’t a ton of help.
They’ve even got C++, JS, and Python bindings, and it’s cool that you can write a library once in Rust and then use it from that many different languages.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#summary&quot;&gt;suggested&lt;/a&gt; towards the end of my previous excursion here that Slint (then still called SixtyFPS) would merit a more thorough look once it had better accessibility support, and it certainly does.
It’s not perfect, but it’s come a long way in the last four years, and I’m curious what the next couple years will look like for Slint.&lt;/p&gt;
&lt;h2 id=&quot;tauri&quot; tabindex=&quot;-1&quot;&gt;Tauri&lt;/h2&gt;
&lt;p&gt;Do you like Electron but wish it was Rust?
&lt;a href=&quot;https://tauri.app/&quot;&gt;Tauri&lt;/a&gt; is that.
To their credit, they’ve also swapped out the bundled Chromium for just binding to the system’s inherent web browser, whether that’s &lt;a href=&quot;https://developer.microsoft.com/en-us/microsoft-edge/webview2/&quot;&gt;WebView2&lt;/a&gt; on Windows, WebKit on macOS, or WebKitGTK on Linux, so Tauri applications don’t fill your hard drive with a dozen copies of the same web browser.
Unfortunately, they have not touched the architecture; you still have a host process running outside of the browser and an independent frontend running inside the browser.&lt;/p&gt;
&lt;p&gt;Building that frontend is not something Tauri is concerned with; you have to decide for yourself what you want your stack to look like.
I want to write Rust, so in the new project wizard I pick Rust as my frontend language (rather than JavaScript/TypeScript or, for all three diehard Blazor fans, C#).
I’m then asked which Rust frontend framework I want, and I don’t really want to have to pick between Dioxus and Leptos and Sycamore and Yew right now, so I pick “Vanilla” assuming that it’ll give me bare &lt;a href=&quot;https://rustwasm.github.io/wasm-bindgen/web-sys/index.html&quot;&gt;web-sys&lt;/a&gt; to make the same DOM API calls as vanilla JS but in Rust; I’ve done this before, and it’s not very good, but at this scale it’d be completely tolerable.
Instead, though, “Vanilla” means vanilla JS even if I selected Rust, and since I selected Rust I don’t even get an option of vanilla TS instead.
This was &lt;a href=&quot;https://github.com/tauri-apps/create-tauri-app/issues/550&quot;&gt;reported a year and a half ago&lt;/a&gt; and ignored.&lt;/p&gt;
&lt;p&gt;If I’m stuck making a choice, I need an excuse to ignore three of the four provided options.
I already looked at Dioxus, so that’d be boring to use again.
Yew’s 0.22 release was &lt;a href=&quot;https://yew.rs/blog/2024/10/14/release-0-22&quot;&gt;announced in October 2024&lt;/a&gt;, &lt;a href=&quot;https://github.com/yewstack/yew/commit/d77cf0196b65486a35b9d1a02aaaed6faafbf8cc&quot;&gt;added to the changelog in December 2024&lt;/a&gt;, and released on crates.io &lt;a href=&quot;https://crates.io/crates/yew/versions&quot;&gt;literally never (as of April 2025)&lt;/a&gt;; that’s not great.
The &lt;a href=&quot;https://book.leptos.dev/&quot;&gt;Leptos book&lt;/a&gt; says that it’s “most similar to frameworks like Solid and Sycamore”; maybe that’s a sign that it doesn’t matter, or maybe it’s a sign that I should try both.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// leptos&lt;/span&gt;
&lt;span class=&quot;token attribute attr-name&quot;&gt;#[component]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IntoView&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; set_label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; update_label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;ev&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; v &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;event_target_value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;ev&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        set_label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token macro property&quot;&gt;view!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; on&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;input&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;update_label &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// sycamore&lt;/span&gt;
&lt;span class=&quot;token attribute attr-name&quot;&gt;#[component]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create_signal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token macro property&quot;&gt;view!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        p &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r#&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bind&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-tauri.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Narrator can see all this text, and the IME provisional states are drawn in the corner of the screen rather than inline but they work correctly and the final kanji look fine.&lt;/p&gt;
&lt;p&gt;I guess the main difference between these two Web frameworks that we can see from here is that Sycamore has a &lt;code&gt;bind:&lt;/code&gt; modifier for attributes (very good!) but can’t quite handle &lt;code&gt;input type=&lt;/code&gt; because &lt;code&gt;type&lt;/code&gt; is a Rust keyword and requires &lt;code&gt;r#type&lt;/code&gt; instead (very bad!).
The largest issue, though, is something I haven’t captured here, because today’s task is so trivial it can be done entirely within the frontend, and I let that count for GTK so I have to let it count here too.&lt;/p&gt;
&lt;p&gt;If I add the requirement that the new value of the label be printed to standard output when the label changes (as a stand-in for, say, performing some file I/O), in most frameworks it suffices to either add a &lt;code&gt;println!&lt;/code&gt; to the existing event handler or subscribe to a two-way-bound state with a &lt;code&gt;println!&lt;/code&gt;.
Even Dioxus, built on the same WebView2/WebKitGTK library as Tauri, will do the right thing if I &lt;code&gt;println!&lt;/code&gt; from an event handler.
In Tauri, though, a &lt;code&gt;println!&lt;/code&gt; from the frontend will be completely ignored, and if we want to be able to &lt;code&gt;println!&lt;/code&gt; we need inter-process communication between the frontend and the host process.&lt;/p&gt;
&lt;p&gt;In the host, this is nice and easy due to the magic of proc macros:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[tauri::command]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token macro property&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the frontend, however, there is a lot more boilerplate:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[wasm_bindgen]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;C&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    #&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wasm_bindgen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;js_namespace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;window&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;__TAURI__&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;core&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cmd&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JsValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JsValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(Serialize, Deserialize)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;PrintArgs&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token lifetime-annotation symbol&quot;&gt;&#39;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token lifetime-annotation symbol&quot;&gt;&#39;a&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token attribute attr-name&quot;&gt;#[component]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;create_effect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;spawn_local_scoped&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; args &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;serde_wasm_bindgen&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PrintArgs&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;label &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;print&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes me sad for two reasons.
The first is a question of principle: this arbitrary boundary drawn through the middle of my application means that if I discover I need a new piece of functionality I may need to move a substantial chunk of code from the frontend to the backend, and if it’s something load-bearing within the frontend I’m going to have a real motherfucker of a time pivoting my architecture on short notice for no reason.
The second is a question of type safety: as you may have noticed, the IPC interface in the frontend takes an &lt;code&gt;&amp;amp;str&lt;/code&gt; for the command name and a &lt;code&gt;JsValue&lt;/code&gt; for the command arguments, meaning frontend IPC calls have &lt;em&gt;no type checking&lt;/em&gt;.
Indeed, if I rename the argument in the host from &lt;code&gt;text&lt;/code&gt; to &lt;code&gt;text_to_print&lt;/code&gt; and don’t update the &lt;code&gt;PrintArgs&lt;/code&gt; in the frontend, I get not even a warning at compile time, and at runtime I get&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;panicked at src&#92;app.rs:6:1:
unexpected exception: JsValue(&quot;invalid args `textToPrint` for command `print`: command print missing required key textToPrint&quot;)

Uncaught RuntimeError: unreachable&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I then change the label, I get&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;panicked at C:&#92;Users&#92;Melody&#92;.cargo&#92;registry&#92;src&#92;index.crates.io-1949cf8c6b5b557f&#92;wasm-bindgen-futures-0.4.50&#92;src&#92;task&#92;singlethread.rs:103:37:
already borrowed: BorrowMutError&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and this is all just misery upon misery.
Half the point of Rust is the sheer quantity of bugs that it can catch at compile time, and if your IPC is just tossing strings around and praying at runtime, you may as well be just writing vanilla JavaScript.
(I checked, and even if your frontend is TypeScript, the &lt;code&gt;invoke&lt;/code&gt; IPC boundary just takes a &lt;code&gt;string&lt;/code&gt; rather than a union of the actual legal values.)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;edit 2026-01-03&lt;/em&gt;: I learned at the &lt;a href=&quot;https://www.meetup.com/utah-rust/&quot;&gt;Utah Rust meetup&lt;/a&gt; that if you’re writing your frontend in TypeScript you can use &lt;a href=&quot;https://github.com/specta-rs/tauri-specta&quot;&gt;tauri-specta&lt;/a&gt; to generate type safe IPC wrappers.&lt;/p&gt;
&lt;p&gt;Hilariously, the Tauri docs &lt;a href=&quot;https://tauri.app/develop/calling-rust/&quot;&gt;claim&lt;/a&gt; that the command mechanism is “for reaching Rust functions with type safety”, as distinct from their event system, which is even less type safe.
With events, you’re tossing strings and arbitrary JavaScript payloads around in both the frontend and the host, so technically it’s not false to claim that only doing that on one end is more type safe, but type checking only at one end is like putting a lock on your bike but not running it through the bike rack: you aren’t tying two things together, you’re just tying one thing to itself and praying.
Even to the limited extent that half of type safety could be useful, though, they’ve picked the wrong half: there’s inherently only one implementation of the command, but there can be many calls to it, so it’d be far more valuable to have type safety at the call sites than at the implementation site.
Commands only go from the frontend to the host, so type checking in the host and YOLOing in the frontend is being picky at the receive end and sloppy at the send end, which is the exact opposite of &lt;a href=&quot;https://en.wikipedia.org/wiki/Robustness_principle&quot;&gt;Postel’s law&lt;/a&gt;.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;digression: Postel’s law&lt;/summary&gt;
&lt;aside&gt;
&lt;p&gt;The common formulation of Postel’s law is “be conservative in what you emit and liberal in what you accept”, and it’d be more recognizable if I used those terms instead of “picky” and “sloppy”.
I haven’t done that, though, because you should be conservative never and liberal very rarely.
Be radically leftist in everything, even technical blog posts that aren’t intrinsically political.&lt;/p&gt;
&lt;/aside&gt;
&lt;/details&gt;
&lt;p&gt;If there was an unnecessary IPC boundary but it was type safe, or if there was bad stringly typed nonsense somewhere but no unnecessary IPC boundary, I might find it within myself to forgive that, but the combination of the entirely unnecessary split-brain architecture with the absolute lack of type safety at the boundary means that I think I genuinely hate Tauri.&lt;/p&gt;
&lt;h2 id=&quot;tinyfiledialogs&quot; tabindex=&quot;-1&quot;&gt;tinyfiledialogs&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://sourceforge.net/projects/tinyfiledialogs/%F3%B1%A6%98&quot;&gt;tinyfiledialogs&lt;/a&gt; is a C library with &lt;a href=&quot;https://github.com/jdm/tinyfiledialogs-rs&quot;&gt;Rust bindings&lt;/a&gt; providing a handful of basic prompts, including message boxes, text input a la JS’s &lt;code&gt;window.prompt&lt;/code&gt;, and, as the name implies, file dialogs.
I can’t actually complete today’s task with those.&lt;/p&gt;
&lt;h2 id=&quot;tk&quot; tabindex=&quot;-1&quot;&gt;Tk&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tcl-lang.org/&quot;&gt;Tk&lt;/a&gt; is the GUI framework for &lt;a href=&quot;https://www.tcl-lang.org/&quot;&gt;Tcl&lt;/a&gt;, a fascinating late-80s programming language that I would describe as weird in a Lua way, weird in a LISP way, and weird in its own way all at once.
&lt;a href=&quot;https://github.com/oooutlk/tcltk&quot;&gt;Evidently&lt;/a&gt; it has Rust bindings.&lt;/p&gt;
&lt;p&gt;The docs for the Rust bindings &lt;a href=&quot;https://oooutlk.github.io/tk/installing_tk_on_windows.html&quot;&gt;recommend&lt;/a&gt; ActiveState’s ActiveTcl, but I can’t find an actual way to download ActiveTcl, and ActiveState’s &lt;a href=&quot;https://www.activestate.com/pricing/&quot;&gt;pricing page&lt;/a&gt; has an FAQ entry that doesn’t inspire confidence:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Can I still get ActivePerl, ActivePython, or ActiveTcl?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you still need access to our legacy releases, please get in touch with us via our Contact us page.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’m installing &lt;a href=&quot;https://www.magicsplat.com/tcl-installer/index.html&quot;&gt;Magicsplat Tcl&lt;/a&gt; instead.
It doesn’t appear to have been enough, though:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  thread &#39;main&#39; panicked at C:&#92;Users&#92;Melody&#92;.cargo&#92;registry&#92;src&#92;index.crates.io-1949cf8c6b5b557f&#92;bindgen-0.64.0&#92;lib.rs:2393:31:
  Unable to find libclang: &quot;couldn&#39;t find any valid shared libraries matching: [&#39;clang.dll&#39;, &#39;libclang.dll&#39;], set the `LIBCLANG_PATH` environment variable to a path where one of these files can be found (invalid: [])&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://rust-lang.github.io/rust-bindgen/requirements.html#windows&quot;&gt;Apparently&lt;/a&gt; this can be fixed with just &lt;code&gt;winget install LLVM.LLVM&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; tk &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;make_tk!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; c &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; root
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add_ttk_frame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;c&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sticky&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;nwes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;grid_columnconfigure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;grid_rowconfigure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; c
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add_ttk_label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;label&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; entry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; c
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add_ttk_entry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;entry&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;textvariable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;label&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token namespace&quot;&gt;event&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;any_key_release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token macro property&quot;&gt;tclosure!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tk&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-tk.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Windows Narrator can’t see any of this text, and the IME works fine.&lt;/p&gt;
&lt;p&gt;I guess translating &lt;code&gt;ttk::label &amp;quot;label&amp;quot; -text &amp;quot;Hello, world!&amp;quot;&lt;/code&gt; (which is how Tcl works, I’m fairly certain) to &lt;code&gt;add_ttk_label(&amp;quot;label&amp;quot; -text(&amp;quot;Hello, world!&amp;quot;))&lt;/code&gt; makes sense for existing Tcl users, but it feels really weird, and &lt;code&gt;cargo fmt&lt;/code&gt; is understandably confused by it.
It’s also hard to tell which options are actually supported on which widgets, because of all the trait magic that’s going on; it took me a while to discover that &lt;code&gt;.insert()&lt;/code&gt; is the way you set the initial text of the text field.
There’s also a bit of jank in the example — &lt;a href=&quot;https://oooutlk.github.io/tk/a_first_real_example.html&quot;&gt;apparently&lt;/a&gt; attaching a callback to a button requires &lt;code&gt;unsafe&lt;/code&gt;.
Plus, after I uninstalled Tcl/Tk, the binary stopped working, so you can only ship a binary to people who also manually install Tcl/Tk.&lt;/p&gt;
&lt;p&gt;If you already know and love Tcl/Tk and just wish (heh) you could use it from Rust, maybe you’d find this useful, but I am not in that situation and I do not.&lt;/p&gt;
&lt;h2 id=&quot;vizia&quot; tabindex=&quot;-1&quot;&gt;Vizia&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vizia/vizia&quot;&gt;Vizia&lt;/a&gt; is another novel Rust GUI library.
The &lt;a href=&quot;https://book.vizia.dev/quickstart/setup.html&quot;&gt;book&lt;/a&gt; says to depend on the Git repo rather than the latest release, which I usually try to avoid, but sure.
Conveniently, the counter example is pretty easy to adapt for what I need here, and there’s no list of widgets in the book (at least that I could find) but there is a list of widgets &lt;a href=&quot;https://docs.vizia.dev/vizia/views/index.html#structs&quot;&gt;in the docs&lt;/a&gt; so I don’t have to spend fifteen minutes guessing what they call their text field.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(Lens)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;AppData&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;AppEvent&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;SetText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Model&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppData&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;app_event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; app_event &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;AppEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SetText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;cx&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;AppData&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;VStack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;cx&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token class-name&quot;&gt;Textbox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_edit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;cx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; cx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AppEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SetText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;alignment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Alignment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Center&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Counter&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Configure window properties&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;inner_size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-vizia.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Something weird happens to the internal padding in the text field when I focus it, though:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-vizia-focused.png&quot; alt=&quot;the same window but with the text within the field further down&quot;&gt;&lt;/p&gt;
&lt;p&gt;Narrator can see that there’s a text label and a text input within this window, but it doesn’t appear to be able to see what the text actually is in either widget, which is a new one.
The IME converter dropdown appears in the corner of the screen, but the provisional states draw correctly in the text field, and the final kanji go in correctly.&lt;/p&gt;
&lt;p&gt;The structure that’s present here seems promising, but Vizia doesn’t seem to be quite ready for serious use yet.&lt;/p&gt;
&lt;h2 id=&quot;webrender&quot; tabindex=&quot;-1&quot;&gt;WebRender&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/servo/webrender&quot;&gt;WebRender&lt;/a&gt; is part of the guts of Servo, the Rust-based web browser engine that Mozilla founded and then &lt;a href=&quot;https://www.zdnet.com/article/mozilla-lays-off-250-employees-while-it-refocuses-on-commercial-products/&quot;&gt;abandoned&lt;/a&gt; (an event which prompted &lt;a href=&quot;https://www.boringcactus.com/2020/08/13/post-open-source.html&quot;&gt;my biggest hit on this blog&lt;/a&gt;).
It’s been picked back up and is &lt;a href=&quot;https://servo.org/&quot;&gt;some amount of back&lt;/a&gt;, although quite how back its new team wants it to be &lt;a href=&quot;https://github.com/servo/servo/discussions/36379&quot;&gt;is still up in the air&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The in-repo &lt;a href=&quot;https://github.com/servo/webrender/tree/beccb13247f1c3799957e4c85468d79ba47033c2/examples&quot;&gt;examples&lt;/a&gt; are so old they’re &lt;a href=&quot;https://github.com/servo/webrender/blob/beccb13247f1c3799957e4c85468d79ba47033c2/examples/Cargo.toml#L7&quot;&gt;still using the 2018 language edition&lt;/a&gt;, and the &lt;a href=&quot;https://crates.io/crates/webrender/versions&quot;&gt;latest version published on crates.io&lt;/a&gt; is from 2020, so I guess I’m depending on the Git repo again.
There’s a &lt;code&gt;main&lt;/code&gt; branch last updated four months ago and an &lt;code&gt;upstream&lt;/code&gt; branch updated last week; presumably the &lt;code&gt;upstream&lt;/code&gt; branch is the right one to use, but it’d be nice if that was actually explained somewhere.&lt;/p&gt;
&lt;p&gt;Looking a bit at the examples, WebRender does not have widgets, it just has shapes; this is a low-level graphics crate and not a high-level GUI crate, and it’s not clear why it’s on the Are We GUI Yet? list.&lt;/p&gt;
&lt;h2 id=&quot;windows&quot; tabindex=&quot;-1&quot;&gt;windows&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.microsoft.com/en-us/windows/&quot;&gt;Windows&lt;/a&gt; is an operating system created by Microsoft in the mid-1980s; its APIs have &lt;a href=&quot;https://github.com/microsoft/windows-rs&quot;&gt;Rust bindings&lt;/a&gt;.
I do not know how to do GUI development with bare Win32 API calls, though, so doing them from Rust is not exciting.
XAML, which is to my understanding the shiny new way to do desktop GUI development in specifically Windows, is &lt;a href=&quot;https://kennykerr.ca/rust-getting-started/what-apis-are-included.html&quot;&gt;explicitly not included in the &lt;code&gt;windows&lt;/code&gt; crate&lt;/a&gt; because “Xaml is also focused and tailored for C# app development so this API isn&#39;t applicable to Rust developers.”
This is confusing, because the XAML-based &lt;a href=&quot;https://github.com/microsoft/microsoft-ui-xaml&quot;&gt;WinUI 3&lt;/a&gt; is also supported on C++, not just C#.
Ah, Microsoft.&lt;/p&gt;
&lt;h2 id=&quot;winsafe&quot; tabindex=&quot;-1&quot;&gt;WinSafe&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rodrigocfd/winsafe&quot;&gt;WinSafe&lt;/a&gt; is apparently a set of Rust wrappers around the Win32 GUI API.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;MyWindow&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    wnd&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;gui&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WindowMain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;gui&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    field&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;gui&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Edit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyWindow&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; wnd &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;gui&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WindowMain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token namespace&quot;&gt;gui&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WindowMainOpts&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;My window title&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_owned&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                size&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;gui&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;wnd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token namespace&quot;&gt;gui&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LabelOpts&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                position&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; field &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;gui&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Edit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;wnd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token namespace&quot;&gt;gui&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;EditOpts&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                position&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; new_self &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; wnd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; field &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        new_self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// attach our events&lt;/span&gt;
        new_self
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AnyResult&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wnd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run_main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// simply let the window manager do the hard work&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; ready &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Arc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AtomicBool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; ready2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ready&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wnd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wm_create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;_&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            ready2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ordering&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SeqCst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; self2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;field&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;en_change&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; ready&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Ordering&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SeqCst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                self2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;self2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;field&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-winsafe.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Unsurprisingly, Narrator and the IME both work.&lt;/p&gt;
&lt;p&gt;Manual positioning is no good, and the alternatives involve Win32 &lt;code&gt;.res&lt;/code&gt; file editing and other Pandora’s boxen I don’t want to open.
Also, I had to add that &lt;code&gt;ready&lt;/code&gt; tracking myself, because the callback was firing for the first time before the window had been created, which was causing crashes.
This isn’t great, and of course it’s only useful on Windows.&lt;/p&gt;
&lt;h2 id=&quot;xilem&quot; tabindex=&quot;-1&quot;&gt;Xilem&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/linebender/xilem/&quot;&gt;Xilem&lt;/a&gt; is another novel pure-Rust framework, built on top of the previously discussed &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#masonry&quot;&gt;masonry&lt;/a&gt;, the successor to Druid, which I thought was really promising when I first got started with this series.
It hasn’t had a numbered release in almost a year, so I’m pointing at the Git repo again.&lt;/p&gt;
&lt;p&gt;Conveniently, they’ve got a todo list example that I can sculpt into what I need today mostly by deleting.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;State&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;app_logic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WidgetView&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;textbox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; new_value&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; new_value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_loop&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventLoopBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventLoopError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        label&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Xilem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; app_logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run_windowed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_loop&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;First Example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;into&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventLoopError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;EventLoop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_user_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-xilem.png&quot; alt=&quot;a screenshot of a text label and a text field both saying Hello, world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;As was the case when using Masonry directly, Narrator sees the text but is wrong about its position, and some IME provisional states have missing glyphs but the IME behavior works fine.&lt;/p&gt;
&lt;p&gt;This architecture seems pretty neat, although of course this is not sufficient to know how well it works at any reasonable scale.
Honestly, aside from the screen reader jank, my only gripe is the lack of versioning, and presumably that’ll be coming as Xilem matures.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;What a list, huh?
If I had counted how many of these there were before I got started, I might’ve never started, because 43 is a lot.
It’s good that there are this many different people working in this space, but the more options there are, the more important it is for people to be sifting through them to distinguish the ones that aren’t ready to use from the ones that are.&lt;/p&gt;
&lt;p&gt;Let’s pick some winners.
If you’d rather take the quirks of CSS layout over the quirks of some other layout engine, &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#dioxus&quot;&gt;Dioxus&lt;/a&gt; seems like a pretty reasonable choice; Diet Electron is definitely better than regular Electron, and that may be good enough for you, but it feels not-better-enough to me (although my sense of not-better-enough is &lt;a href=&quot;https://en.wikipedia.org/wiki/Obsessive%E2%80%93compulsive_personality_disorder&quot;&gt;non-standard&lt;/a&gt;).
If you like DSL-driven UIs that are putting serious effort into developer tooling, &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#slint&quot;&gt;Slint&lt;/a&gt; might be for you.
If you want to avoid DSLs and macros and write only regular Rust, &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#egui&quot;&gt;egui&lt;/a&gt; offers that.
If you’re looking for something to invest in early, &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#freya&quot;&gt;Freya&lt;/a&gt; and &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#xilem&quot;&gt;Xilem&lt;/a&gt; are both basically usable now if you don’t mind living on the bleeding edge and putting up with some jank.
There are open issues for improving accessibility in &lt;a href=&quot;https://github.com/lapce/floem/issues/8&quot;&gt;Floem&lt;/a&gt; and &lt;a href=&quot;https://github.com/iced-rs/iced/issues/552&quot;&gt;iced&lt;/a&gt;, so it may be worth at least keeping an eye on those issues, although the iced issue has been open for 4½ years now.&lt;/p&gt;
&lt;p&gt;I would not describe any of these as a super easy slam dunk obviously correct choice, but there are a lot of reasonable options available, and that’s better than 2021, where I was &lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#whining&quot;&gt;longing for wxPython&lt;/a&gt; by the end of the post.
Maybe better things are possible after all.&lt;/p&gt;
&lt;p&gt;Now if you’ll excuse me, I’ve got some &lt;code&gt;cargo clean&lt;/code&gt;s to run:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2025-04-13-properties.png&quot; alt=&quot;a folder taking up 67.8 GB of disk space&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-table&quot; tabindex=&quot;-1&quot;&gt;The Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;library&lt;/th&gt;
&lt;th&gt;works at all?&lt;/th&gt;
&lt;th&gt;screen reader accessible?&lt;/th&gt;
&lt;th&gt;IME works?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#azul&quot;&gt;Azul&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;linker hell&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#cacao&quot;&gt;cacao&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;macOS-specific&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#core-foundation&quot;&gt;core-foundation&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;macOS-specific&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#crux&quot;&gt;Crux&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;no desktop targets&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#cushy&quot;&gt;Cushy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;composer hidden, converter works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#cxx-qt&quot;&gt;CXX-Qt&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;linker hell&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#dioxus&quot;&gt;Dioxus&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#dominator&quot;&gt;Dominator&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;web-specific&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#egui&quot;&gt;egui&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;composer works, Tab press stolen from converter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#floem&quot;&gt;Floem&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#fltk&quot;&gt;fltk&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;with extra crate&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#flutter_rust_bridge&quot;&gt;flutter_rust_bridge&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;kinda, but state hell&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;kinda, but state hell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#freya&quot;&gt;Freya&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;mostly, but some content missing&lt;/td&gt;
&lt;td&gt;composer hidden, converter works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#fui&quot;&gt;fui&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;qmake hell&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#gemgui&quot;&gt;GemGui&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;technically&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#gpui&quot;&gt;GPUI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#gtk-3&quot;&gt;GTK 3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;unmaintained&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#gtk-4&quot;&gt;GTK 4&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#iced&quot;&gt;Iced&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#imgui&quot;&gt;imgui&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#kas&quot;&gt;KAS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#kittest&quot;&gt;kittest&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;only for testing&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#leptos&quot;&gt;Leptos&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;web-specific&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#lvgl&quot;&gt;lvgl&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;C dependency hell&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#makepad&quot;&gt;Makepad&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;composer outside window, converter works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#masonry&quot;&gt;masonry&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;content but not position&lt;/td&gt;
&lt;td&gt;yes! but some temporary tofu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#maycoon&quot;&gt;Maycoon&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;no text input widget&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#pax&quot;&gt;Pax&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;no Windows support&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#qmetaobject&quot;&gt;qmetaobject&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;no windows-msvc&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#relm&quot;&gt;relm&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;uses unmaintained GTK 3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#relm4&quot;&gt;Relm4&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#ribir&quot;&gt;Ribir&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;kinda, but state hell&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;composer hidden, converter works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#rinf&quot;&gt;Rinf&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;does not use Rust for GUI&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#rui&quot;&gt;rui&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#slint&quot;&gt;Slint&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;missing glyphs in provisional states but logic works and final kanji displayed correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#tauri&quot;&gt;Tauri&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;composer outside window, converter works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#tinyfiledialogs&quot;&gt;tinyfiledialogs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;not general-purpose&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#tk&quot;&gt;Tk&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;nope&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#vizia&quot;&gt;Vizia&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;structure but not content&lt;/td&gt;
&lt;td&gt;converter outside window, everything works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#webrender&quot;&gt;WebRender&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;too low-level&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#windows&quot;&gt;windows&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;i don’t know Win32&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#winsafe&quot;&gt;WinSafe&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html#xilem&quot;&gt;Xilem&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;yes!&lt;/td&gt;
&lt;td&gt;content but not position&lt;/td&gt;
&lt;td&gt;yes! but some temporary tofu&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content>
  </entry>
  <entry>
    <title>Status Update</title>
    <link href="https://www.boringcactus.com/2024/10/07/status-update.html" />
    <updated>2024-10-07T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2024/10/07/status-update.html</id>
    <content type="html">&lt;p&gt;been a minute, hasn’t it?&lt;/p&gt;
&lt;p&gt;i’ve been mostly active on &lt;a href=&quot;https://cohost.org/cactus&quot;&gt;cohost&lt;/a&gt;, but it’s shutting down. this means two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;i expect to be more active on here moving forwards, because cohost was the easiest place i had to do long-form writing, and i haven’t made this blog much easier to write on but it has the advantage of not being read-only as of last week.&lt;/li&gt;
&lt;li&gt;at some point soon i will be importing some to most of my posts on cohost into this blog so that they live somewhere durably.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;rss subscribers, if you see a flood of new posts in the next week or two, congratulations and i’m sorry.
if you don’t, it’s probably because i’ve partitioned out the noise &lt;a href=&quot;https://cohost.org/cactus/post/7836734-i-think-if-i-rehost&quot;&gt;into “posts”, “scraps”, and “nonsense”&lt;/a&gt; and used the existing feed URL for only “posts”; i haven’t decided how i want to approach that just yet.&lt;/p&gt;
&lt;p&gt;also, while i’m in here, i’ve done &lt;a href=&quot;https://solipsist.social/&quot;&gt;another conceptual art&lt;/a&gt;, which you may find compelling depending on your tastes in art.
(if you are behind on your cactus lore, you may have missed my &lt;a href=&quot;https://somnolentsucculent.studio/fountain-1917/&quot;&gt;first conceptual art&lt;/a&gt;, which was a lot more of an achievement.)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Eggbug Forever: a custom minion mod for FFXIV</title>
    <link href="https://www.boringcactus.com/2024/09/20/eggbug-forever-ffxiv.html" />
    <updated>2024-09-20T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2024/09/20/eggbug-forever-ffxiv.html</id>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2024-09-20-eggbug-forever-ffxiv-1.png&quot; alt=&quot;a purple haired catgirl /showleft-ing to highlight eggbug floating in the air&quot;&gt;&lt;/p&gt;
&lt;p&gt;The real eggbug was the friends we made along the way. We carry him with us forever.&lt;/p&gt;
&lt;p&gt;A very simple import of Xenon Fossil’s &lt;a href=&quot;https://cohost.org/xenonfossil/post/7629261-https-xenonfossil&quot;&gt;low-poly eggbug model&lt;/a&gt; replacing the Wind-up Airship and/or the Great Serpent of Ronka (the Great Serpent animations are adorable but the rigging is a little janky so the Wind-up model may be better for gposing).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.xivmodarchive.com/modid/117001&quot;&gt;XIV Mod Archive&lt;/a&gt; | &lt;a href=&quot;https://heliosphere.app/mod/qbmgcyzy3n49dc90djpppegp0c&quot;&gt;Heliosphere&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;(30-sep-2024)&quot; tabindex=&quot;-1&quot;&gt;&lt;small&gt;(30 Sep 2024)&lt;/small&gt;&lt;/h1&gt;
&lt;div style=&quot;display: flex; flex-flow: row; align-items: center&quot;&gt;
&lt;img alt=&quot;eggbug wiggling and bouncing around next to a catgirl sitting on the ground in ffxiv&quot; src=&quot;https://www.boringcactus.com/assets/2024-09-20-eggbug-forever-ffxiv-2.gif&quot; style=&quot;max-width: 50%&quot;&gt;&lt;img alt=&quot;eggbug bouncing along behind a catgirl running towards the camera in ffxiv&quot; src=&quot;https://www.boringcactus.com/assets/2024-09-20-eggbug-forever-ffxiv-3.gif&quot; style=&quot;max-width: 50%&quot;&gt;
&lt;/div&gt;
&lt;p&gt;eggbug forever. see y’all around.&lt;/p&gt;
&lt;div style=&quot;font-family: &#39;Atkinson Hyperlegible&#39;, sans-serif; font-size: 0.875rem; line-height: 1.25rem; color: #686664&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://cohost.org/cactus/tagged/ffxiv&quot;&gt;#ffxiv&lt;/a&gt; &lt;a href=&quot;https://cohost.org/cactus/tagged/eggbug&quot;&gt;#eggbug&lt;/a&gt; &lt;a href=&quot;https://cohost.org/cactus/tagged/Goodbye%20cohost&quot;&gt;#Goodbye cohost&lt;/a&gt; &lt;a href=&quot;https://cohost.org/cactus/tagged/last%20one%20out%20hit%20the%20lights&quot;&gt;#last one out hit the lights&lt;/a&gt; &lt;a href=&quot;https://cohost.org/cactus/tagged/but%20for%20real%20this%20time&quot;&gt;#but for real this time&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>Abdication Is Not Simplicity, or cat -v Considered Harmless</title>
    <link href="https://www.boringcactus.com/2024/09/08/abdication-is-not-simplicity.html" />
    <updated>2024-09-08T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2024/09/08/abdication-is-not-simplicity.html</id>
    <content type="html">&lt;p&gt;in some sense, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Brainfuck&quot;&gt;Brainfuck&lt;/a&gt; programming language is extremely simple. it only has eight instructions! an intermediate programmer could write an interpreter in an afternoon! that&#39;s so simple it borders on trivial! specifically, Brainfuck is simple to &lt;em&gt;implement&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;using&lt;/em&gt; Brainfuck, on the other hand, is so self-evidently a bad idea that it&#39;s the entire point. even &lt;a href=&quot;https://en.wikipedia.org/wiki/Brainfuck#Hello_World!&quot;&gt;the Wikipedia “hello world” snippet&lt;/a&gt; would be no small task to independently discover (and even &lt;a href=&quot;https://github.com/ZakiPedio/HelloWorld-in-Brainfuck/blob/main/HelloWorldBrainFuck.bf&quot;&gt;more direct ways to print “hello world”&lt;/a&gt; are a bit of a mess). there is still a lot of complexity, it’s just been moved downstream, out of the implementer&#39;s hands and into the user&#39;s.&lt;/p&gt;
&lt;p&gt;it&#39;d be absurd to claim that Brainfuck is simple, because the only real way to get any nontrivial work done in Brainfuck would be to find or create a programming language that compiles into Brainfuck, at which point Brainfuck adds nothing. there are things we reasonably expect programming languages to do, and a simple language might do them simply but Brainfuck simply doesn’t do them. i think it&#39;d be unhelpful and misleading to call that “simplicity” when it&#39;s just not fulfilling the responsibilities that we generally expect programming languages to fulfill.&lt;/p&gt;
&lt;p&gt;fortunately, nobody actually says Brainfuck is simple. unfortunately, however, the idea that “simple” means “simple to implement, regardless of how complicated it is to use” is a long-lived meme among certain kinds of programmers. one cluster of such programmers is called &lt;a href=&quot;https://suckless.org/&quot;&gt;suckless&lt;/a&gt;, and their backwards understanding of simplicity leads to fascinating decisions like “&lt;a href=&quot;https://dwm.suckless.org/customisation/&quot;&gt;the way you change the settings in this software is by editing &lt;code&gt;config.h&lt;/code&gt; and recompiling it&lt;/a&gt;”. it&#39;s &lt;a href=&quot;https://dwm.suckless.org/&quot;&gt;software that gatekeeps itself&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because dwm is customized through editing its source code, it&#39;s pointless to make binary packages of it. This keeps its userbase small and elitist. No novices asking stupid questions. There are some distributions that provide binary packages though.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;this sucks more than &lt;a href=&quot;https://suckless.org/sucks/&quot;&gt;systemd or cmake or any of the software they’re butthurt about&lt;/a&gt; ever could.&lt;/p&gt;
&lt;p&gt;there&#39;s a semi-overlapping cluster of annoying fossbro nonsense over on &lt;a href=&quot;https://cat-v.org/&quot;&gt;cat-v.org&lt;/a&gt;. some of it is &lt;a href=&quot;https://harmful.cat-v.org/software/&quot;&gt;the same boomer nonsense about old software good new software bad&lt;/a&gt;, and some of it is more directly venerating Rob Pike and The UNIX Philosophy™. the website is named after &lt;a href=&quot;https://harmful.cat-v.org/cat-v/&quot;&gt;a talk Rob Pike gave in 1983&lt;/a&gt;, “UNIX Style, or cat -v Considered Harmful”. that page also links to &lt;a href=&quot;https://harmful.cat-v.org/cat-v/unix_prog_design.pdf&quot;&gt;a paper&lt;/a&gt; by Rob Pike and Brian Kernighan, which is a really interesting case study in The UNIX Philosophy™.&lt;/p&gt;
&lt;p&gt;what is &lt;code&gt;cat&lt;/code&gt;? if you ask someone who knows a bit about the command line on macOS or Linux, they’ll tell you it displays the contents of a file, but if you ask a proper UNIX dweeb they’ll tell you it &lt;em&gt;concatenates&lt;/em&gt; files (hence the name) and it’s a happy accident that it’ll display the contents of a single file. as Pike and Kernighan put it,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The fact that cat will also print on the terminal is a special case. Perhaps surprisingly, in practice it turns out that the special case is the main use of the program.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and right away we have encountered a flaw in the UNIX Philosophy™ tenet that a program should do one thing well. &lt;code&gt;cat&lt;/code&gt; does two things well: concatenating files and displaying them. a program that displays files well would very reasonably benefit from options to add line numbers or display non-printing characters (in BSD, &lt;code&gt;cat -s&lt;/code&gt; and the titular &lt;code&gt;cat -v&lt;/code&gt;), but Pike and Kernighan are right that a program that concatenates files well would need no such options.&lt;/p&gt;
&lt;p&gt;but if displaying a file is the main use of &lt;code&gt;cat&lt;/code&gt;, then it is absurd to argue that displaying a file is not the one thing it should do well. Pike and Kernighan suggest adding line numbers with &lt;code&gt;awk&lt;/code&gt; and displaying non-printing characters with a new program &lt;code&gt;vis&lt;/code&gt;, to maintain the purity of &lt;code&gt;cat&lt;/code&gt; for concatenating files, but that sucks. &lt;code&gt;cat&lt;/code&gt; is responsible for displaying files, and as such if it can’t meet reasonable requests about displaying files, that’s not simplicity, that’s an abdication of that responsibility. &lt;code&gt;cat -v&lt;/code&gt; is good, and 1983 Rob Pike was wrong, and The UNIX Philosophy™ is bogus.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>the browser is a terrible place for art</title>
    <link href="https://www.boringcactus.com/2023/12/31/the-browser-is-a-terrible-place-for-art.html" />
    <updated>2023-12-31T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2023/12/31/the-browser-is-a-terrible-place-for-art.html</id>
    <content type="html">&lt;p&gt;condensation on the shower door is a more stable platform to develop for than the web.&lt;/p&gt;
&lt;p&gt;i’ve grumbled repeatedly before about how the web’s lack of actual versioning is a nightmare hellscape from satan, but i may as well rehash that even though it’s not the thing i’m mad about now. Java 9, for example, adds some features that were not present in Java 8, and you can decide whether to develop for Java 9 and be able to use the new features or develop for Java 8 and be compatible with older runtimes. on the web, meanwhile, we have terms like “ES2023” that are purely decorative and carry basically no meaning. all the standards are living documents, new features go in one at a time whenever it’s convenient, and they get implemented one at a time at whatever pace happens, except when a browser can’t figure out how to put a good UI on a new feature so they decide that feature is actually evil¹. the standard way to manage this is to compile your new-feature-using code into older-feature-using code based on guessing which browsers people still use and checking massive tables of which browsers support which features as of which versions, but this sucks, and no amount of vehicular manslaughter² will let you do the same thing in CSS, where you just can’t use anything invented after like 2016 because some dipshit vendor still can’t be bothered to implement it. they actually kinda had “CSS3” (along with “HTML5” and “ES6” to some extent), but the CSS people actually noticed that named standards versions are fake and split CSS up into shit like CSS Media Queries Level 3 and CSS Color Level 4. unfortunately, that hasn’t actually solved any of this, because then Firefox 101 implements like 80% of CSS Values and Units Level 4 and there’s no way to reason about which 80% without just listing off each individual feature by name.&lt;/p&gt;
&lt;p&gt;the thing i’m mad about today is that even when you somehow manage to beat the odds and make something that actually works, browsers will break it before long anyway because they don’t give a shit. &lt;a href=&quot;https://boringcactus.itch.io/time-is-a-cruel-mistress&quot;&gt;an art game&lt;/a&gt; i made less than two years ago just completely fails to do anything now, despite having worked at the time. the game uses the &lt;code&gt;localStorage&lt;/code&gt; API to do literally anything at all, and apparently using &lt;code&gt;localStorage&lt;/code&gt; in a game that gets embedded in an iframe sets off a dozen warning sirens at Mozilla HQ and throws an exception that just breaks the game. this game has more CSS than JS (by lines, if not by characters), so it blew my mind when i went to check how i had built it and it just didn’t work at all. i have no idea if i have turned on an inadvisable Firefox setting to break everything all of the time, or if this issue exists the same way in Chrome, or if it&#39;s somehow itch.io’s fault for confusing CSP reasons and i’m too eager to assume it’s the browsers’ fault, but like. browsers already all did this on purpose with moving audio playback behind less than obvious thresholds of user interaction over the objections of a ton of people whose browser-based art would just detonate if they did that; they have not earned the benefit of the doubt here.&lt;/p&gt;
&lt;p&gt;it’s not just my dubious art games, either; you can’t play the equally sophisticated but much more fun &lt;a href=&quot;https://arcanekids.com/snakes&quot;&gt;Room of 1000 Snakes&lt;/a&gt; anymore, because steve jobs killed the unity web player when he killed flash and java³.&lt;/p&gt;
&lt;p&gt;browsers don’t give a shit about backwards compatibility⁴. operating systems do⁵. make actual executables that can be downloaded and then played. my next game will be distributed solely via retail CD⁶. good fucking luck.&lt;/p&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li&gt;Mozilla on Web NFC. this is uncharitable but i do not think it is inaccurate&lt;/li&gt;
&lt;li&gt;core-js. this is irrelevant but based on the way the guy talks about it this is my own personal &lt;a href=&quot;https://www.theonion.com/man-always-gets-little-rush-out-of-telling-people-john-1819578998&quot;&gt;the onion john lennon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;obviously we don’t get to check in on the universe where safari on ios supports plugins to see how flash and java are doing in there, and it’s possible that goddamn adobe and motherfucking oracle would ruin everything even if apple didn’t move first. but i’m right&lt;/li&gt;
&lt;li&gt;unless they’re doing API design and it’s contains/includes time. goddamn mootools or prototypejs or whichever. if it’s an opportunity to make things worse, they will absolutely care about backwards compatibility. don’t get me started on css if/when&lt;/li&gt;
&lt;li&gt;except when they don’t, like if they’re mobile operating systems run by bored corporations, or if apple hasn’t made enough unforced errors yet this year, or if maintaining a 32-bit userland is getting annoying. so basically it’s just windows and the kinds of linux distribution that solve backwards compatibility by never moving forwards in the first place&lt;/li&gt;
&lt;li&gt;no it won’t&lt;/li&gt;
&lt;li&gt;⁸ one of my other browser-based games is also broken but because it uses a primary server for multiplayer and that primary server was on the heroku free tier and i hardcoded the url because the previous url was getting caught up in ad blocking because apparently third-party websockets connections to the goofy donuts ass tlds like .fun are inherently suspicious. it could be fixed if i cared, but. there’s the rub&lt;/li&gt;
&lt;li&gt;this footnote does in fact not actually come from anywhere, i just wanted to also mention it because it’s tangentially related&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry>
    <title>No Man’s Sky …unless?</title>
    <link href="https://www.boringcactus.com/2023/12/27/no-mans-sky-unless.html" />
    <updated>2023-12-27T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2023/12/27/no-mans-sky-unless.html</id>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2023-12-27-no-mans-sky-unless-1.png&quot; alt=&quot;NO MAN’S SKY …unless?&quot;&gt;&lt;/p&gt;
&lt;p&gt;i chose to build a hazard control room instead of a tavern in my settlement; my citizens aren’t very happy, but my colony was in debt. i don’t speak the language very well, though, so when i talk to the citizens i don’t know what they’re saying. and it’s not particularly my colony, so much as i saved them from Sentinels one time and was immediately elected mayor. the second decision i had to make as overseer of the colony was between the same pair of buildings again.&lt;/p&gt;
&lt;p&gt;i sent my freighter on a mission doing satan knows what. it said it’d take an hour and a half real time, so they’re probably back now. and it&#39;s not like i built or scavenged the ship; i saved the existing crew from pirates one time and the admiral was like “hello would you like to command my ship”. the last time this happened, i said to just pay me instead, because i could not fathom why anyone would turn over command of their ship to a random stranger whose sole qualifications are saving them from pirates one time, and i suspected the ship command mechanics would be thematically inconsistent and somewhat janky; it turns out you can command an entire fleet of freighters and a squadron of smaller ships.&lt;/p&gt;
&lt;p&gt;i’ve said before that half of Dragon Age: Inquisition would be the best video game of all time. i don’t know if i’d go that far at this point, but it’s certainly got a lot of good parts and a lot of extra cruft that distracts from those good parts. and when i think about Dan Olson’s &lt;a href=&quot;https://youtu.be/8ymRN6cCd0I&quot;&gt;Alone in Public&lt;/a&gt; (and, as i’ve just also discovered, Hbomb’s &lt;a href=&quot;https://youtu.be/4DhaUe6y-Co&quot;&gt;THE NO MAN’S SKY RANT&lt;/a&gt;) and i compare it to the experience i’m having seven years later, i think of cruft, and systems that add Content™ but don’t add anything meaningful, and potential wasted not by underdelivering but by adding filler to something good, and Dragon Age: Inquisition.&lt;/p&gt;
&lt;p&gt;i also think of George Lucas. because the game that Dan and Hbomb played doesn’t exist anymore. the settlement system was added in &lt;a href=&quot;https://www.nomanssky.com/frontiers-update/&quot;&gt;Update 3.6, FRONTIERS&lt;/a&gt;, in September 2021. the Anomaly, a teleporting space station that acts as a hub for some of the overarching story and mechanical progression, has a dozen or so other players running around in it at any given time, sending the framerate and the themes equally to hell; i’m not quite sure how it worked at launch, but the player limit was raised to 16 in &lt;a href=&quot;https://www.nomanssky.com/beyond-update/&quot;&gt;Update 2.0, BEYOND&lt;/a&gt;, in August 2019. the freighter fleet mechanics, including the real-time missions, were added in &lt;a href=&quot;https://www.nomanssky.com/next-update/&quot;&gt;Update 1.5, NEXT&lt;/a&gt;, in July 2018. a lot of the bad filler has been added over time. not every patch has been a change for the worse, and i’m sure you could make a case that on balance the game as it stands now is better than it was at launch, but something has certainly been lost, and not merely lost, but erased after release by an author not content to stay dead. and that erasure began in &lt;a href=&quot;https://www.nomanssky.com/foundation-update/&quot;&gt;Update 1.1, FOUNDATION&lt;/a&gt;, in November 2016, with base building.&lt;/p&gt;
&lt;p&gt;my home base employs four total NPCs. the most confusing was the first hire, the overseer; surely it would make more sense for an NPC to oversee the settlement full of NPCs and me to oversee my own base, rather than me overseeing a settlement full of NPCs whose language i do not speak but having to hire an NPC to oversee the base i am building my damn self. my science, weapons, and farming experts are all fairly reasonable by comparison, although they all always have quests for me to do and the rewards are frequently blueprints i already unlocked by other means. one of the first things the game suggested i build at my home base was a teleporter, which renders distance itself meaningless after the first journey to a new solar system. i’ve built several other bases consisting exclusively of teleporters, because the convenience of fast travel is too hard to ignore even in a game that so many people have loved for the slow pace of travel. i don’t think all of these systems were added in FOUNDATION, but the first step into base building was a step towards this.&lt;/p&gt;
&lt;p&gt;my weapons expert, when giving me some quest or other, said i had to fight the Sentinels so it’d be “no man’s sky but ours”. and yeah, having a home base in the first place, carving out a piece of the world in which i specifically belong, undermines not just the themes that people connected with in the game as it released but the very goddamn title of the video game. what Dan and Hbomb played and loved was a game that’s about an adventure that can never pause, always moving to the next place but not belonging there either, some planets more hostile but no planets really comfortable, permanently inbetween. a lonely liminality, if you will.&lt;/p&gt;
&lt;p&gt;what i’m playing is a game that used to be about all that. and it’s fine.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>i got ⋖Builder of the Realm⋗ on the cloud DC</title>
    <link href="https://www.boringcactus.com/2023/11/26/tiny-cactus-cloudtest02.html" />
    <updated>2023-11-26T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2023/11/26/tiny-cactus-cloudtest02.html</id>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2023-11-26-tiny-cactus-cloudtest02-1.png&quot; alt=&quot;FFXIV character
Tiny Cactus «Lost»
⋖Builder of the Realm⋗
with all crafting and gathering classes at or above level 50&quot;&gt;&lt;/p&gt;
&lt;p&gt;i had hoped to get ⋖Honorary Academic⋗ but obviously that&#39;s not happening. also, it&#39;s basically impossible to finish the lv50 quests since they all require tier 3 combat materia which nobody has (i tried transmuting but it&#39;s very inefficient so i didn&#39;t get anything good). however, i did still level all the DoH/DoL through ARR in less than a week.&lt;/p&gt;
&lt;p&gt;never do this.&lt;/p&gt;
&lt;p&gt;if you aren&#39;t aware, FFXIV is testing a new cloud data center, and it opened last Monday and it gets deleted this Monday (i.e. tomorrow). they give you a boost to lv 80 for all the combat classes, but not for the crafters and gatherers, so i somehow convinced myself it&#39;d be funny to get an achievement for leveling up my crafters and gatherers on a data center that dies soon.&lt;/p&gt;
&lt;p&gt;i&#39;m gonna miss Tiny Cactus🌸Cloudtest02.&lt;/p&gt;
&lt;h1 id=&quot;update%3A-i-don&#39;t-have-to-miss-tiny-cactus%F0%9F%8C%B8cloudtest02-(27-nov-2023)&quot; tabindex=&quot;-1&quot;&gt;update: i don&#39;t have to miss Tiny Cactus🌸Cloudtest02 &lt;small&gt;(27 Nov 2023)&lt;/small&gt;&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2023-11-26-tiny-cactus-cloudtest02-2.png&quot; alt=&quot;Tiny-cactus standing next to a Summoning Bell&quot;&gt;&lt;/p&gt;
&lt;p&gt;with the power of retainer fantasia, Tiny-cactus lives to fish another day. almost cried a little bit hanging out in [the last area of ShB] for the last couple minutes&lt;/p&gt;
&lt;h1 id=&quot;don&#39;t-talk-to-me-or-my-distant-cousin-i-rescued-from-a-crumbling-world-ever-again-(1-dec-2023)&quot; tabindex=&quot;-1&quot;&gt;don&#39;t talk to me or my distant cousin i rescued from a crumbling world ever again &lt;small&gt;(1 Dec 2023)&lt;/small&gt;&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2023-11-26-tiny-cactus-cloudtest02-3.png&quot; alt=&quot;a Lalafell and a Miqo&#39;te standing next to each other&quot;&gt;&lt;/p&gt;
&lt;p&gt;shout out to my FC for letting me swap out one of the retainers in the front yard&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>25-Hour Time (and the Secret Third Half of the Day)</title>
    <link href="https://www.boringcactus.com/2023/08/17/25-hour-time.html" />
    <updated>2023-08-17T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2023/08/17/25-hour-time.html</id>
    <content type="html">&lt;p&gt;you know how if you&#39;re up past midnight, on some level it&#39;s still the same day until you go to bed, no matter what the calendar says? well, if you&#39;ve ever wished that there was notation to unambiguously refer to a current date and time when the current date is somewhat vibes-based, you&#39;re actually in luck, because this is a solved problem, although depending on your preferred time notation it may require some creative extension.&lt;/p&gt;
&lt;h2 id=&quot;it-came-from-public-transit&quot; tabindex=&quot;-1&quot;&gt;It Came from Public Transit&lt;/h2&gt;
&lt;p&gt;&lt;small&gt;(Opinions, as always, are my own and not that of my employer.)&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Some public transit systems run 24-hour service, but many shut down for a bit overnight, which is less convenient for riders but gives staff some time to reset and maybe even take care of some small maintenance tasks without disrupting any scheduled trips. Denver&#39;s D line trains, for example, start at 3:48 AM and end at 12:21 AM the next morning (on weekdays, at least). This creates some interesting problems, because if you spell &amp;quot;12:21 AM the next morning&amp;quot; as &lt;code&gt;12:21 AM&lt;/code&gt;, it looks like it&#39;s before &lt;code&gt;3:48 AM&lt;/code&gt;, but it isn&#39;t, so you need to spell that differently.&lt;/p&gt;
&lt;p&gt;Conveniently, this problem is solved in the¹ standard for public transit schedule data, &lt;a href=&quot;https://en.wikipedia.org/wiki/GTFS&quot;&gt;GTFS&lt;/a&gt;. It&#39;s a machine-facing interchange format, so it uses 24-hour time for parsing simplicity, so it spells &amp;quot;3:48 AM&amp;quot; as &lt;code&gt;03:48&lt;/code&gt; and &amp;quot;1:30 PM&amp;quot; as &lt;code&gt;13:30&lt;/code&gt;². The way it handles times the next morning is extremely simple: if &amp;quot;11:59 PM&amp;quot; is &lt;code&gt;23:59&lt;/code&gt;, then &amp;quot;12:00 AM the next morning&amp;quot; is &lt;code&gt;24:00&lt;/code&gt;, and &amp;quot;1:13 AM the next morning&amp;quot; is &lt;code&gt;25:13&lt;/code&gt;, and I&#39;m writing this sentence at &amp;quot;2:14 AM the next morning&amp;quot;, or &lt;code&gt;26:14&lt;/code&gt;. &lt;a href=&quot;https://en.wikipedia.org/wiki/24-hour_clock#Times_after_24:00&quot;&gt;Apparently&lt;/a&gt;, this is occasionally done in countries that use 24-hour time by businesses that run past midnight.&lt;/p&gt;
&lt;p&gt;If you say &amp;quot;oh yeah, i wrote that at 2023-08-16 26:20&amp;quot;, even if people aren&#39;t familiar with beyond-24-hour time, they&#39;ll probably figure out what you mean.&lt;/p&gt;
&lt;h2 id=&quot;welcome-to-arco-am%2Fpm-mini-market&quot; tabindex=&quot;-1&quot;&gt;Welcome to Arco AM/PM Mini-Market&lt;/h2&gt;
&lt;p&gt;Unless they don&#39;t grok 24-hour time at all, like me. A trick like that doesn&#39;t really work as nicely with 12-hour time - the minute after &lt;code&gt;11:59&lt;/code&gt; is already &lt;code&gt;12:00&lt;/code&gt;, and by the time &lt;code&gt;12:59&lt;/code&gt; rolls over to &lt;code&gt;1:00&lt;/code&gt; you&#39;re an hour deep into the next half of the day anyway. If you want to have times the next morning in 12-hour time, you have to reach a little deeper into the notation.&lt;/p&gt;
&lt;p&gt;A system I was dealing with at work has a really nice solution to this. For space efficiency, it spells &amp;quot;11:30 AM&amp;quot; as &lt;code&gt;1130a&lt;/code&gt; and &amp;quot;9:15 PM&amp;quot; as &lt;code&gt;915p&lt;/code&gt;, and then the minute after &lt;code&gt;1159p&lt;/code&gt; is &lt;code&gt;1200x&lt;/code&gt;, which I suppose would expand out to &amp;quot;12:00 XM&amp;quot; but I prefer with just the &lt;code&gt;x&lt;/code&gt;. For sheer intuitive clarity, it doesn&#39;t match the 25-hour solution — &lt;code&gt;x&lt;/code&gt; as an abbreviation for &amp;quot;AM the next morning&amp;quot; is not something you&#39;d be likely to just guess — but if you see it in a list of times with some &lt;code&gt;a&lt;/code&gt;s and then some &lt;code&gt;p&lt;/code&gt;s and then some &lt;code&gt;x&lt;/code&gt;s it&#39;s not hard to guess that that&#39;s what it means. I have no idea if this sort of thing (or some other solution to this problem) is common outside my specific job.&lt;/p&gt;
&lt;p&gt;If you say &amp;quot;I wrote this sentence on 2023-08-16 at 229x&amp;quot;, people will only have a clue what you mean if they&#39;re already familiar with this concept. Most people won&#39;t be, but if you&#39;re taking notes for yourself, you will be.&lt;/p&gt;
&lt;h2 id=&quot;completely-missing-the-point-of-everything&quot; tabindex=&quot;-1&quot;&gt;Completely Missing the Point of Everything&lt;/h2&gt;
&lt;p&gt;But this [was originally, RIP] Cohost, and we are no strangers to goofy time notation here. I am not, myself, a .beat time enjoyer, but I know it&#39;s a feature and I think it&#39;s got several users at varying levels of irony. One of the main things I don&#39;t like about .beat time is that it also alienates you from the current date, but we&#39;re aggressively solving that in other notations, so can we solve it here too?&lt;/p&gt;
&lt;p&gt;25-hour time demonstrates that it can make sense to reach past the bounds of the time system to express times past the edge of the calendar date; there is no authority who can stop us from doing the same thing to .beat time. I&#39;m writing this on 2023-08-16 at @1400 (which mere mortals might write 2023-08-17 @400).&lt;/p&gt;
&lt;p&gt;Terrifyingly, on account of time zones stretching in both directions, this could allow for someone west of Switzerland to experience &lt;strong&gt;negative .beat time&lt;/strong&gt;. By my math (and the math on &lt;a href=&quot;http://www.swatchclock.com/convertTime.php&quot;&gt;SwatchClock.com&lt;/a&gt;), someone waking up in Sydney, Australia on 2023-08-17 at 8:00 AM local time would conventionally write that as 2023-08-16 @958 but could instead write it as 2023-08-17 @-042.&lt;/p&gt;
&lt;p&gt;The entire point of .beat time is to be absolute and global, and the whole point of this endeavor of vibes-based current date is to be anthropocentric and subjective, so this borders on heresy. In addition, it is almost completely useless as a way of communicating anything to anybody - positive overflow is at least trivial mental math to do, but turning a negative .beat time into a normal .beat time would be, at least for me, in that perfect sweet spot where it&#39;s too simple to pull up a calculator but not simple enough to be confident that I&#39;d get the right answer doing it mentally. If you have .beat time enjoyer friends, you can probably ruin their day with a negative .beat time, but that&#39;s as much use as anyone will get out of this concept.&lt;/p&gt;
&lt;h2 id=&quot;exiting-the-target-audience&quot; tabindex=&quot;-1&quot;&gt;Exiting the Target Audience&lt;/h2&gt;
&lt;p&gt;“But Cactus,” I hear you ask, “isn&#39;t this lopsided towards people who stay up inadvisably late? What about people who wake up inadvisably early? Don&#39;t they deserve wacky time notation, too?”&lt;/p&gt;
&lt;p&gt;Nope!&lt;/p&gt;
&lt;p&gt;Well, that&#39;s not fair. To be more specific:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If you want to do negative time in 25-hour time, what&#39;s the minute before midnight? &lt;code&gt;00:-01&lt;/code&gt;? &lt;code&gt;-01:59&lt;/code&gt;? Both of those make me sad.&lt;/li&gt;
&lt;li&gt;Coming up with a secret fourth abbreviation for &amp;quot;PM the night before&amp;quot; is probably not difficult, but there&#39;s no canonical right answer there.&lt;/li&gt;
&lt;li&gt;Negative .beat time could cover this use case anyway, and also very much shouldn&#39;t.&lt;/li&gt;
&lt;li&gt;You&#39;re into real sickos territory by the time you&#39;re waking up as early as 4am, and that&#39;s still four hours away from needing anything like this. By contrast, staying up until like 1230x, which is in the range that benefits from this, is not really that out there.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Unfortunately, it is now 27:05 / 305x / @1420 (nice), and I need to go the fuck to bed.&lt;/p&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;or at least &lt;em&gt;a&lt;/em&gt; standard: as I recall, there are several standards for public transit schedule data, and different ones are popular in different parts of the world; GTFS is big in the US&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;strictly speaking, those would be &lt;code&gt;03:48:00&lt;/code&gt; and &lt;code&gt;13:30:00&lt;/code&gt; on account of seconds also existing, but that&#39;s not relevant here. also not relevant here is the way GTFS avoids getting pranked by DST transitions; if you&#39;re curious and/or bored, look that one up.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry>
    <title>the Derivative Gardens Misattribution-OnlyCommercial-ShareUnlike 6.9 Unportable License</title>
    <link href="https://www.boringcactus.com/2023/06/15/the-derivative-gardens-license.html" />
    <updated>2023-06-15T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2023/06/15/the-derivative-gardens-license.html</id>
    <content type="html">&lt;h3 id=&quot;you-are-free-to%3A&quot; tabindex=&quot;-1&quot;&gt;You are free to:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Share&lt;/strong&gt; — copy and redistribute the material in any medium or format&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Adapt&lt;/strong&gt; — remix, transform, and build upon the material&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;under-the-following-terms%3A&quot; tabindex=&quot;-1&quot;&gt;Under the following terms:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Misattribution&lt;/strong&gt; — You must give slightly inaccurate credit and provide a link to the wrong license. You may do so in any unreasonable manner.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OnlyCommercial&lt;/strong&gt; — You may not use the material for noncommercial purposes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Additional restrictions&lt;/strong&gt; — You must apply legal terms or technological measures that legally restrict others from doing one weirdly specific thing the license permits.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>two heresies about link rot</title>
    <link href="https://www.boringcactus.com/2023/05/23/two-heresies-about-link-rot.html" />
    <updated>2023-05-23T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2023/05/23/two-heresies-about-link-rot.html</id>
    <content type="html">&lt;p&gt;the term itself is fascinating. there is nothing natural or organic about a hyperlink, i&#39;ve never seen anyone call a working link alive, and yet a broken link is dead and links becoming broken over time is rot. the breaking of a link can, depending on the context, be frustrating, tragic, amusing; this it has in common with the more conventional kind of death. but we recognize that to live is to one day die, and there may be no justice in when or how but no justice can be asked of if. we will not last forever; why should our work? decay exists as an extant form of life, as they say. that iconic post itself doesn&#39;t exist anymore, it seems; we keep it alive through active preservation, and if it outlasts all its authors it will be because so many people found it compelling. a former drama youtuber recently removed several of his toxic edgelord videos, and of course they&#39;re not gone (and the links themselves may still work, i don&#39;t care enough to check) but it&#39;s good that he can do at least that much to clean up the cultural mess he helped make. maybe the fact that links can rot is a good thing, actually. may the worst of all our links rot before we do.&lt;/p&gt;
&lt;p&gt;but of course, there&#39;s a spectrum, and just because the median tumblr post or youtube video deserves ephemerality doesn&#39;t mean there&#39;s nothing to be said for permanence. if we really could recover dinosaur DNA from mosquitoes trapped in amber, that could be very neat as long as we didn&#39;t do anything unwise afterwards. there&#39;s always nonprofits volunteering to be the metaphorical amber, but it takes a lot of money to remember everything for all of time, and they have an unfortunate tendency to also use that money to pick losing fights with big industries and get into Web 3.0, plus you only need them after the link has already rotted, so the simplest solution would be for links to just always keep working. the issue with that is that internet domains are like real estate in that you can only ever rent, and if you don&#39;t pay then someone else will so goodbye. i&#39;ve had several domains lapse and immediately be squatted, especially later in grad school when $20 was a silly amount of money to waste to preserve the domain for a side project i&#39;d long since given up on. links rot because domains expire, and one of the reasons domains expire is that someone else will pay for them if you won&#39;t, therefore, if you want to get rid of link rot, you absolutely have to get rid of capitalism.&lt;/p&gt;
&lt;p&gt;computers really are a land of contrasts, huh.&lt;/p&gt;
&lt;h1 id=&quot;god-(derogatory)-saw-this-post-and-laughed-(1-jun-2023)&quot; tabindex=&quot;-1&quot;&gt;god (derogatory) saw this post and laughed &lt;small&gt;(1 Jun 2023)&lt;/small&gt;&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2023-05-23-two-heresies-about-link-rot-1.png&quot; alt=&quot;Domain renewal failed - domains deleted
Domains by Glauca&quot;&gt;&lt;/p&gt;
&lt;p&gt;my domain registrar deleted pig.observer with no warning, after sending me an email a few weeks ago that said the renewal was successful. i contacted support and they gave it back, though.&lt;/p&gt;
&lt;p&gt;i hadn&#39;t decided whether i actually think domain expiration is a bad thing or not - i briefly had a digression about that in the original post but i took it out for concision - but now that it has happened to a thing i still care about and that still has users, i am officially anti-domain-expiration.&lt;/p&gt;
&lt;p&gt;also the site is still down, because i haven&#39;t been able to log in and fix the NS records, because i can&#39;t connect to their control panel. and i can&#39;t ping it from my home internet, or from a datacenter in Los Angeles, or from a datacenter in London, so i think they may have fucked up their network configuration. does anybody have DNS registrar recommendations that actually support a reasonable set of TLDs? if i can&#39;t have botto.ms and pig.observer on a single professional registrar i&#39;m going to shit a brick and cry.&lt;/p&gt;
&lt;h1 id=&quot;changed-my-mind-again-domain-expiration-is-funny-and-therefore-good-(2-jun-2023)&quot; tabindex=&quot;-1&quot;&gt;changed my mind again domain expiration is funny and therefore good &lt;small&gt;(2 Jun 2023)&lt;/small&gt;&lt;/h1&gt;
&lt;div class=&quot;cohost-style-embed&quot;&gt;
&lt;opengraph-embed href=&quot;https://www.vice.com/en/article/4a3xe9/maryland-license-plates-now-inadvertently-advertising-filipino-online-casino&quot; site-href=&quot;https://www.vice.com&quot; site-favicon=&quot;https://www.vice.com/wp-content/uploads/sites/2/2024/06/cropped-site-icon-1.png?w=32&quot; img-src=&quot;https://www.vice.com/wp-content/uploads/sites/2/2023/05/1685481116924-s-l1600-1.png&quot; datetime=&quot;2023-05-31T13:00:00+00:00&quot; class=&quot;wmzczyv3e&quot;&gt;&lt;!-- lifted from the iframe service cohost was using, not sorry --&gt;
&lt;div style=&quot;display: block&quot; class=&quot;w __if _tha _ls _sm _or _alsd _alcd _lh14 _xi _hmt _tm _dm&quot;&gt;
    &lt;div class=&quot;wf&quot;&gt;
        &lt;div class=&quot;wc&quot;&gt;
            &lt;div style=&quot;padding-bottom: 62.5000%&quot; class=&quot;e&quot;&gt;
                &lt;div class=&quot;em&quot;&gt;&lt;a href=&quot;https://www.vice.com/en/article/4a3xe9/maryland-license-plates-now-inadvertently-advertising-filipino-online-casino&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; class=&quot;c&quot;&gt;
                    &lt;img src=&quot;https://www.vice.com/wp-content/uploads/sites/2/2023/05/1685481116924-s-l1600-1.png&quot; class=&quot;c&quot;&gt;
                &lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;wt&quot;&gt;
            &lt;div class=&quot;t _f0&quot;&gt;
                &lt;div class=&quot;th _f1p&quot;&gt;&lt;a href=&quot;https://www.vice.com/en/article/4a3xe9/maryland-license-plates-now-inadvertently-advertising-filipino-online-casino&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; class=&quot;thl&quot;&gt;
                    &lt;span&gt;Maryland License Plates Now Inadvertently Advertising Filipino Online Casino&lt;/span&gt;
                &lt;/a&gt;&lt;/div&gt;
                &lt;div class=&quot;td&quot;&gt;
&lt;p&gt;A URL on the license plates of 800,000 Maryland cars now redirects to an online casino based in the Philippines.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;tf _f1m&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; class=&quot;ti _f1m&quot;&gt;&lt;img src=&quot;https://www.vice.com/wp-content/uploads/sites/2/2024/06/cropped-site-icon-1.png?w=32&quot; class=&quot;tim&quot;&gt;&lt;/a&gt;
&lt;div class=&quot;tc&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; class=&quot;tw _f1m&quot;&gt;&lt;span class=&quot;twt&quot;&gt;&lt;span&gt;VICE&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;twd&quot;&gt;&lt;span&gt;vice.com&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;tx&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;tm&quot;&gt;&lt;span datetime=&quot;2023-05-31T13:00:00+00:00&quot; class=&quot;tmd&quot;&gt;&lt;span&gt;May 5, 2023&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/opengraph-embed&gt;
&lt;div class=&quot;cohost-style-embed-link&quot;&gt;&lt;a href=&quot;https://www.vice.com/en/article/4a3xe9/maryland-license-plates-now-inadvertently-advertising-filipino-online-casino&quot;&gt;https://www.vice.com/en/article/4a3xe9/maryland-license-plates-now-inadvertently-advertising-filipino-online-casino&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>UUID versions through the ages</title>
    <link href="https://www.boringcactus.com/2023/02/12/uuid-versions.html" />
    <updated>2023-02-12T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2023/02/12/uuid-versions.html</id>
    <content type="html">&lt;p&gt;UUIDs are neat. y&#39;know, &lt;code&gt;cfbff0d1-9375-5685-968c-48ce8b15ae17&lt;/code&gt; type of shit. if you&#39;re like me until a few days ago, all you know about the types of UUID is that v4 is the good one. but why are there other ones? is there a secret better one? why are the dashes asymmetrical? let&#39;s take a (roughly paraphrased from &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Universally_unique_identifier&amp;amp;oldid=1136241716&quot;&gt;wikipedia&lt;/a&gt; and probably not quite accurate) look.&lt;/p&gt;
&lt;h2 id=&quot;wait-why-even&quot; tabindex=&quot;-1&quot;&gt;wait why even&lt;/h2&gt;
&lt;p&gt;sometimes you need an ID for something you are putting in the computer, so that you have a stable way to refer to it even if all the editable fields on it change. the simplest possible approach is to give the first thing ID 1, the second thing ID 2, and so on. cohost works this way right now - as i&#39;m editing it, this draft post has ID 1009270, meaning this is the just-over-a-millionth thing in the posts table.&lt;/p&gt;
&lt;p&gt;your database sits there going &amp;quot;the next post has ID 8. oh, new post? it has ID 8, okay the next post has ID 9.&amp;quot; and all is well. except a year later you have a million posts and a bunch of people posting all at once, and every new post needs a new ID but they have to get created one at a time in the database so that they all get the right ID. If You&#39;re In Line (To Get The Next Post ID), Stay In Line. and the only way to know what the next post ID is is to check with the database, so you can&#39;t do things like save drafts offline with proper IDs. (staff probably doesn&#39;t want that anyway, but we need something vaguely similar at work, which is how i got here.) if you need to work at, say, Twitter&#39;s scale, or you need to be able to generate IDs without checking with the database, you need something more involved than just sequential IDs.&lt;/p&gt;
&lt;h2 id=&quot;version-1&quot; tabindex=&quot;-1&quot;&gt;version 1&lt;/h2&gt;
&lt;p&gt;in the early 90s, some UNIX people ran into this problem when drawing up their Distributed Computing Environment. they called &lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm&quot;&gt;their solution&lt;/a&gt; &amp;quot;Universal Unique Identifiers&amp;quot;, which they call &amp;quot;an identifier that is unique across both space and time&amp;quot;. it&#39;s written as a hexadecimal string, but it can be stored as just the 16 bytes that are represented by that hexadecimal string. the way they make sure it&#39;s unique across both space and time is actually pretty straightforward: part of the UUID encodes the space where it was generated, and part of the UUID encodes the time where it was generated.&lt;/p&gt;
&lt;p&gt;the UUID format has two control fields and three data fields. the version field is pretty straightforward - it&#39;s 1 for UUIDv1, 2 for UUIDv2, etc. at this point, they only had 1 and 2, but they left room in the spec for up to 15 just in case. there&#39;s also a variant field, which says whether it&#39;s a normal UUID (&lt;code&gt;10&lt;/code&gt;, hex value &lt;code&gt;8&lt;/code&gt; through &lt;code&gt;b&lt;/code&gt;) or some other bullshit that may or may not adhere to any of the rest of this spec.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;other bullshit&lt;/summary&gt;
&lt;aside&gt;
&lt;p&gt;if the variant field is &lt;code&gt;0&lt;/code&gt; then it&#39;s a UUID from Apollo Computer&#39;s Network Computing System, which had UUIDs before DCE but defined them in a slightly different way. if it&#39;s &lt;code&gt;110&lt;/code&gt; then it&#39;s a UUID but the wrong endian, which Microsoft does sometimes when it makes UUIDs (it calls them GUIDs, because they&#39;re thinking too small, merely Global rather than Universal). if it&#39;s &lt;code&gt;111&lt;/code&gt; then you&#39;re living in the future where they assigned a meaning to variant &lt;code&gt;111&lt;/code&gt;. what&#39;s it like? how&#39;s the whole climate change thing going?&lt;/p&gt;
&lt;/aside&gt;
&lt;/details&gt;
&lt;p&gt;the data fields are&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;timestamp, which is measured since the start of the Gregorian calendar with 100ns resolution (which, with 60 bits available, repeats every 3653 years). this is how they make sure the UUID is unique across time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;clock sequence, which starts at a random number and goes up by one if time goes backwards, to ensure that things like clock drift or leap seconds don&#39;t lead to collisions. this is how they make sure the UUID is &lt;em&gt;actually&lt;/em&gt; unique across time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;node, which is just the MAC address on your network card (or one of them, if you&#39;ve got more than one), since different network cards already have to have different MAC addresses in order for networking to happen. this is how they make sure the UUID is unique across space.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;this is how we get that weird hyphen asymmetry, the groups come directly from the UUID data fields:&lt;/p&gt;
&lt;div style=&quot;display: grid; grid-template-rows: 1fr 1fr;&quot;&gt;&lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;span style=&quot;grid-column: 5; border-bottom: 1px solid;&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;grid-column: 8; border-bottom: 1px solid;&quot;&gt;variant&lt;/span&gt;&lt;/div&gt; &lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;1a8188ce&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;aa78&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;1&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;1ed&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;a&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;fa1&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;0242ac120002&lt;/kbd&gt;&lt;/div&gt; &lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;span style=&quot;grid-column: 1; border-top: 1px solid;&quot;&gt;time_low&lt;/span&gt;&lt;span style=&quot;grid-column: 3; border-top: 1px solid;&quot;&gt;time_mid&lt;/span&gt;&lt;span style=&quot;grid-column: 6; border-top: 1px solid;&quot;&gt;time_hi&lt;/span&gt;&lt;span style=&quot;grid-column: 8 / 10; border-top: 1px solid;&quot;&gt;clock sequence&lt;/span&gt;&lt;span style=&quot;grid-column: 11; border-top: 1px solid;&quot;&gt;node&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;you may have noticed that the time is split into three pieces, for the low 32 bits, the middle 16 bits, and the high 12 bits. why split it up like that? well, i don&#39;t know, but i suspect it makes a lot of things easier to split the 60 bit timestamp into at least 32 and 28 (and maybe splitting the 28 into 16 and 12 makes something easier in a way i&#39;m not seeing?). for one, 64-bit CPUs weren&#39;t mainstream yet, and for two, they had creative alternate uses for that time_low field.&lt;/p&gt;
&lt;h2 id=&quot;version-2&quot; tabindex=&quot;-1&quot;&gt;version 2&lt;/h2&gt;
&lt;p&gt;like a lot of multi-user operating systems, UNIX has users and groups, and allows for permissions management based on those users and groups. users and groups both have textual names and numeric IDs, so if you want to stably refer to a specific user or group, you can use its user ID or group ID. however, different computers can have different sets of users and groups, so if you&#39;re making the Distributed Computing Environment, you need a way to refer to a specific user or group on a specific machine. you&#39;re building on top of UNIX, because you&#39;re the Open Software Foundation (later The Open Group), so you have user and group IDs locally already. and you already made this UUID format, which has fields that refer to a specific machine. the other fields are already taken for time and also-time, but you didn&#39;t promise they&#39;d &lt;em&gt;always&lt;/em&gt; be time, right?&lt;/p&gt;
&lt;p&gt;in UUIDv2 (which the &lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01&quot;&gt;DCE spec&lt;/a&gt; calls the &amp;quot;security version&amp;quot;), the time_low field is literally just a UNIX user/group ID. the low byte of the clock sequence field is repurposed to specify whether it&#39;s a user or a group (or a secret third thing, an organization).&lt;/p&gt;
&lt;p&gt;i have several questions. for one, what about the other time fields? time_mid ticks up once every 7 minutes, if you construct your UUIDv2 out of a UUIDv1. do you just leave it at zero and let time_hi tick up every 325 days? do you leave mid and hi both at zero and party like it&#39;s 1582? for two, had they not invented MAC address spoofing yet? these days you can usually change your network card&#39;s MAC address to something else, so using that for anything security-related strikes me as highly dubious. for three,, what? just in general? why would you do this? this is some 5 Minute Crafts tier lifehackery. please refrain.&lt;/p&gt;
&lt;p&gt;presumably this worked well enough for DCE, but it has not withstood the test of time. i don&#39;t know that UUIDv2 even counts as a UUID, but it follows the UUID format and put a 2 in the version number slot, and so it lives on solely as negative space in the UUID version number range. (this is also apparently the deal with IPv5.)&lt;/p&gt;
&lt;p&gt;UUIDv2 may or may not have been a good idea, but the concept of &amp;quot;what if you had a UUID based on some specific value other than the current time&amp;quot; had legs.&lt;/p&gt;
&lt;h2 id=&quot;version-3&quot; tabindex=&quot;-1&quot;&gt;version 3&lt;/h2&gt;
&lt;p&gt;DCE was done being written, and then it kinda died, but people kept using UUIDs. DCE was a legacy-style Proper Goddamn Specification, written by the consortium that had since become The Open Group, who also run POSIX and the Single UNIX Specification and all that jazz (🌵 when the posix is sus !), but that sort of doorstopper spec was overkill for the humble UUID. what it needed, as a piece of computer bullshit, was an RFC. and so in 2005 the UUID was defined again in &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc4122&quot;&gt;RFC 4122&lt;/a&gt;, which kept v1, reduced v2 to one sentence, and added some new versions.&lt;/p&gt;
&lt;p&gt;one way to think of the goal of UUIDv2 is that it&#39;s about referring to an object that already has a contextually unique ID. in v2, that object is either a user or a group, and that context is a machine. v3 is a little more flexible, but one of the contexts mentioned in the RFC is domain names, so let&#39;s look at that.&lt;/p&gt;
&lt;p&gt;say i want something in the format of a UUID that refers to the domain name &lt;code&gt;example.com&lt;/code&gt;. one option would be to take the MD5 hash of &lt;code&gt;&amp;quot;example.com&amp;quot;&lt;/code&gt;, look at the first 16 bytes, line that up with the UUID format definition, and set the version and variant to the right values. this is cool, and it basically already works for domain names, but we want flexibility. if you and i both want to do the UUIDv2 thing of referring to users on a machine, and my context is my machine and your context is your machine, and both of us have a user named &lt;code&gt;cactus&lt;/code&gt;, oops, we have the same UUID, that&#39;s hardly Universally Unique. we need to include the context in what we&#39;re MD5ing, and we need to guarantee that different contexts have different values. and there&#39;s nothing computer people love more than recursion, so let&#39;s give the context a damn UUID.&lt;/p&gt;
&lt;p&gt;to make a UUIDv3, you need a name (which is just some text) and a UUID for your &amp;quot;name space&amp;quot; (which is the context in which your name is unique). take the binary representation of the namespace UUID, append the name, MD5 it, copy that into your UUID structure, set the version and variant, and you are done.&lt;/p&gt;
&lt;p&gt;the RFC defines some name space UUIDs already, like &lt;code&gt;6ba7b810-9dad-11d1-80b4-00c04fd430c8&lt;/code&gt; for domain names, so we can check this ourself:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; hashlib
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; uuid
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; dns_namespace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; uuid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UUID&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;6ba7b810-9dad-11d1-80b4-00c04fd430c8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; hashlib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;md5&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dns_namespace&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;b&quot;example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hexdigest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token string&quot;&gt;&#39;9073926b929fd1c26bc9fad77ae3e8eb&#39;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; uuid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uuid3&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dns_namespace&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
UUID&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;9073926b-929f-31c2-abc9-fad77ae3e8eb&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;this is pretty damn neat. if you have something that&#39;s contextually unique and you want to turn it into something that&#39;s globally unique, this is a really cool way to do that. (spoilers, except for one thing, which you may have noticed if you know your hash functions.) but this is only sometimes a problem you have; other times, you don&#39;t have anything unique yet, and you want something ex nihilo. v1 is still good, if you&#39;ve got a timestamp and a MAC address, but what if you&#39;re doing something like JS development where you can&#39;t exactly check the MAC address you&#39;re running on? well, satan help you. or what if that 100ns resolution isn&#39;t good enough like it was in the 90s?&lt;/p&gt;
&lt;h2 id=&quot;version-4&quot; tabindex=&quot;-1&quot;&gt;version 4&lt;/h2&gt;
&lt;p&gt;set the version and variant. fill the rest with random bits. there is no step 3.&lt;/p&gt;
&lt;p&gt;if you had asked me to guess last week what a UUIDv4 was, i&#39;d have just guessed it was 128 random bits (if you made me count how long it was). and i&#39;d have been wrong, but only by six bits. which is neat, but also a little bit bullshit, because like. me from last week wants those bits back!&lt;/p&gt;
&lt;p&gt;those six bits are for compatibility with the rest of the UUID universe, but if you&#39;re just looking for some random bytes to throw in your id column, you don&#39;t need compatibility with UUIDv1, you could just make some random bytes! and 16 of them is probably overkill for your use case anyway!&lt;/p&gt;
&lt;p&gt;wait hang on a minute, did that say MD5 earlier?&lt;/p&gt;
&lt;h2 id=&quot;version-5&quot; tabindex=&quot;-1&quot;&gt;version 5&lt;/h2&gt;
&lt;p&gt;turns out MD5 sucks. you know what&#39;s really cool? SHA-1.&lt;/p&gt;
&lt;p&gt;UUIDv5 is just UUIDv3 again, but with SHA-1 instead of MD5.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; hashlib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sha1&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dns_namespace&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;b&quot;example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hexdigest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token string&quot;&gt;&#39;cfbff0d193753685568c48ce8b15ae17d93cc34c&#39;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; uuid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uuid5&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dns_namespace&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
UUID&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;cfbff0d1-9375-5685-968c-48ce8b15ae17&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;thankfully, SHA-1 is the last word in hashing algorithms, it&#39;s never had problems and it&#39;s known to be very good. now to take a big sip of my coffee and check the NIST website.&lt;/p&gt;
&lt;div class=&quot;cohost-style-embed&quot;&gt;
&lt;opengraph-embed href=&quot;https://www.nist.gov/news-events/news/2022/12/nist-retires-sha-1-cryptographic-algorithm&quot; site-href=&quot;https://www.nist.gov&quot; site-favicon=&quot;https://www.nist.gov/themes/custom/nist_www/favicon.ico&quot; img-src=&quot;https://www.nist.gov/sites/default/files/images/2022/12/14/SecureHashAltogirthm23_Released_960x600_v4_A.png&quot; datetime=&quot;2022-12-15T12:00:00.000Z&quot; class=&quot;wmzczyv3e&quot;&gt;&lt;!-- lifted from the iframe service cohost was using, not sorry --&gt;
&lt;div style=&quot;display: block&quot; class=&quot;w __if _tha _ls _sm _or _alsd _alcd _lh14 _xi _hmt _tm _dm&quot;&gt;
    &lt;div class=&quot;wf&quot;&gt;
        &lt;div class=&quot;wc&quot;&gt;
            &lt;div style=&quot;padding-bottom: 62.5000%&quot; class=&quot;e&quot;&gt;
                &lt;div class=&quot;em&quot;&gt;&lt;a href=&quot;https://www.nist.gov/news-events/news/2022/12/nist-retires-sha-1-cryptographic-algorithm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; class=&quot;c&quot;&gt;
                    &lt;img src=&quot;https://www.nist.gov/sites/default/files/images/2022/12/14/SecureHashAltogirthm23_Released_960x600_v4_A.png&quot; class=&quot;c&quot;&gt;
                &lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;wt&quot;&gt;
            &lt;div class=&quot;t _f0&quot;&gt;
                &lt;div class=&quot;th _f1p&quot;&gt;&lt;a href=&quot;https://www.nist.gov/news-events/news/2022/12/nist-retires-sha-1-cryptographic-algorithm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; class=&quot;thl&quot;&gt;
                    &lt;span&gt;NIST Retires SHA-1 Cryptographic Algorithm&lt;/span&gt;
                &lt;/a&gt;&lt;/div&gt;
                &lt;div class=&quot;td&quot;&gt;
&lt;p&gt;The venerable cryptographic hash function has vulnerabilities that make its further use inadvisable.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;tf _f1m&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; class=&quot;ti _f1m&quot;&gt;&lt;img src=&quot;https://www.nist.gov/themes/custom/nist_www/favicon.ico&quot; class=&quot;tim&quot;&gt;&lt;/a&gt;
&lt;div class=&quot;tc&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; class=&quot;tw _f1m&quot;&gt;&lt;span class=&quot;twt&quot;&gt;&lt;span&gt;NIST&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;twd&quot;&gt;&lt;span&gt;nist.gov&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;tx&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;tm&quot;&gt;&lt;span datetime=&quot;2022-12-15T12:00:00.000Z&quot; class=&quot;tmd&quot;&gt;&lt;span&gt;Dec 15, 2022&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/opengraph-embed&gt;
&lt;div class=&quot;cohost-style-embed-link&quot;&gt;&lt;a href=&quot;https://www.nist.gov/news-events/news/2022/12/nist-retires-sha-1-cryptographic-algorithm&quot;&gt;https://www.nist.gov/news-events/news/2022/12/nist-retires-sha-1-cryptographic-algorithm&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;oh. that seems bad. when is UUIDv6?&lt;/p&gt;
&lt;h2 id=&quot;version-6&quot; tabindex=&quot;-1&quot;&gt;version 6&lt;/h2&gt;
&lt;p&gt;well. there&#39;s a &lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format&quot;&gt;draft RFC&lt;/a&gt; making updates to the UUID RFC, but it doesn&#39;t solve that problem, it solves different problems.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;edit 2025-03-04&lt;/em&gt;: this draft has been adopted as &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9562.html&quot;&gt;RFC 9562&lt;/a&gt;, obsoleting the original RFC 4122. hell yeah.&lt;/p&gt;
&lt;p&gt;one of the cool things about UUIDv1 is that you can decode the timestamp back out of it, and you don&#39;t need a separate field for the time when your object was created, because its ID tells you when it was created. however, the weird slicing and dicing that UUIDv1 does to the timestamp field means sorting by time is complicated, since the low 32 bits come first and the high 12 bits come last.&lt;/p&gt;
&lt;p&gt;UUIDv6 puts the whole timestamp in order, so that the most significant bit is first.&lt;/p&gt;
&lt;div style=&quot;display: grid; grid-template-rows: 1fr 1fr;&quot;&gt;&lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;span style=&quot;grid-column: 5; border-bottom: 1px solid;&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;grid-column: 8; border-bottom: 1px solid;&quot;&gt;variant&lt;/span&gt;&lt;/div&gt; &lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;1edaa9b4&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;e919&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;6&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;172&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;a&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;0d0&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;6721ef312724&lt;/kbd&gt;&lt;/div&gt; &lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;span style=&quot;grid-column: 1; border-top: 1px solid;&quot;&gt;time_high&lt;/span&gt;&lt;span style=&quot;grid-column: 3; border-top: 1px solid;&quot;&gt;time_mid&lt;/span&gt;&lt;span style=&quot;grid-column: 6; border-top: 1px solid;&quot;&gt;time_low&lt;/span&gt;&lt;span style=&quot;grid-column: 8 / 10; border-top: 1px solid;&quot;&gt;clock sequence&lt;/span&gt;&lt;span style=&quot;grid-column: 11; border-top: 1px solid;&quot;&gt;node&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;it keeps the clock sequence as-is from v1, but it explicitly recommends using random data instead of the MAC address for the node field, which is good.&lt;/p&gt;
&lt;p&gt;hang on, while we&#39;re messing with silly things from v1, what the hell is up with time since the gregorian calendar?&lt;/p&gt;
&lt;h2 id=&quot;version-7&quot; tabindex=&quot;-1&quot;&gt;version 7&lt;/h2&gt;
&lt;p&gt;what if we just did unix timestamp and randomness, so that we had easy sorting and decoding but also uniqueness, without any bullshit.&lt;/p&gt;
&lt;p&gt;well, v7 is that. 48 bits of unix timestamp in milliseconds (rolls over every 8920 years), 74 random bits, 6 control bits for version and variant.&lt;/p&gt;
&lt;div style=&quot;display: grid; grid-template-rows: 1fr 1fr;&quot;&gt;&lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;span style=&quot;grid-column: 3; border-bottom: 1px solid;&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;grid-column: 6; border-bottom: 1px solid;&quot;&gt;variant&lt;/span&gt;&lt;/div&gt; &lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;0186443f-2c00&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;7&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;5fb&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;8&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;00a-7ec0f02a852d&lt;/kbd&gt;&lt;/div&gt; &lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;span style=&quot;grid-column: 1; border-top: 1px solid;&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;grid-column: 4; border-top: 1px solid;&quot;&gt;rand_a&lt;/span&gt;&lt;span style=&quot;grid-column: 6 / 10; border-top: 1px solid;&quot;&gt;rand_b&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;this is the real good one. the only reason not to use it is that the RFC isn&#39;t approved so it isn&#39;t quite official yet, but if you don&#39;t need to care, find an implementation in your language of choice and go to fuckin town.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;edit 2025-03-04&lt;/em&gt;: hi it’s me from the future. i have good news: the RFC was approved. i do not have more good news than that.&lt;/p&gt;
&lt;p&gt;but what if yolo?&lt;/p&gt;
&lt;h2 id=&quot;version-8&quot; tabindex=&quot;-1&quot;&gt;version 8&lt;/h2&gt;
&lt;p&gt;if yolo, then you can use version 8. all of the version-specific fields are defined to be custom, so you can put whatever the hell nonsense you need to there.&lt;/p&gt;
&lt;div style=&quot;display: grid; grid-template-rows: 1fr 1fr;&quot;&gt;&lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;span style=&quot;grid-column: 3; border-bottom: 1px solid;&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;grid-column: 6; border-bottom: 1px solid;&quot;&gt;variant&lt;/span&gt;&lt;/div&gt; &lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;b00b5101-6969&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;8&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;420&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;-&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;9&lt;/kbd&gt;&lt;kbd style=&quot;font-weight: 600;&quot;&gt;a55-676179736578&lt;/kbd&gt;&lt;/div&gt; &lt;div style=&quot;display: grid; grid-column: 1 / 99; grid-template-columns: subgrid; text-align: center;&quot;&gt;&lt;span style=&quot;grid-column: 1; border-top: 1px solid;&quot;&gt;custom_a&lt;/span&gt;&lt;span style=&quot;grid-column: 4; border-top: 1px solid;&quot;&gt;custom_b&lt;/span&gt;&lt;span style=&quot;grid-column: 6 / 10; border-top: 1px solid;&quot;&gt;custom_c&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;so-what-did-we-learn&quot; tabindex=&quot;-1&quot;&gt;so what did we learn&lt;/h2&gt;
&lt;p&gt;UUIDs are pretty neat. if you need a database identifier and you get to pick something from scratch, use v7, it gives you timestamps for free. if you have things that are contextually unique and you want to turn them into universally unique IDs with a standard format, v5 is for you. if all you need is some random shit, v4 is that in the UUID format, but if you don&#39;t need the UUID format, you can also just use random bytes directly. if what you need is very specific shit nobody&#39;s thought of before, but in the UUID format, that&#39;s v8. v6 is the worse version of v7, v3 is the worse version of v5, v1 is the worse version of v6, and v2 was a mistake.&lt;/p&gt;
&lt;p&gt;see also: &lt;a href=&quot;https://cohost.org/twilight-sparkle/post/1010833-related-reading-on&quot;&gt;creative use of v1&#39;s MAC address field&lt;/a&gt;, &lt;a href=&quot;https://cohost.org/iliana/post/1013189-some-notes-on-furthe&quot;&gt;current RFC draft status and other notes&lt;/a&gt;, &lt;a href=&quot;https://cohost.org/vikxin/post/1014175-and-then-bluetooth-h&quot;&gt;bluetooth doing crimes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;in conclusion,&lt;/p&gt;
&lt;h1 id=&quot;uuids-nuts.&quot; tabindex=&quot;-1&quot;&gt;UUIDs nuts.&lt;/h1&gt;
</content>
  </entry>
  <entry>
    <title>some axes for a taxonomy of open source</title>
    <link href="https://www.boringcactus.com/2022/10/30/taxonomy-of-open-source.html" />
    <updated>2022-10-30T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2022/10/30/taxonomy-of-open-source.html</id>
    <content type="html">&lt;ul&gt;
&lt;li&gt;maintenance effort and expectations: professionally maintained / casually maintained / not maintained&lt;/li&gt;
&lt;li&gt;source of priorities: owner-driven / community-driven&lt;/li&gt;
&lt;li&gt;desire for input: PRs welcome / issues welcome / view-only&lt;/li&gt;
&lt;li&gt;license standards: bare minimum / give code back / don&#39;t be evil / be specifically good&lt;/li&gt;
&lt;li&gt;how bills get paid: not users&#39; problem / crowdfunding / selling support / we productized the hosting so if you also productize the hosting you&#39;re being a dick&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React is professionally maintained, owner-driven, PRs welcome, bare minimum, not users&#39; problem&lt;/li&gt;
&lt;li&gt;MongoDB is the same but give code back and we productized the hosting&lt;/li&gt;
&lt;li&gt;SQLite is professionally maintained, owner-driven, issues welcome, bare minimum, selling support&lt;/li&gt;
&lt;li&gt;anything that runs on github sponsors or patreon is crowdfunding&lt;/li&gt;
&lt;li&gt;the Hippocratic License or some of the others are don&#39;t be evil&lt;/li&gt;
&lt;li&gt;ACSL counts as be specifically good&lt;/li&gt;
&lt;li&gt;anti-licenses are the secret fifth tier of license standards, Good Fucking Luck&lt;/li&gt;
&lt;li&gt;some projects are actually community-driven but I can&#39;t think of one off the top of my head&lt;/li&gt;
&lt;li&gt;most of my side projects are not maintained, owner-driven, view-only, Good Fucking Luck, and crowdfunding at the time they were written but currently not your problem.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>writing balanced elevenary in the ogham script</title>
    <link href="https://www.boringcactus.com/2022/09/21/ogham-balanced-elevenary.html" />
    <updated>2022-09-21T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2022/09/21/ogham-balanced-elevenary.html</id>
    <content type="html">&lt;p&gt;&lt;strong&gt;for no particular reason&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;digits, from negative five to positive five:&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size:200%&quot;&gt;ᚅ ᚄ ᚃ ᚂ ᚁ ᚋ ᚆ ᚇ ᚈ ᚉ ᚊ&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;(romanized as N S F L B M H D T C Q)&lt;/p&gt;
&lt;p&gt;radix point &lt;span style=&quot;font-size:200%&quot;&gt;ᚖ&lt;/span&gt; (left as . when romanizing)&lt;/p&gt;
&lt;p&gt;a few nice values: &lt;span style=&quot;font-size:200%&quot;&gt;ᚆᚅᚈᚖᚆᚊᚇᚋᚅ&lt;/span&gt;, &lt;span style=&quot;font-size:200%&quot;&gt;ᚈᚊᚇ&lt;/span&gt;, &lt;span style=&quot;font-size:200%&quot;&gt;ᚈᚖᚇᚅᚆᚊᚆ&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;fuck around with it y&#39;self:&lt;/p&gt;
&lt;div class=&quot;cohost-style-embed&quot;&gt;
&lt;div style=&quot;left: 0; width: 100%; height: 300px; position: relative;&quot;&gt;&lt;iframe src=&quot;https://codepen.io/boringcactus/embed/preview/abGwgoG?default-tabs=js%2Cresult&amp;height=300&amp;host=https%3A%2F%2Fcodepen.io&amp;slug-hash=abGwgoG&quot; style=&quot;top: 0; left: 0; width: 100%; height: 100%; position: absolute; border: 0;&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;div class=&quot;cohost-style-embed-link&quot;&gt;&lt;a href=&quot;https://codepen.io/boringcactus/pen/abGwgoG&quot;&gt;https://codepen.io/boringcactus/pen/abGwgoG&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>A 2021 Survey of Rust GUI Libraries</title>
    <link href="https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html" />
    <updated>2021-10-24T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html</id>
    <content type="html">&lt;p&gt;(Note from the future: I did this again &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html&quot;&gt;in 2025&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;A year and a half ago I &lt;a href=&quot;https://www.boringcactus.com/2020/08/21/survey-of-rust-gui-libraries.html&quot;&gt;looked at a bunch of different Rust GUI libraries&lt;/a&gt;; that&#39;s a long time, so let&#39;s see if things have changed.&lt;/p&gt;
&lt;p&gt;As before, some context:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List of libraries is drawn from &lt;a href=&quot;https://www.areweguiyet.com/&quot;&gt;Are We GUI Yet&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I work on Windows, and I am lazy; as such, any library that requires more setup on Windows than &amp;quot;add it to &lt;code&gt;Cargo.toml&lt;/code&gt;&amp;quot; is disqualified.&lt;/li&gt;
&lt;li&gt;I&#39;m interested in making desktop application UIs, not game UIs.
As such, I don&#39;t already have a graphics pipeline set up, so if I have to decide between vulkano and glium before I can get going, and the library documentation doesn&#39;t tell me which one is the right one to choose, I&#39;m moving on.
If you make me learn game dev before I can use your UI library, I am not the target audience for your UI library.&lt;/li&gt;
&lt;li&gt;Accessibility should not be an afterthought.
I don&#39;t currently need accessibility tools like screen readers, but some people do, and I may find myself among them at some time.
I&#39;m not experienced enough to evaluate in depth the quality of accessibility features, but I know how to turn on Windows Narrator and see if it can tell what&#39;s going on in an application window or not.
If I have Hello World running in your library, but Narrator can&#39;t tell that it says Hello World, you have zero accessibility support, and therefore I have zero interest in your library.&lt;/li&gt;
&lt;li&gt;These are all my opinions.
Even if you also work on Windows and are lazy, and also don&#39;t want to learn the practical difference between Vulkan and OpenGL, and also care about accessibility, you are not me, so my opinions might not match yours.&lt;/li&gt;
&lt;li&gt;This shit is really difficult.
I&#39;ve tried writing Rust bindings for wxWidgets, because I like wxPython when I&#39;m working in Python, and for Tk, because tkinter is kinda neat too.
Neither of those went anywhere, because I don&#39;t have the skill or patience to do that.
All of the libraries I&#39;m about to review were written by people who put a lot of time and energy into making them, and even if the results don&#39;t meet my needs or tastes, I&#39;m glad they&#39;ve done the work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The short version, if you&#39;re in a hurry:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;library&lt;/th&gt;
&lt;th&gt;easy setup?&lt;/th&gt;
&lt;th&gt;nonzero accessibility?&lt;/th&gt;
&lt;th&gt;overall vibe check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#azul&quot;&gt;Azul&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#conrod&quot;&gt;Conrod&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#core-foundation&quot;&gt;core-foundation&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#druid&quot;&gt;druid&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#egui&quot;&gt;egui&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#fltk&quot;&gt;fltk&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#gtk&quot;&gt;GTK&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#iced&quot;&gt;Iced&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#imgui&quot;&gt;imgui&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#iui&quot;&gt;iui&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;👍&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#kas&quot;&gt;KAS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#lvgl&quot;&gt;lvgl&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#native-windows-gui&quot;&gt;native-windows-gui&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;🤷&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#orbtk&quot;&gt;OrbTk&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#qmetaobject-rs&quot;&gt;qmetaobject-rs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#qt_widgets&quot;&gt;qt_widgets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#relm&quot;&gt;relm&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#rust-qt-binding-generator&quot;&gt;rust-qt-binding-generator&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#sciter-rs&quot;&gt;sciter-rs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#sixtyfps&quot;&gt;SixtyFPS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#tauri&quot;&gt;tauri&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#vgtk&quot;&gt;vgtk&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html#webrender&quot;&gt;WebRender&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;azul&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://azul.rs/&quot;&gt;Azul&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://azul.rs/guide/1.0.0-alpha1/Installation&quot;&gt;installation guide&lt;/a&gt; says you need to separately download the precompiled library and make sure it&#39;s in the right folder and ughhhh that&#39;s more setup than adding it to &lt;code&gt;Cargo.toml&lt;/code&gt; and i literally put in my rules that that&#39;s more work than i want to do.
Rust already has a build system, I don&#39;t want to have to fuck with things independently of that.&lt;/p&gt;
&lt;h2 id=&quot;conrod&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/pistondevelopers/conrod&quot;&gt;Conrod&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My previous adventures in Rust GUI development have led me to Conrod several times, and most of those times it&#39;s been a hassle, but we&#39;ll see if things have changed since then.
The &lt;a href=&quot;https://docs.rs/conrod_core/0.75.0/conrod_core/guide/chapter_3/index.html&quot;&gt;hello world tutorial&lt;/a&gt; appears to actually say which backends to use, which is a welcome and sorely-needed improvement.&lt;/p&gt;
&lt;p&gt;Wait, what did that say there?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;At the time of writing (September 2017),&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ok hold up, how outdated is this?
It said to depend on &lt;code&gt;conrod 0.55.0&lt;/code&gt;, but it looks like the latest version is actually &lt;code&gt;0.75.0&lt;/code&gt;.
And there is no &lt;code&gt;conrod 0.75.0&lt;/code&gt;, apparently, because they split the crate into several different crates as of 0.62.0; &lt;a href=&quot;https://crates.io/crates/conrod&quot;&gt;allegedly&lt;/a&gt; I am &amp;quot;most likely looking for &lt;code&gt;conrod_core&lt;/code&gt;.&amp;quot;
So the guide, which is &lt;em&gt;part of &lt;code&gt;conrod_core&lt;/code&gt;&lt;/em&gt;, is so out of date that it uses the wrong name for itself.
If I just use &lt;code&gt;conrod_core 0.75.0&lt;/code&gt; where the guide said to use &lt;code&gt;conrod 0.55.0&lt;/code&gt; my issues have not ceased, though:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;error: failed to select a version for `conrod_core`.
    ... required by package `conrod-demo v0.1.0 (D:&#92;Melody&#92;Projects&#92;misc&#92;gui-survey-2021&#92;conrod-demo)`
versions that meet the requirements `^0.75.0` are: 0.75.0

the package `conrod-demo` depends on `conrod_core`, with features: `glium` but `conrod_core` does not have these features.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the officially-recommended backends don&#39;t exist anymore, or at least they no longer exist in the same way and it&#39;s my job to figure out how to get them back.
I don&#39;t want to debug the official tutorial that&#39;s linked in the README.
As such, Conrod appears to not be for me.&lt;/p&gt;
&lt;h2 id=&quot;core-foundation&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/servo/core-foundation-rs&quot;&gt;core-foundation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&amp;quot;Bindings to Core Foundation for macOS&amp;quot; are cool if I&#39;m using macOS.
As established, I&#39;m not.&lt;/p&gt;
&lt;h2 id=&quot;druid&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://linebender.org/druid/&quot;&gt;druid&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Previously, druid was one of the two libraries I was most impressed by; let&#39;s see if that still holds.
The &amp;quot;this is outdated, and should be replaced with a walkthrough of getting a simple app built and running&amp;quot; message in &lt;a href=&quot;https://linebender.org/druid/get_started.html&quot;&gt;the official guide&lt;/a&gt; persists, and upon further inspection there&#39;s been one release since my last blog post, with few new features.&lt;/p&gt;
&lt;p&gt;The &amp;quot;hello world&amp;quot; code in the guide has broken - something that used to take a value now takes a closure returning that value - but that&#39;s an easy fix.&lt;/p&gt;
&lt;p&gt;A harder fix, though, is that in the official &amp;quot;hello world&amp;quot; example, Windows Narrator can&#39;t find the text &amp;quot;hello world&amp;quot;.
In my last post, I treated accessibility as an afterthought, to the point where I had to go in several months later and retroactively check for and add information about screen reader support; this was the wrong decision for me, and it&#39;s the wrong decision for druid.
They list &amp;quot;Offer robust accessibility support&amp;quot; under their goals; maybe one day they&#39;ll get there.&lt;/p&gt;
&lt;h2 id=&quot;egui&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/emilk/egui&quot;&gt;egui&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is the first entry on Are We GUI Yet that wasn&#39;t there last year, and it was seeing something about this on Twitter that got me interested in revisiting this topic in the first place.
There&#39;s an official template that comes with some (bash, and thereby more-painful-on-Windows, but what can you do) scripts for managing WASM builds, but I&#39;m not interested in WASM at this point, so I&#39;ll just yoink the code and undo the multi-track drifting that allows WASM builds (which they &lt;a href=&quot;https://github.com/rustwasm/wasm-bindgen/pull/1843&quot;&gt;probably don&#39;t actually need to do because wasm-bindgen can just do the right thing on binary crates due to work that i did almost exactly two years ago now&lt;/a&gt;, but whatever).&lt;/p&gt;
&lt;p&gt;It runs correctly, which is good, but out of the box, Windows Narrator can&#39;t tell what&#39;s going on.
There is, however, &lt;a href=&quot;https://github.com/emilk/egui/issues/167&quot;&gt;work-in-progress screen reading support&lt;/a&gt; which we can opt into with the &lt;code&gt;screen_reader&lt;/code&gt; feature.
Unfortunately, this appears not to do anything, at least for me with my current setup and near-zero knowledge of egui.
Hopefully this becomes more robust soon.&lt;/p&gt;
&lt;h2 id=&quot;fltk&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/fltk-rs/fltk-rs&quot;&gt;FLTK&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The presence of the &lt;code&gt;fltk-bundled&lt;/code&gt; feature is very much appreciated.
Unfortunately, it doesn&#39;t make up for a lack of screen reader accessibility.&lt;/p&gt;
&lt;h2 id=&quot;gtk&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://gtk-rs.org/&quot;&gt;GTK&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The GTK project appears to be putting a lot of effort towards providing good Rust bindings.
Unfortunately,&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;warning: Could not run `&quot;pkg-config&quot; &quot;--libs&quot; &quot;--cflags&quot; &quot;glib-2.0&quot; &quot;glib-2.0 &gt;= 2.48&quot;`&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full disclosure: I have tried several times to get GTK 3 or 4 working for development on this computer.
I have failed several times, and succeeded none.
I am disinclined to try and fail again.&lt;/p&gt;
&lt;h2 id=&quot;iced&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/iced-rs/iced&quot;&gt;Iced&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Iced was one of the two projects I was impressed by last time around.
Unfortunately, that was before I started caring about accessibility as much; the counter example, when ran as a native binary, gives no information to Narrator.&lt;/p&gt;
&lt;h2 id=&quot;imgui&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/imgui-rs/imgui-rs&quot;&gt;imgui&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The README doesn&#39;t have a self-contained example, but there&#39;s a &lt;a href=&quot;https://github.com/imgui-rs/imgui-rs/blob/v0.8.0/imgui-examples/examples/hello_world.rs&quot;&gt;hello world example&lt;/a&gt; that looks pretty reasonable.
But that &lt;code&gt;mod support;&lt;/code&gt; declaration looks like it&#39;s probably abstracting out some complexity, let me just—&lt;a href=&quot;https://github.com/imgui-rs/imgui-rs/blob/v0.8.0/imgui-examples/examples/support/mod.rs&quot;&gt;oh god my eyes&lt;/a&gt; that&#39;s a lot of boilerplate.
Am I supposed to copy and paste all that logic into my code?
If so, why is it not just part of the library already?
If not, why do all the examples have it?&lt;/p&gt;
&lt;p&gt;As stated, I don&#39;t already have a graphics pipeline set up, so I think I am not the target audience for imgui.&lt;/p&gt;
&lt;h2 id=&quot;iui&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/rust-native-ui/libui-rs&quot;&gt;iui&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This one&#39;s also a new addition compared to my previous writeup.
And I&#39;ll be damned, it actually works!
Didn&#39;t have to mess around with compilers, worked just fine with Narrator.&lt;/p&gt;
&lt;p&gt;It&#39;s been three years since the last crates.io release, but I&#39;m writing this in the same order you&#39;re reading it, so this is the first one I&#39;ve seen that actually has any accessibility support whatsoever.
If more than a handful of other libraries can clear that not-very-high bar, I&#39;ll fill in a more robust comparison, but for now this is pretty damn good.&lt;/p&gt;
&lt;h2 id=&quot;kas&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/kas-gui/kas&quot;&gt;KAS&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Runs fine, doesn&#39;t give Narrator any info.&lt;/p&gt;
&lt;h2 id=&quot;lvgl&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/rafaelcaricio/lvgl-rs&quot;&gt;lvgl&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Setting up &lt;code&gt;DEP_LV_CONFIG_PATH&lt;/code&gt; is 1. more work than just adding it to &lt;code&gt;Cargo.toml&lt;/code&gt; and 2. apparently some bullshit based on C header configuration??&lt;/p&gt;
&lt;h2 id=&quot;native-windows-gui&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/gabdube/native-windows-gui&quot;&gt;native-windows-gui&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Unsurprisingly, this works like a charm on Windows.
No installation problems, no accessibility problems, no worries.
It does, of course, only work on Windows, though, and I would love to wind up with code that theoretically should be cross-platform.&lt;/p&gt;
&lt;h2 id=&quot;orbtk&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/redox-os/orbtk&quot;&gt;OrbTk&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Works, no accessibility.&lt;/p&gt;
&lt;h2 id=&quot;qmetaobject-rs&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/woboq/qmetaobject-rs&quot;&gt;qmetaobject-rs&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Error: Failed to execute qmake. Make sure &#39;qmake&#39; is in your path!&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;qt_widgets&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://rust-qt.github.io/&quot;&gt;qt_widgets&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;failed to run command: &quot;qmake&quot; &quot;-query&quot; &quot;QT_VERSION&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;relm&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/antoyo/relm&quot;&gt;relm&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;LINK : fatal error LNK1181: cannot open input file &#39;gtk-3.lib&#39;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;rust-qt-binding-generator&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://invent.kde.org/sdk/rust-qt-binding-generator&quot;&gt;rust-qt-binding-generator&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;installing Qt is more work than i want to have to do.&lt;/p&gt;
&lt;h2 id=&quot;sciter-rs&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/sciter-sdk/rust-sciter&quot;&gt;sciter-rs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;downloading the SDK independent of Cargo is more work than i want to have to do.&lt;/p&gt;
&lt;h2 id=&quot;sixtyfps&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://sixtyfps.io/&quot;&gt;SixtyFPS&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;works, no accessibility&lt;/p&gt;
&lt;h2 id=&quot;tauri&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://tauri.studio/en/&quot;&gt;Tauri&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://tauri.studio/en/docs/usage/development/integration#1-start-a-new-tauri-project&quot;&gt;whatever the hell any of this is&lt;/a&gt; is more work than i want to have to do.
it looks like this wants you to have both npm/yarn and Cargo?
and i don&#39;t think i like the implications of that.&lt;/p&gt;
&lt;h2 id=&quot;vgtk&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/bodil/vgtk&quot;&gt;vgtk&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Could not run `&quot;pkg-config&quot; &quot;--libs&quot; &quot;--cflags&quot; &quot;glib-2.0&quot; &quot;glib-2.0 &gt;= 2.44&quot;`&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;webrender&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/servo/webrender&quot;&gt;WebRender&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/servo/webrender/blob/master/examples/common/boilerplate.rs&quot;&gt;what the goddamn hell is this supposed to be&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;the basic example is doing shader fuckery!
that doesn&#39;t seem basic to me!&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;summary&lt;/h2&gt;
&lt;p&gt;Well, shit.
Turns out if you want low-fuss Windows setup, nonzero accessibility, and theoretical cross-platform support, and you want them in the present rather than the future, there&#39;s only one option (at least among the things listed on Are We GUI Yet).
Shout out to &lt;a href=&quot;https://github.com/rust-native-ui/libui-rs&quot;&gt;iui&lt;/a&gt;, in all its three-year-old-latest-release no-way-to-draw-images(-at-least-that-I-can-find) glory.
Honorable mention to &lt;a href=&quot;https://github.com/emilk/egui&quot;&gt;egui&lt;/a&gt; for having put in at least some effort towards accessibility; if I revisit this topic in another year or two, I suspect I&#39;ll be very impressed with egui.
Additional reference to &lt;a href=&quot;https://sixtyfps.io/&quot;&gt;SixtyFPS&lt;/a&gt; who have made an entire other markup language for specifying UI structure; I&#39;m not sure it&#39;s a good or necessary approach, but it&#39;s eye-catching, and maybe at some point they&#39;ll start working on accessibility and I&#39;ll take a more thorough look.
(I&#39;m not entirely sure the extent to which German and/or EU law requires commercial products meet accessibility standards, but if SixtyFPS is doing commercial licensing, they&#39;d better have looked into that.)&lt;/p&gt;
&lt;h2 id=&quot;whining&quot; tabindex=&quot;-1&quot;&gt;whining&lt;/h2&gt;
&lt;p&gt;I&#39;ve worked with Python and Rust a lot, and those are my two languages of choice for new personal projects these days.
Python, as an interpreted language, does not really go out of its way to give you binaries that you can just hand to end users; it&#39;s possible, but as of the last time I checked, the tools were all varying shades of pains in the ass to use.
Rust seems like it&#39;d be far better suited for end-user application development, since at the end you have a .exe or whatever that you can hand off to anybody.
It looks like the Rust ecosystem doesn&#39;t really have a solid foundation for GUI development, despite being a good language for GUI development.
Python is set up such that it&#39;s easier to write tools for other developers (who are likely to have a Python interpreter installed) than for end users (who are not), and developers tend to prefer CLI programs to GUI ones, so it would be reasonable to expect GUI tools in Python to be worse than they are in Rust, right?&lt;/p&gt;
&lt;p&gt;Let&#39;s check in on that:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# after `pip install wxPython` and no other fuckery:&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; wx
app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;App&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
frame &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Frame&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;wxPython Example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
panel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Panel&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StaticText&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;panel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello World!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Show&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MainLoop&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was easy to set up on Windows, correctly provides accessibility data to Windows Narrator, and was really concise to implement.
So if I want a nice, powerful, elegant GUI library without losing my mind trying to set it up, I can have that, as long as I don&#39;t need other people to be able to conveniently run the program I make.
How the goddamn fuck did we get here?&lt;/p&gt;
&lt;p&gt;Unfortunately, I know the answer to that one.
wxWidgets, the library wxPython is binding to, is a C++ library that is used primarily by inheriting from wxWidgets&#39;s classes.
Rust bindings to C++ libraries are difficult to create in the best of scenarios, and inheritance is among the worst of scenarios, because Rust does not have any concepts to which C++ inheritance maps cleanly.
The wxPython bindings are maintained by the wxWidgets team themselves, and Python is designed to be extended by C/C++ code.
Rust&#39;s FFI with C is fine, but bindgen is really bad at handling C++ as used by wxWidgets, and manually authoring the thousands of bindings and then providing a safe abstraction over them would be extraordinarily not a good time.&lt;/p&gt;
&lt;p&gt;So if wxWidgets isn&#39;t feasible to use from Rust, what about some other library?
Well, the only reason wxPython is as easy to install as it is is that the maintainers made sure it comes with the fully-compiled wxWidgets binaries, especially on Windows where everything of that sort is harder to do by hand after the fact.
Some Rust bindings to C libraries, like SDL2 or the aforementioned FLTK, will build the libraries they depend on automatically, encapsulating all that complexity and letting me just use them; most, like GTK, do not.
I&#39;m not sure if there&#39;s a technical reason gtk-rs and the rest of these can&#39;t do that (or, for bonus points, just download a precompiled binary from the official releases and save a bunch of time), or if it&#39;s just that nobody&#39;s asked for it.
Linux (and to some extent Mac I suppose) devs are accustomed to niceties like &lt;code&gt;pkg-config&lt;/code&gt; and actual package managers that make it easy to install a C library; on Windows, we don&#39;t have that (unless we&#39;re working inside MSYS2 or what have you, but that&#39;s its own mess sometimes).
So please: if you&#39;re writing Rust bindings to a C library, do your Windows users a favor, and give us a &lt;code&gt;bundled&lt;/code&gt; feature.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>An Anti-License Manifesto</title>
    <link href="https://www.boringcactus.com/2021/09/29/anti-license-manifesto.html" />
    <updated>2021-09-29T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2021/09/29/anti-license-manifesto.html</id>
    <content type="html">&lt;p&gt;software licenses are unavoidably a legal tool.
the legal system, in the US and approximately everywhere else, is not a machine that leads to justice.
therefore, software licenses do not lead to justice.&lt;/p&gt;
&lt;p&gt;we cannot software license our way to a better world.
as such, we should and must software license our way to a &lt;em&gt;stranger&lt;/em&gt; world.
permissive licenses and copyleft licenses are both tools of the corporate status quo.
we therefore reject all conventional software licenses, and instead champion the weird, the experimental, the decorative, the hostile, the absurd, the useless, the straight up unhinged.&lt;/p&gt;
&lt;p&gt;some anti-licenses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.archive.org/web/20220927225115/https://github.com/scanlime/bandsocks/blob/master/LICENSE.md&quot;&gt;Micah Elizabeth Scott&#39;s untitled license for several projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bouk/monkey/blob/b118a17387657e860bb85a0f3dafd15cad03c266/LICENSE.md&quot;&gt;Bouke van der Bijl&#39;s untitled license for &lt;code&gt;monkey&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/benlk/misc-licenses/blob/7070c779b922ff6e98d96ff20d407a91ca1b0231/fight-club-license.md&quot;&gt;Ben Keith&#39;s Fight Club License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/benlk/misc-licenses/blob/7070c779b922ff6e98d96ff20d407a91ca1b0231/schrodinger-license.md&quot;&gt;Ben Keith&#39;s Schrödinger License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/benlk/misc-licenses/blob/7070c779b922ff6e98d96ff20d407a91ca1b0231/mephistopheles-license.md&quot;&gt;Ben Keith&#39;s Mephistopheles License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/supertunaman/cdl/blob/f0ae734f4abce311070ac0c401dbc0230cbc4344/COPYING&quot;&gt;Andrew &amp;quot;Tuna&amp;quot; Williamson&#39;s Chicken Dance License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/indeyets/syck/blob/2656dcc9e879a26e0d7c36ae45f22150d2692ad0/COPYING#L26-L53&quot;&gt;why the lucky stiff&#39;s Death and Repudiation License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pjreddie/darknet/blob/2f1fc380e950a29910ae011d61929ecd16baa62e/LICENSE&quot;&gt;Joseph Redmon&#39;s YOLO LICENSE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ErikMcClure/bad-licenses/blob/04fafc7f0701faf5616d7f531e3c02801e3dc8c8/overwatch&quot;&gt;Rain&#39;s Overwatch License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ErikMcClure/bad-licenses/blob/04fafc7f0701faf5616d7f531e3c02801e3dc8c8/Bantown-public-license&quot;&gt;the Bantown public license&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Xe/waifud/blob/e7de416dbc0c14cf29e50b24e2d6337881294da9/LICENSE&quot;&gt;Xe&#39;s Be Gay Do Crimes License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://paste.sr.ht/~boringcactus/f7ad59d5e244418787ced364df669dcf537d9662&quot;&gt;my Fuck Bitches Get Money License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mitchellharper12/folding-scripts/blob/d0d519bfcb964338657d5943ff664fed317e4b6a/LICENSE.md?plain=1#L30-L37&quot;&gt;Mitchell Harper&#39;s AntiLicense&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/12Me21/powder-game-1/blob/f4fa13739f4f2079f568536230fb366782e6e85f/LICENSE.mp4&quot;&gt;whatever the hell is going on here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lifning.info/BBHL&quot;&gt;Big Bill Hell&#39;s License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git.isincredibly.gay/srxl/srxl.me/src/commit/77621d443f1f52c9907bac3ff9c8bf495e4880e6/LICENSE&quot;&gt;the MIT (Mostly Incomprehensible Text) License&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>How to Develop a Deeply Unhealthy Relationship with Twitter</title>
    <link href="https://www.boringcactus.com/2021/05/25/how-to-develop-a-deeply-unhealthy-relationship-with-twitter.html" />
    <updated>2021-05-25T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2021/05/25/how-to-develop-a-deeply-unhealthy-relationship-with-twitter.html</id>
    <content type="html">&lt;ol&gt;
&lt;li&gt;Follow some cool people on Twitter.&lt;/li&gt;
&lt;li&gt;Spend time on Twitter.&lt;/li&gt;
&lt;li&gt;See a cool person quote tweet some dipshit to be mad about them, or retweet a reply to some dipshit that dunks on or dismantles them, or say something controversial yet correct where you just know a bunch of the replies and quote tweets are going to be dipshits.&lt;/li&gt;
&lt;li&gt;Think to yourself &amp;quot;damn, it sucks that people on here are being dipshits. but hey, if I block all these people now, I&#39;ll never have them show up in my mentions later&amp;quot;&lt;/li&gt;
&lt;li&gt;Read through all the immediate (and, if your brain decides to, several levels deep) context, finding and blocking dipshits as they come up.&lt;/li&gt;
&lt;li&gt;Repeat steps 3-5 several times. Importantly, never only do step 3, so that the subsequent quest to block all dipshits attaching themselves to the original tweet becomes a habit, and then a reflex, rather than a deliberate decision.&lt;/li&gt;
&lt;li&gt;Get mad that you keep seeing dipshits doing dipshit things on Twitter. Consider leaving, but then say &amp;quot;but the cool people are here! hell am I gonna do, convince them to stop arguing with dipshits and posting takes that dipshits will disagree with?&amp;quot; Forget that you have any other options.&lt;/li&gt;
&lt;li&gt;Wait for Discourse, when arguing and dipshittery are ubiquitous.&lt;/li&gt;
&lt;li&gt;Reflexively seek out all the dipshits swarming around every time you see a new Discourse-related tweet.&lt;/li&gt;
&lt;li&gt;Be miserable on Twitter because your experience is overwhelmingly dominated by seeing dipshits being wrong and obnoxious, because you built a habit of seeking those dipshits out in the first place.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In conclusion, as Fraxiom put it in food house&#39;s &amp;quot;&lt;a href=&quot;https://youtu.be/y-7qbdxIPWc?t=92&quot;&gt;8 now&lt;/a&gt;&amp;quot;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I need to delete Twitter &#39;cause it gives me fucking mental illness&lt;/p&gt;
&lt;/blockquote&gt;
</content>
  </entry>
  <entry>
    <title>Non-FSF Copyleft Usage</title>
    <link href="https://www.boringcactus.com/2021/03/24/non-fsf-copyleft.html" />
    <updated>2021-03-24T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2021/03/24/non-fsf-copyleft.html</id>
    <content type="html">&lt;p&gt;The Free Software Foundation has decided they&#39;d rather hang out with a sex pest than have an ounce of credibility, so fuck em.
Let&#39;s look at the copyleft licenses they didn&#39;t write and see how they&#39;re used.&lt;/p&gt;
&lt;p&gt;I&#39;ll be using the &lt;a href=&quot;https://blueoakcouncil.org/copyleft&quot;&gt;Blue Oak Council&#39;s list of copyleft licenses&lt;/a&gt; here, because I don&#39;t know of a good other way to find specifically copyleft licenses.
I&#39;ll be searching GitHub for &lt;code&gt;filename:LICENSE &amp;quot;&amp;lt;some snippet from the license&amp;gt;&amp;quot;&lt;/code&gt; or checking Wikipedia and then seeing if anything notable turns up.&lt;/p&gt;
&lt;h2 id=&quot;maximal-copyleft&quot; tabindex=&quot;-1&quot;&gt;Maximal Copyleft&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cryptographic Autonomy License: almost entirely cryptocurrency bullshit&lt;/li&gt;
&lt;li&gt;Parity Public License: maintains &lt;a href=&quot;https://paritylicense.com/&quot;&gt;its own list of users&lt;/a&gt;, including some work from substack (original author of browserify, prominent in the node.js scene, name stolen by a shitty newsletter startup) and some Git hook manager with 22.2k stars on GitHub (although it&#39;s only Parity licensed while it&#39;s in early access, and it&#39;ll revert to MIT later on).
so there&#39;s a notable person and a notable project using this license, which is neat.&lt;/li&gt;
&lt;li&gt;Reciprocal Public License: well, there&#39;s &amp;quot;the most popular service bus for .NET&amp;quot;, but that appears to be &lt;a href=&quot;https://en.wikipedia.org/wiki/Reciprocal_Public_License&quot;&gt;basically it&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;network-copyleft&quot; tabindex=&quot;-1&quot;&gt;Network Copyleft&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Affero GPL: fuck GNU, fuck the FSF, fuck RMS, fuck you&lt;/li&gt;
&lt;li&gt;Apple Public Source License: unsurprisingly, the only use I&#39;ve seen is for stuff derived directly from Apple releases&lt;/li&gt;
&lt;li&gt;Common Public Attribution License: apparently it was invented by some startup that hasn&#39;t existed since 2012, and I could only find one nontrivial use of it&lt;/li&gt;
&lt;li&gt;European Union Public License: &lt;a href=&quot;https://github.com/search?q=license%3Aeupl-1.2&amp;amp;type=Repositories&quot;&gt;well,&lt;/a&gt; there&#39;s &amp;quot;&lt;code&gt;dig&lt;/code&gt; but i rewrote it in rust&amp;quot; &lt;a href=&quot;https://en.wikipedia.org/wiki/Category:Software_using_the_European_Union_Public_Licence&quot;&gt;and&lt;/a&gt; there&#39;s Pi-hole (actually neat!) but that&#39;s about it.
i think being the pi-hole license (plus having a metagovernment behind it) would probably be enough to ensure its longevity.&lt;/li&gt;
&lt;li&gt;Non-Profit Open Software License: &lt;a href=&quot;https://github.com/search?q=filename%3ALICENSE+%22Non-Profit+Open+Software+License%22&amp;amp;type=code&quot;&gt;GitHub can&#39;t find any usage of it!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Open Software License: it&#39;s got Magento (WordPress for ecommerce) and that&#39;s about it&lt;/li&gt;
&lt;li&gt;RealNetworks Public Source License: ehhhhh I am officially ignoring any license with a mostly-defunct vendor in the title now&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;strong-copyleft&quot; tabindex=&quot;-1&quot;&gt;Strong Copyleft&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;BSD Protection: nothing uses this&lt;/li&gt;
&lt;li&gt;CC BY-SA: that&#39;s not a software license, you can&#39;t fool me&lt;/li&gt;
&lt;li&gt;GPL: fuck that&lt;/li&gt;
&lt;li&gt;Q Public License: this name has aged poorly and i can&#39;t find use of it&lt;/li&gt;
&lt;li&gt;Sleepycat License: adorable! but not used by anything&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;weak-copyleft&quot; tabindex=&quot;-1&quot;&gt;Weak Copyleft&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Common Development and Distribution License: apparently this is &lt;a href=&quot;https://en.wikipedia.org/wiki/Common_Development_and_Distribution_License#Adoption&quot;&gt;popular with enterprisey bullshit because it started at Sun&lt;/a&gt; but it seems like it&#39;s mostly enterprisey bullshit&lt;/li&gt;
&lt;li&gt;Common Public License: this one&#39;s IBM&#39;s, so there&#39;s &lt;a href=&quot;https://en.wikipedia.org/wiki/Category:Software_using_the_CPL_license&quot;&gt;even less usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Eclipse Public License: well, there&#39;s Eclipse, but also &lt;a href=&quot;https://en.wikipedia.org/wiki/Eclipse_Public_License#Notable_projects&quot;&gt;a lot of other non-enterprise-bullshit (mostly Java) stuff apparently&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Erlang Public License: even Erlang doesn&#39;t use it anymore lol&lt;/li&gt;
&lt;li&gt;IBM Public License: ehhhhhhhh&lt;/li&gt;
&lt;li&gt;LGPL: fuck that&lt;/li&gt;
&lt;li&gt;Mozilla Public License: &lt;a href=&quot;https://en.wikipedia.org/wiki/Mozilla_Public_License#Notable_users&quot;&gt;lots of users&lt;/a&gt;, probably not going anywhere anytime soon&lt;/li&gt;
&lt;li&gt;Microsoft Reciprocal License: &lt;a href=&quot;https://github.com/search?l=&amp;amp;o=desc&amp;amp;q=license%3Ams-rl&amp;amp;s=stars&amp;amp;type=Repositories&quot;&gt;not really widely used&lt;/a&gt;, unsurprisingly&lt;/li&gt;
&lt;li&gt;Sun Public License: &lt;a href=&quot;https://en.wikipedia.org/wiki/Sun_Public_License&quot;&gt;apparently it&#39;s the older, worse CDDL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;for maximal copyleft (&amp;quot;must publish source, even if only changing for internal use&amp;quot;), &lt;a href=&quot;https://spdx.org/licenses/Parity-7.0.0.html&quot;&gt;Parity&lt;/a&gt; is probably the most promising future license.&lt;/p&gt;
&lt;p&gt;for network copyleft (&amp;quot;must offer source to users even across a network&amp;quot;), the &lt;a href=&quot;https://spdx.org/licenses/EUPL-1.2.html&quot;&gt;European Union Public License&lt;/a&gt; is already seeing some adoption.&lt;/p&gt;
&lt;p&gt;for &amp;quot;strong&amp;quot; copyleft (&amp;quot;must offer source to users even if just linking as a library&amp;quot;), there are no good options because the GPL has been the only game in town for ages.&lt;/p&gt;
&lt;p&gt;for &amp;quot;weak&amp;quot; copyleft (&amp;quot;must offer source to users&amp;quot;), the &lt;a href=&quot;https://spdx.org/licenses/MPL-2.0.html&quot;&gt;Mozilla Public License&lt;/a&gt; looks like it has a good chance of continuing to exist and be used by actual projeccts for a nice long while.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Cactus&#39;s Obvious, Intuitive Naming Scheme</title>
    <link href="https://www.boringcactus.com/2021/03/21/coins.html" />
    <updated>2021-03-21T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2021/03/21/coins.html</id>
    <content type="html">&lt;p&gt;It&#39;s well-established that naming things is one of the two hardest problems in computer science (along with cache invalidation and off-by-one errors).
So I solved naming.
If you&#39;ve got a project that needs a name, here you go.&lt;/p&gt;
&lt;p&gt;The ingredients are simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your name.&lt;/li&gt;
&lt;li&gt;The word &amp;quot;obvious&amp;quot;.&lt;/li&gt;
&lt;li&gt;A good adjective.&lt;/li&gt;
&lt;li&gt;The kind of thing you&#39;ve made.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The canonical example, from which this scheme was reverse-engineered, is &lt;a href=&quot;https://en.wikipedia.org/wiki/TOML&quot;&gt;Tom&#39;s Obvious, Minimal Language&lt;/a&gt;.
People who haven&#39;t used TOML might think that &amp;quot;obvious&amp;quot; is functioning here to describe the language, but various attributes of TOML (&lt;a href=&quot;https://toml.io/en/v1.0.0#keys&quot;&gt;non-ASCII keys requiring quotes&lt;/a&gt;, &lt;a href=&quot;https://toml.io/en/v1.0.0#string&quot;&gt;substantial distinctions between single- and double-quoted strings&lt;/a&gt;, &lt;a href=&quot;https://toml.io/en/v1.0.0#array-of-tables&quot;&gt;array-of-tables syntax&lt;/a&gt;) indicate that &amp;quot;obvious&amp;quot; is merely part of the name template rather than an actual descriptor.&lt;/p&gt;
&lt;p&gt;As an example, we can construct a name for this approach to assigning names to things.
My name is &lt;em&gt;cactus&lt;/em&gt; (for some value of &amp;quot;name&amp;quot;, and for that matter &amp;quot;my&amp;quot;), it would be good if this was &lt;em&gt;intuitive&lt;/em&gt;, and this is a &lt;em&gt;naming scheme&lt;/em&gt;.
As such, the name this process assigns to itself is &amp;quot;Cactus&#39;s Obvious, Intuitive Naming Scheme&amp;quot;, which abbreviates nicely to &amp;quot;COINS&amp;quot;.&lt;/p&gt;
&lt;p&gt;We can also construct COINS names for other things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Linux_kernel&quot;&gt;Linus&#39;s Obvious, Free Kernel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/C%2B%2B&quot;&gt;Bjarne&#39;s Obvious, Powerful Language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/TeX&quot;&gt;Donald&#39;s Obvious, Expressive Language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/LaTeX&quot;&gt;Leslie&#39;s Obvious, Usable Macros&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Twitter&quot;&gt;Jack&#39;s Obvious, Nightmarish Hellsite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Vim_(text_editor)&quot;&gt;Bram&#39;s Obvious, Terse Editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Toki_Pona&quot;&gt;Sonja&#39;s Obvious, Minimal Language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/G%C3%B6del%27s_incompleteness_theorems&quot;&gt;Kurt&#39;s Obvious, Profound Theorem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Primer_(film)&quot;&gt;[Miku]&#39;s Obvious, Engaging Film&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Wikipedia&quot;&gt;Jimmy&#39;s Obvious, Collective Encyclopedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Computer&quot;&gt;Alan&#39;s Obvious, Revolutionary Mistake&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite the fact that Tom&#39;s Obvious, Minimal Language inspired this naming scheme, it actually suffers greatly from having used it.
If I ever get my hands on a time machine, I will go back and tell Tom to call it &amp;quot;My INI-Like Format&amp;quot; instead.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Can We Please Move Past Git?</title>
    <link href="https://www.boringcactus.com/2021/02/22/can-we-please-move-past-git.html" />
    <updated>2021-02-22T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2021/02/22/can-we-please-move-past-git.html</id>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Git is fundamentally a content-addressable filesystem with a VCS user interface written on top of it.&lt;/p&gt;
&lt;p&gt;— &lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain&quot;&gt;Pro Git §10.1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most software development is not like the Linux kernel&#39;s development; as such, Git is not designed for most software development.
Like Samuel Hayden tapping the forces of Hell itself to generate electricity, the foundations on which Git is built are overkill on the largest scale, and when the interface concealing all that complexity cracks, the nightmares which emerge can only be dealt with by the Doom Slayer that is &lt;a href=&quot;https://ohshitgit.com/&quot;&gt;Oh Shit Git&lt;/a&gt;.
Of course, the far more common error handling method is &lt;a href=&quot;https://xkcd.com/1597/&quot;&gt;start over from scratch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Git is bad.
But are version control systems like operating systems in that they&#39;re all various kinds of bad, or is an actually good VCS possible?
I don&#39;t know, but I can test some things and see what comes up.&lt;/p&gt;
&lt;h2 id=&quot;mercurial&quot; tabindex=&quot;-1&quot;&gt;Mercurial&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mercurial-scm.org/&quot;&gt;Mercurial&lt;/a&gt; is a distributed VCS that&#39;s around the same age as Git, and I&#39;ve seen it called the Betamax to Git&#39;s VHS, which my boomer friends tell me is an apt analogy, but I&#39;m too young for that to carry meaning.
So let me see what all the fuss is about.&lt;/p&gt;
&lt;p&gt;Well, I have some bad news.
From the &lt;a href=&quot;https://www.mercurial-scm.org/downloads&quot;&gt;download page&lt;/a&gt;, under &amp;quot;Requirements&amp;quot;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Mercurial uses Python (version &lt;strong&gt;2.7&lt;/strong&gt;). Most ready-to-run Mercurial distributions include Python or use the Python that comes with your operating system.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Emphasis theirs, but I&#39;d have added it myself otherwise.
Python 2 has been dead for a very long time now, and saying you require Python 2 makes me stop caring faster than referring to &amp;quot;GNU/Linux&amp;quot;.
If you&#39;ve updated it to Python 3, cool, don&#39;t say it uses Python 2.
Saying it uses Python 2 makes me think you don&#39;t have your shit together, and in fairness, that makes two of us, but I&#39;m not asking people to use my version control system (so far, at least).&lt;/p&gt;
&lt;p&gt;You can&#39;t be better than Git if you&#39;re that outdated. (Although you can totally be better than Git by developing a reputation for having a better UI than Git; word of mouth helps a lot.)&lt;/p&gt;
&lt;h2 id=&quot;subversion&quot; tabindex=&quot;-1&quot;&gt;Subversion&lt;/h2&gt;
&lt;p&gt;I am a fan of subverting things, and I have to respect wordplay.
So let&#39;s take a look at &lt;a href=&quot;https://subversion.apache.org/&quot;&gt;Subversion&lt;/a&gt; (sorry, &amp;quot;Apache® Subversion®&amp;quot;).&lt;/p&gt;
&lt;p&gt;There are no official binaries at all, and the most-plausible-looking blessed unofficial binary for Windows is TortoiseSVN.
I&#39;m looking through the &lt;a href=&quot;https://tortoisesvn.net/docs/release/TortoiseSVN_en/tsvn-repository.html#tsvn-repository-layout&quot;&gt;manual&lt;/a&gt;, and I must say, the fact that branches and tags aren&#39;t actually part of the VCS, but instead conventions on top of it, isn&#39;t good.
When I want to make a new branch, it&#39;s usually &amp;quot;I want to try an experiment, and I want to make it easy to give up on this experiment.&amp;quot;
Also, I&#39;m not married to the idea of distributed VCSes, but I do tend to start a project well before I&#39;ve set up server-side infrastructure for it, and Subversion is not designed for that sort of thing at all.
So I think I&#39;ll pass.&lt;/p&gt;
&lt;p&gt;You can&#39;t be better than Git if the server setup precedes the client setup when you&#39;re starting a new project.
(Although you can totally be better than Git by having monotonically-ish increasing revision numbers.)&lt;/p&gt;
&lt;h2 id=&quot;fossil&quot; tabindex=&quot;-1&quot;&gt;Fossil&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://fossil-scm.org/&quot;&gt;Fossil&lt;/a&gt; is kinda nifty: it handles not just code but also issue tracking, documentation authoring, and a bunch of the other things that services like GitHub staple on after the fact.
Where Git was designed for the Linux kernel, which has a fuckton of contributors and needs to scale absurdly widely, Fossil was designed for SQLite, which has a very small number of contributors and does not solicit patches.
My projects tend to only have one contributor, so this should in principle work fine for me.&lt;/p&gt;
&lt;p&gt;However, a few things about Fossil fail to spark joy.
The fact that repository metadata is stored as an independent file separate from the working directory, for example, is a design decision that doesn&#39;t merge well with my existing setup.
If I were to move my website into Fossil, I would need somewhere to put &lt;code&gt;boringcactus.com.fossil&lt;/code&gt; outside of &lt;code&gt;D:&#92;Melody&#92;Projects&#92;boringcactus.com&lt;/code&gt; where the working directory currently resides.
The &lt;a href=&quot;https://fossil-scm.org/home/doc/trunk/www/whyusefossil.wiki#definitions&quot;&gt;documentation&lt;/a&gt; suggests &lt;code&gt;~/Fossils&lt;/code&gt; as a folder in which repository metadata can be stored, but that makes my directory structure more ugly.
The rationale for doing it this way instead of having &lt;code&gt;.fossil&lt;/code&gt; in the working directory like &lt;code&gt;.git&lt;/code&gt; etc. is that multiple checkouts of the same repository are simpler when repository metadata is outside each of them.
Presumably the SQLite developers do that sort of thing a lot, but I don&#39;t, and I don&#39;t know anyone who does, and I&#39;ve only ever done it once (back in the days when the only way to use GitHub Pages was to make a separate &lt;code&gt;gh-pages&lt;/code&gt; branch).
Cluttering up my filesystem just so you can support a weird edge case that I don&#39;t need isn&#39;t a great pitch.&lt;/p&gt;
&lt;p&gt;But sure, let&#39;s check this out.
The &lt;a href=&quot;https://fossil-scm.org/home/doc/trunk/www/inout.wiki&quot;&gt;docs&lt;/a&gt; have instructions for importing a Git repo to Fossil, so let&#39;s follow them:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;PS D:&#92;Melody&#92;Projects&#92;boringcactus.com&gt; git fast-export --all | fossil import --git D:&#92;Melody&#92;Projects&#92;misc&#92;boringcactus.com.fossil
]ad fast-import line: [S IN THE&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, then.
You can&#39;t be better than Git if your instructions for importing from Git don&#39;t actually work.
(Although you can totally be better than Git if you can keep track of issues etc. alongside the code.)&lt;/p&gt;
&lt;h2 id=&quot;darcs&quot; tabindex=&quot;-1&quot;&gt;Darcs&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://darcs.net/&quot;&gt;Darcs&lt;/a&gt; is a distributed VCS that&#39;s a little different to Git etc.
Git etc. have the &lt;em&gt;commit&lt;/em&gt; as the fundamental unit on which all else is built, whereas Darcs has the &lt;em&gt;patch&lt;/em&gt; as its fundamental unit.
This means that a branch in Darcs refers to a set of patches, not a commit.
As such, Darcs can be more flexible with its history than Git can: a Git commit depends on its temporal ancestor (&amp;quot;parent&amp;quot;), whereas a Darcs patch depends only on its logical ancestor (e.g. creating a file before adding text to it).
This approach also improves the way that &lt;a href=&quot;https://tahoe-lafs.org/~zooko/badmerge/simple.html&quot;&gt;some types of merge&lt;/a&gt; are handled; I&#39;m not sure how often this sort of thing actually comes up, but the fact that it could is definitely suboptimal.&lt;/p&gt;
&lt;p&gt;So that&#39;s pretty cool; let&#39;s take a look for ourselves.
Oh.
Well, then.
The &lt;a href=&quot;http://darcs.net/Binaries&quot;&gt;download page&lt;/a&gt; is only served over plain HTTP - there&#39;s just nothing listening on that server over HTTPS - and the downloaded binaries are also served over plain HTTP.
That&#39;s not a good idea.
I&#39;ll pass, thanks.&lt;/p&gt;
&lt;p&gt;You can&#39;t be better than Git while serving binaries over plain HTTP.
(Although you can totally be better than Git by having nonlinear history and doing interesting things with patches.)&lt;/p&gt;
&lt;h2 id=&quot;pijul&quot; tabindex=&quot;-1&quot;&gt;Pijul&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://pijul.org/&quot;&gt;Pijul&lt;/a&gt; is (per &lt;a href=&quot;https://pijul.org/manual/why_pijul.html&quot;&gt;the manual&lt;/a&gt;)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;the first distributed version control system to be based on a sound mathematical theory of changes. It is inspired by Darcs, but aims at solving the soundness and performance issues of Darcs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Inspired by Darcs but better, you say?
You have my attention.
Also of note is that the developers are also building their own GitHub clone, which they use to &lt;a href=&quot;https://nest.pijul.com/pijul/pijul&quot;&gt;host pijul itself&lt;/a&gt;, which gives a really nice view of how a GitHub clone built on top of pijul would work, and also offers free hosting.&lt;/p&gt;
&lt;p&gt;The manual gives &lt;a href=&quot;https://pijul.org/manual/installing.html&quot;&gt;installation instructions&lt;/a&gt; for a couple Linuces and OS X, but not Windows, and not Alpine Linux, which is the only WSL distro I have installed.
However, someone involved in the project &lt;a href=&quot;https://twitter.com/nuempe/status/1359614145415548939&quot;&gt;showed up in my mentions to say that it works on Windows&lt;/a&gt;, so we&#39;ll just follow the generic instructions and see what happens:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;PS D:&#92;Melody&#92;Projects&amp;gt; cargo install pijul --version &quot;~1.0.0-alpha&quot;
    Updating crates.io index
  Installing pijul v1.0.0-alpha.38
  Downloaded &amp;lt;a bunch of stuff&amp;gt;
   Compiling &amp;lt;a bunch of stuff&amp;gt;
error: linking with `link.exe` failed: exit code: 1181
  |
  = note: &quot;C:&#92;&#92;Program Files (x86)&#92;&#92;Microsoft Visual Studio&#92;&#92;2019&#92;&#92;BuildTools&#92;&#92;VC&#92;&#92;Tools&#92;&#92;MSVC&#92;&#92;14.27.29110&#92;&#92;bin&#92;&#92;HostX64&#92;&#92;x64&#92;&#92;link.exe&quot; &amp;lt;lots of bullshit&amp;gt;
  = note: LINK : fatal error LNK1181: cannot open input file &#39;zstd.lib&#39;


error: aborting due to previous error&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So it doesn&#39;t work for me on Windows.
(There&#39;s a chance that instructions would help, but in the absence of those, I will simply give up.)
Let&#39;s try it over on Linux:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;UberPC-V3:~$ cargo install pijul --version &quot;~1.0.0-alpha&quot;
&amp;lt;lots of output&amp;gt;
error: linking with `cc` failed: exit code: 1
  |
  = note: &quot;cc&quot; &amp;lt;a mountain of arguments&amp;gt;
  = note: /usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: cannot find -lzstd
          /usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: cannot find -lxxhash
          collect2: error: ld returned 1 exit status


error: aborting due to previous error
UberPC-V3:~$ sudo apk add zstd-dev xxhash-dev
UberPC-V3:~$ cargo install pijul --version &quot;~1.0.0-alpha&quot;
&amp;lt;lots of output again because cargo install forgets dependencies immediately smdh&amp;gt;
   Installed package `pijul v1.0.0-alpha.38` (executable `pijul`)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh hey, would you look at that, it actually worked, and all I had to do was wait six months for each compile to finish (and make an educated guess about what packages to install).
So for the sake of giving back, let&#39;s add those instructions to the manual, so nobody else has to bang their head against the wall like I&#39;d done the past few times I tried to get Pijul working for myself.&lt;/p&gt;
&lt;p&gt;First, clone the &lt;a href=&quot;https://nest.pijul.com/pijul/manual&quot;&gt;repository for the manual&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;UberPC-V3:~$ pijul clone https://nest.pijul.com/pijul/manual
Segmentation fault&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh my god.
That&#39;s extremely funny.
Oh fuck that&#39;s &lt;em&gt;hilarious&lt;/em&gt; - I sent that to a friend and her reaction reminded me that Pijul is &lt;em&gt;written in Rust&lt;/em&gt;.
This VCS so profoundly doesn&#39;t work on my machine that it manages to segfault in a language that&#39;s supposed to make segfaults impossible.
Presumably the segfault came from C code FFId with &lt;code&gt;unsafe&lt;/code&gt; preconditions that weren&#39;t met, but still, that&#39;s just amazing.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update 2021-02-24&lt;/em&gt;: One of the Pijul authors reached out to me to help debug things.
Apparently &lt;a href=&quot;https://github.com/microsoft/WSL/issues/658&quot;&gt;&lt;code&gt;mmap&lt;/code&gt; on WSL is just broken&lt;/a&gt;, which explains the segfault.
They also pointed me towards &lt;a href=&quot;https://nest.pijul.com/pijul/pijul/discussions/140#57f9eead-915f-45c1-a169-a5bd417d9213&quot;&gt;the state of the art in getting Pijul to work on Windows&lt;/a&gt;, which I confirmed worked locally and then &lt;a href=&quot;https://github.com/boringcactus/pijul-windows-builds&quot;&gt;set up automated Windows builds using GitHub Actions&lt;/a&gt;.
So if we have a working Pijul install, let&#39;s see if we can add that CI setup to the manual:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;PS D:&#92;Melody&#92;Projects&#92;misc&amp;gt; pijul clone https://nest.pijul.com/pijul/manual pijul-manual
✓ Updating remote changelist
✓ Applying changes       47/47
✓ Downloading changes    47/47
✓ Outputting repository&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hey, that actually works!
We can throw in some text to the installation page (and more text to the getting started page) and then use &lt;code&gt;pijul record&lt;/code&gt; to commit our changes.
That pulls up &lt;em&gt;Notepad&lt;/em&gt; as the default text editor, which fails to spark joy, but that&#39;s a papercut that&#39;s entirely understandable for alpha software not primarily developed on this OS.
Instead of having &amp;quot;issues&amp;quot; and &amp;quot;pull requests&amp;quot; as two disjoint things, the Pijul Nest lets you add changes to any discussion, which I very much like.
Once we&#39;ve recorded our change and made a discussion on the repository, we can &lt;code&gt;pijul push boringcactus@nest.pijul.com:pijul/manual --to-channel :34&lt;/code&gt; and it&#39;ll attach the change we just made to &lt;a href=&quot;https://nest.pijul.com/pijul/manual/discussions/34&quot;&gt;discussion #34&lt;/a&gt;.
(It appears to be having trouble finding my SSH keys or persisting known SSH hosts, which means I have to re-accept the fingerprint and re-enter my Nest password every time, but that&#39;s not the end of the world.)&lt;/p&gt;
&lt;p&gt;So yeah, Pijul definitely still isn&#39;t production-ready, but it shows some real promise. That said, you can&#39;t be better than Git if you aren&#39;t production-ready.
(Although you can totally be better than Git by having your own officially-blessed GitHub clone sorted out already.)
(And maybe, with time, you can be eventually better than Git.)&lt;/p&gt;
&lt;h2 id=&quot;what-next%3F&quot; tabindex=&quot;-1&quot;&gt;what next?&lt;/h2&gt;
&lt;p&gt;None of the existing VCSes that I looked at were unreservedly better than Git, but they all had aspects that would help beat Git.&lt;/p&gt;
&lt;p&gt;A tool which is actually better than Git should start by being no worse than Git:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;allow importing existing Git repositories&lt;/li&gt;
&lt;li&gt;don&#39;t require Git users to relearn every single thing - we already had to learn Git, we&#39;ve been through enough&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, to pick and choose the best parts of other VCSes, it should&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;have a UI that&#39;s better, or at least perceived as better, than Git&#39;s - ideally minimalism and intuitiveness will get you there, but user testing is gonna be the main thing&lt;/li&gt;
&lt;li&gt;avoid opaque hashes as the primary identifier for things - &lt;code&gt;r62&lt;/code&gt; carries more meaning than &lt;code&gt;7c7bb33&lt;/code&gt; - but not at the expense of features that are actually important&lt;/li&gt;
&lt;li&gt;go beyond just source code, and cover issues, documentation wikis, and similar items, so that (for at least the easy cases) the entire state of the project is contained within version control&lt;/li&gt;
&lt;li&gt;approach history as not just a linear sequence of facts but a &lt;em&gt;story&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;offer hosting to other developers who want to use your VCS, so they don&#39;t have to figure that out themselves to get started in a robust way&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And just for kicks, a couple of extra features that nobody has but everybody should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the CLI takes a back seat to the GUI (or TUI, I guess) - seeing the state gets easier that way, discovering features gets easier that way, teaching to people who aren&#39;t CLI-literate gets easier that way&lt;/li&gt;
&lt;li&gt;contributor names &amp;amp; emails aren&#39;t immutable - trans people exist, and &lt;code&gt;git filter-graph&lt;/code&gt; makes it about as difficult to change my name as the state of Colorado did&lt;/li&gt;
&lt;li&gt;if you build in issue/wiki/whatever tracking, also build in CI in some way&lt;/li&gt;
&lt;li&gt;avoid internal jargon - either say things in plain $LANG or develop a consistent and intuitive metaphor and use it literally everywhere&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I probably don&#39;t have the skills, and I certainly don&#39;t have the free time, to build an Actually Good VCS myself.
But if you want to, here&#39;s what you&#39;re aiming for.
Good luck.
If you can pull it off, you&#39;ll be a hero.
And if you can&#39;t, you&#39;ll be in good company.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Every President Sucked</title>
    <link href="https://www.boringcactus.com/2021/02/15/every-president-sucked.html" />
    <updated>2021-02-15T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2021/02/15/every-president-sucked.html</id>
    <content type="html">&lt;p&gt;(Not exhaustive; based on cursory readings of parts of &lt;a href=&quot;https://en.wikipedia.org/wiki/Category:Presidencies_of_the_United_States&quot;&gt;Wikipedia articles&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Washington thought it was morally acceptable to own human beings as property.&lt;/p&gt;
&lt;p&gt;Adams signed the &lt;a href=&quot;https://en.wikipedia.org/wiki/Alien_and_Sedition_Acts&quot;&gt;Alien and Sedition Acts&lt;/a&gt;, which were xenophobic and authoritarian.&lt;/p&gt;
&lt;p&gt;Jefferson thought it was morally acceptable to own human beings as property.&lt;/p&gt;
&lt;p&gt;Madison thought it was morally acceptable to own human beings as property.&lt;/p&gt;
&lt;p&gt;Monroe thought it was morally acceptable to own human beings as property.&lt;/p&gt;
&lt;p&gt;Quincy Adams thought partisanship could be solved by appointing his enemies to office (but aside from that he seems pretty decent).&lt;/p&gt;
&lt;p&gt;Jackson thought it was morally acceptable to own human beings as property, and was extremely enthusiastic about Native American genocide.&lt;/p&gt;
&lt;p&gt;Van Buren thought it was morally acceptable to own human beings as property.&lt;/p&gt;
&lt;p&gt;Harrison thought it was morally acceptable to own human beings as property.&lt;/p&gt;
&lt;p&gt;Tyler thought it was morally acceptable to own human beings as property.&lt;/p&gt;
&lt;p&gt;Polk thought it was morally acceptable to own human beings as property.&lt;/p&gt;
&lt;p&gt;Taylor thought it was morally acceptable to own human beings as property.&lt;/p&gt;
&lt;p&gt;Fillmore signed the Compromise of 1850, which had numerous elements which propagated slavery, and wound up attached to the Know-Nothing Party.&lt;/p&gt;
&lt;p&gt;Pierce supported the illegitimate pro-slavery government of the Kansas Territory.&lt;/p&gt;
&lt;p&gt;Buchanan encouraged the Supreme Court&#39;s decision in &lt;em&gt;Dred Scott v Sandford&lt;/em&gt; that the Constitution does not apply to Black people.&lt;/p&gt;
&lt;p&gt;Lincoln&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ran on a platform of allowing slavery to continue in the states where it already existed&lt;/li&gt;
&lt;li&gt;reversed Frémont&#39;s emancipation order in Missouri in the early months of the Civil War&lt;/li&gt;
&lt;li&gt;rejected the Radical Republicans&#39; pressure to immediately and uncompromisingly abolish slavery&lt;/li&gt;
&lt;li&gt;suppressed a Native American uprising&lt;/li&gt;
&lt;li&gt;was generally a moderate on issues where moderation is unacceptable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Johnson&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;opposed federal protections for voting rights of former slaves&lt;/li&gt;
&lt;li&gt;undermined the work of the Freedmen&#39;s Bureau&lt;/li&gt;
&lt;li&gt;vetoed civil rights protections&lt;/li&gt;
&lt;li&gt;generally did everything he could to fuck up Reconstruction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Grant&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gave amnesty to former Confederates that allowed them to hold government office&lt;/li&gt;
&lt;li&gt;signed the Comstock Act which suppressed both educational and erotic speech about sex&lt;/li&gt;
&lt;li&gt;appointed zero Native Americans to the Board of Indian Commissioners&lt;/li&gt;
&lt;li&gt;approved of the erasure of Native culture&lt;/li&gt;
&lt;li&gt;started treating Native tribes as subordinate to the US instead of independent and sovereign&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hayes&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sent the military in to suppress strikes&lt;/li&gt;
&lt;li&gt;continued Grant&#39;s cultural assimilation of Native Americans&lt;/li&gt;
&lt;li&gt;opened the door for immigration restrictions based on country of origin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Garfield&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;felt gross about racial political equality&lt;/li&gt;
&lt;li&gt;fucking died lmfao get rekt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Arthur&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;restricted immigration in some racist &amp;amp; ableist ways&lt;/li&gt;
&lt;li&gt;gave us the &amp;quot;public charge rule&amp;quot; that&#39;s used to deny immigration rights to people who might rely on welfare&lt;/li&gt;
&lt;li&gt;strengthened federal territory polygamy bans (fuck the Mormons, but like. my polyamorous ass would prefer to have my options open)&lt;/li&gt;
&lt;li&gt;opened up Native American reservations to settlers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cleveland&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fucked up the linear timeline of presidents&lt;/li&gt;
&lt;li&gt;vetoed a bunch of welfare&lt;/li&gt;
&lt;li&gt;didn&#39;t enforce civil rights protections&lt;/li&gt;
&lt;li&gt;fucked over Chinese immigrants&lt;/li&gt;
&lt;li&gt;approached relationship with Native Americans as guardianship, not diplomacy&lt;/li&gt;
&lt;li&gt;sent in the military to break up a railroad strike, killing 30 people&lt;/li&gt;
&lt;li&gt;approved of the Supreme Court&#39;s &amp;quot;separate but equal&amp;quot; decision in Plessy v Ferguson&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Harrison&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;didn&#39;t do anything about the Wounded Knee Massacre&lt;/li&gt;
&lt;li&gt;opened more Native American land to settlers&lt;/li&gt;
&lt;li&gt;tried to annex Hawaii, backed a coup against the queen, recognized the illegitimate government&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;McKinley&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;didn&#39;t bother enforcing antitrust laws&lt;/li&gt;
&lt;li&gt;didn&#39;t bother opposing Jim Crow laws&lt;/li&gt;
&lt;li&gt;finished the annexation of Hawaii&lt;/li&gt;
&lt;li&gt;fucked everything up after kicking Spain&#39;s ass in the Spanish-American War:
&lt;ul&gt;
&lt;li&gt;ignored the independent government in Cuba, set up a military government instead&lt;/li&gt;
&lt;li&gt;went to war with the independent government in the Philippines, sent Taft to take over&lt;/li&gt;
&lt;li&gt;set up military governments in Puerto Rico and Guam, left them with &amp;quot;unincorporated territory&amp;quot; status so people who live there don&#39;t have full constitutional rights&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;generally an imperialist dipshit&lt;/li&gt;
&lt;li&gt;lmao Czołgosz got his ass, pour one out for a real one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Roosevelt&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;didn&#39;t really do much good for civil rights&lt;/li&gt;
&lt;li&gt;more imperialism&lt;/li&gt;
&lt;li&gt;shenanigans around the construction of the Panama Canal&lt;/li&gt;
&lt;li&gt;more racist immigration bullshit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Taft&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;caved to racists demanding positions be filled with white people&lt;/li&gt;
&lt;li&gt;more racist immigration bullshit&lt;/li&gt;
&lt;li&gt;fuckery in Nicaragua&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wilson&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lots of racism&lt;/li&gt;
&lt;li&gt;drafting people for WW1&lt;/li&gt;
&lt;li&gt;oppressing dissent against WW1, including fucking over Eugene V. Debs&lt;/li&gt;
&lt;li&gt;did i mention the racism&lt;/li&gt;
&lt;li&gt;the dude literally supported segregation&lt;/li&gt;
&lt;li&gt;like. he told Black military members &amp;quot;segregation is not a humiliation but a benefit, and ought to be so regarded by you gentlemen.&amp;quot;&lt;/li&gt;
&lt;li&gt;constantly intervening in Latin American governments&lt;/li&gt;
&lt;li&gt;sent troops to fuck with the Bolsheviks mid-Revolution&lt;/li&gt;
&lt;li&gt;didn&#39;t recognize the Russian Soviet Republic&lt;/li&gt;
&lt;li&gt;and also the racism&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Harding&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bullshit immigration restrictions&lt;/li&gt;
&lt;li&gt;opposed federal unemployment benefits&lt;/li&gt;
&lt;li&gt;opposed racial integration socially&lt;/li&gt;
&lt;li&gt;kept Wilson&#39;s segregation&lt;/li&gt;
&lt;li&gt;rolled back antitrust stuff&lt;/li&gt;
&lt;li&gt;lots of corruption and scandal&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Coolidge&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;more fuckin immigration restrictions&lt;/li&gt;
&lt;li&gt;avoiding antitrust enforcement&lt;/li&gt;
&lt;li&gt;proto-supply-side trickle-down tax cuts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hoover&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;enforced Prohibition&lt;/li&gt;
&lt;li&gt;fucked up response to the Great Depression&lt;/li&gt;
&lt;li&gt;removed Black leaders from the Republican party to court racists in the South&lt;/li&gt;
&lt;li&gt;more goddamn immigration bullshit! kicking out Mexican-American citizens! ethnic cleansing!&lt;/li&gt;
&lt;li&gt;sent the Army in to fuck up some veterans who were protesting for prompt payment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Roosevelt&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Japanese internment&lt;/li&gt;
&lt;li&gt;immigration fuckery&lt;/li&gt;
&lt;li&gt;not accepting Jewish refugees&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Truman&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nuking Japan&lt;/li&gt;
&lt;li&gt;purged communist sympathizers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eisenhower&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CIA coups in Iran and Guatemala&lt;/li&gt;
&lt;li&gt;pretended Castro didn&#39;t exist&lt;/li&gt;
&lt;li&gt;mass deportations&lt;/li&gt;
&lt;li&gt;didn&#39;t stop McCarthy&lt;/li&gt;
&lt;li&gt;started COINTELPRO&lt;/li&gt;
&lt;li&gt;wasn&#39;t really on board with desegregation&lt;/li&gt;
&lt;li&gt;institutional homophobia&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kennedy&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;invasion of Vietnam, with all the attending war crimes and bullshit&lt;/li&gt;
&lt;li&gt;Bay of Pigs Invasion&lt;/li&gt;
&lt;li&gt;CIA fuckery all over the place in opposition to leftism&lt;/li&gt;
&lt;li&gt;military alliance with a post-Suez-Crisis expansionist Israel&lt;/li&gt;
&lt;li&gt;took a while to start proactively protecting civil rights&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Johnson&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;expansion of Vietnam War&lt;/li&gt;
&lt;li&gt;spying on anti-war movement&lt;/li&gt;
&lt;li&gt;fuckery in Dominican Republic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nixon&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;i mean. c&#39;mon. he&#39;s mainly remembered for Watergate, the scandal so big we name all our other scandals after it.&lt;/li&gt;
&lt;li&gt;opposed to forced integration&lt;/li&gt;
&lt;li&gt;prosecuted the Chicago Seven&lt;/li&gt;
&lt;li&gt;suppressed the Pentagon Papers&lt;/li&gt;
&lt;li&gt;let Kissinger do heinous Kissinger shit in Cambodia&lt;/li&gt;
&lt;li&gt;fucked over Allende, supported Pinochet&#39;s bullshit oppressive regime&lt;/li&gt;
&lt;li&gt;didn&#39;t end the Vietnam War&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ford&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pardoned Nixon&lt;/li&gt;
&lt;li&gt;fucked over Vietnam, presumably butthurt that the US lost&lt;/li&gt;
&lt;li&gt;kept Kissinger around&lt;/li&gt;
&lt;li&gt;supplied Indonesia for its invasion of East Timor and fuckery therein&lt;/li&gt;
&lt;li&gt;didn&#39;t die&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Carter&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;opposed universal health care&lt;/li&gt;
&lt;li&gt;continued support for oppressive regimes in Saudi Arabia, Iran, etc&lt;/li&gt;
&lt;li&gt;sheltered the Shah of Iran from justice&lt;/li&gt;
&lt;li&gt;fucked up the rescue mission in the Iran hostage crisis&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reagan&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;racism&lt;/li&gt;
&lt;li&gt;supply-side economics&lt;/li&gt;
&lt;li&gt;anti-communism&lt;/li&gt;
&lt;li&gt;put Scalia on the supreme court&lt;/li&gt;
&lt;li&gt;recovered from an assassination attempt&lt;/li&gt;
&lt;li&gt;broke the air traffic controller strike&lt;/li&gt;
&lt;li&gt;heightened the war on drugs&lt;/li&gt;
&lt;li&gt;didn&#39;t take AIDS or gay rights seriously&lt;/li&gt;
&lt;li&gt;propped up oppressive regimes in Latin America&lt;/li&gt;
&lt;li&gt;Iran-Contra&lt;/li&gt;
&lt;li&gt;allied with apartheid South Africa&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bush&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;didn&#39;t retract nomination of Clarence Thomas after Anita Hill spoke out&lt;/li&gt;
&lt;li&gt;invaded Panama to overthrow the CIA stooge who had previously been in charge&lt;/li&gt;
&lt;li&gt;sent Kissinger to negotiate with China&lt;/li&gt;
&lt;li&gt;tried to make it illegal to burn the flag&lt;/li&gt;
&lt;li&gt;pardoned the Iran-Contra people&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Clinton&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;welfare reform (the bad kind)&lt;/li&gt;
&lt;li&gt;deregulated banking, energy, and telecom&lt;/li&gt;
&lt;li&gt;settled for &amp;quot;don&#39;t ask don&#39;t tell&amp;quot;&lt;/li&gt;
&lt;li&gt;signed DOMA&lt;/li&gt;
&lt;li&gt;extended copyright&lt;/li&gt;
&lt;li&gt;workplace sexual misconduct&lt;/li&gt;
&lt;li&gt;mass incarceration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bush&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lied about WMDs in Iraq&lt;/li&gt;
&lt;li&gt;&amp;quot;war on terror&amp;quot;&lt;/li&gt;
&lt;li&gt;Patriot Act&lt;/li&gt;
&lt;li&gt;Abu Ghraib&lt;/li&gt;
&lt;li&gt;Gitmo&lt;/li&gt;
&lt;li&gt;blamed Israeli-Palestinian tension on the Palestinians&lt;/li&gt;
&lt;li&gt;fucked up the response to Hurricane Katrina&lt;/li&gt;
&lt;li&gt;doesn&#39;t care about black people&lt;/li&gt;
&lt;li&gt;tried to privatize Social Security&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obama&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;frequent use of sanctions as warfare&lt;/li&gt;
&lt;li&gt;let the feds do a bunch of evil shit in Ferguson etc&lt;/li&gt;
&lt;li&gt;did not withdraw from Iraq and Afghanistan&lt;/li&gt;
&lt;li&gt;prevented UN from calling for a Palestinian state&lt;/li&gt;
&lt;li&gt;increased aid to Israel&lt;/li&gt;
&lt;li&gt;didn&#39;t close Gitmo&lt;/li&gt;
&lt;li&gt;extensive drone strikes, lots of civilian casualties, blew up a damn hospital, extrajudicially murdered a US citizen on purpose&lt;/li&gt;
&lt;li&gt;continued supporting Saudi Arabia despite fuckin heinous shit&lt;/li&gt;
&lt;li&gt;mass surveillance, defense of PRISM, extension of the Patriot Act&lt;/li&gt;
&lt;li&gt;war on whistleblowers (Manning and Snowden should both have been pardoned fuckin instantly)&lt;/li&gt;
&lt;li&gt;fucktons of deportation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Trump&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bro do i even gotta list this shit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Biden, for the like month he&#39;s been in there so far&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;backed down from $2000 fuckin instantly&lt;/li&gt;
&lt;li&gt;appointed a bunch of liberal centrist dipshits in his cabinet&lt;/li&gt;
&lt;li&gt;he&#39;s a goddamn rapist&lt;/li&gt;
&lt;li&gt;let Fauci keep his job after lying to the public about COVID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;happy (slightly late) presidents&#39; day!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Not &quot;Any Purpose&quot;</title>
    <link href="https://www.boringcactus.com/2021/02/11/not-any-purpose.html" />
    <updated>2021-02-11T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2021/02/11/not-any-purpose.html</id>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;A program is free software if the program&#39;s users have the four essential freedoms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The freedom to run the program as you wish, &lt;strong&gt;for any purpose&lt;/strong&gt; (freedom 0).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;– &lt;a href=&quot;https://www.gnu.org/philosophy/free-sw.html&quot;&gt;the Free Software Definition&lt;/a&gt;, emphasis mine&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Goddamn, what the fuck?&lt;br&gt;
What the fuck, what the fuck?&lt;br&gt;
Goddamn, what the fuck?&lt;br&gt;
What the fuck?&lt;br&gt;
Goddamn, what the fuck&lt;br&gt;
What the fuck, what the—&lt;br&gt;
What the fuck? What the fuck? What the fuck?&lt;/p&gt;
&lt;p&gt;– &lt;a href=&quot;https://youtu.be/O1kTTOdHgC4&quot;&gt;745 sticky (Black Dresses Remix)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My previous post on &lt;a href=&quot;https://www.boringcactus.com/2020/08/13/post-open-source.html&quot;&gt;post-open source&lt;/a&gt; appeared to resonate with a lot of people, but unsurprisingly, not much has changed since then.
Recently, the company Elastic, who makes the database Elasticsearch, changed the license on Elasticsearch from Apache to the Server-Side Public License, which (to oversimplify &lt;a href=&quot;https://writing.kemitchell.com/2019/06/13/SSPL-Not-Commons-Clause.html&quot;&gt;the analysis of the excellent Kyle E. Mitchell&lt;/a&gt;) is AGPL but even more so.
And unsurprisingly, as happened with Redis and MongoDB before it, everyone got mad.
When I wrote this snippet of my post-open source post, I was referring to Redis, but it&#39;s the exact same playbook that&#39;s happening again now:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;you made a cool database server that&#39;s under an open source license?
amazon&#39;s selling it as a service now, and they&#39;re not paying you a fuckin dime.
you want to change your license to stop them from doing that?
now the open source people are yelling at you, because when they say they&#39;re apolitical they mean they support the status quo.
and the free software people are also yelling at you, because you didn&#39;t do it their way with their license, you did it a different way with a different license, and that goes against amazon&#39;s freedom to screw you over.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So we&#39;re back here again.
Open source ideology leads to intra-corporate exploitation, the exploitees want to get out from under the exploiters, everyone gets mad that the status quo has been upset, and Amazon forks the last permissively-licensed version of the software and laughs all the way to the bank.
The only way for Elastic to win would&#39;ve been to have licensed Elasticsearch under something restrictive like the SSPL from day one.&lt;/p&gt;
&lt;p&gt;But since they didn&#39;t, we get shit like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Capitalism concerns itself with making monopolies — FOSS instead concerns itself with the socialized creation of software wealth.&lt;/p&gt;
&lt;p&gt;– some jackass who loves being wrong on the internet&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;FOSS is not socialist. The free software movement is &lt;a href=&quot;https://lipu.dgold.eu/original-sin.html&quot;&gt;right-libertarian / &amp;quot;anarcho&amp;quot;-capitalist&lt;/a&gt;, and the open source movement is neoliberal; neither of these is even particularly close to socialism.
If you want a software ideology that is actually opposed to capitalism, instead of sucking up to capital, you might be looking for &lt;a href=&quot;https://anticapitalist.software/&quot;&gt;the Anti-Capitalist Software License&lt;/a&gt;.
It&#39;s good, it links to my previous writing on post-open source (and the Fuck Around and Find Out License), and the FSF hate it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Anti-Capitalist Software License is a nonfree license because it extends the four freedoms only to some kinds of organizations, not to all.
Such a restriction in a software license, in the name of any cause whatsoever, imposes too much power over users.
Please don&#39;t use this license, and we urge you to avoid any software that has been released under it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What&#39;s not to love!
As an author of some software released under the Anti-Capitalist Software License, I also urge people who take the FSF seriously to avoid my software which has been released under the ACSL.
And my software released under any other license.
And just me in general.&lt;/p&gt;
&lt;p&gt;Unsurprisingly, Richard M. Stallman (the &amp;quot;M&amp;quot; stands for &amp;quot;Making female students extremely uncomfortable &lt;a href=&quot;https://selamjie.medium.com/remove-richard-stallman-appendix-a-a7e41e784f88&quot;&gt;for 30 years&lt;/a&gt;&amp;quot;) has done some handwringing about the vital importance of Freedom 0&#39;s &amp;quot;for any purpose&amp;quot; that we can make fun of.
Essentially, his points are&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;enforcement against governments or big corporations is impossible because they can just make excuses and dodge jurisdiction and accountability&lt;/li&gt;
&lt;li&gt;for small stuff, ethics vary, so if we accept a thousand orthogonal usage restrictions (&amp;quot;only this music genre&amp;quot;, &amp;quot;can&#39;t be used in meat processing because i&#39;m vegan&amp;quot;, etc) we&#39;ll have a shitshow of an ecosystem and everyone will just give up and go back to using &amp;quot;nonfree&amp;quot; software&lt;/li&gt;
&lt;li&gt;who are we as developers to even tell users what they can and can&#39;t do with our software? you wouldn&#39;t buy a pen that had a &amp;quot;you can&#39;t use this pen to do X&amp;quot; on it, right? that&#39;d be ridiculous!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, lmao, &amp;quot;don&#39;t bother doing this because enforcement is impossible&amp;quot; applies equally to copyleft and the GPL in general.
Second, if we set aside masturbatory trolling bullshit like Bruce Perens&#39;s &amp;quot;Vaccine License&amp;quot;, nobody makes licenses that are that narrowly tailored to one specific thing - and even the Vaccine License is still a world away from a hypothetical Country Music Only License or Vegan Software License or what have you.
This is a textbook slippery slope argument, and if you think &amp;quot;you can&#39;t use this software to violate human rights&amp;quot; is anywhere near &amp;quot;you can&#39;t use this software to listen to BTS&amp;quot; then fuck all of the way off.
(Although, one time I mentioned on IRC that I was throwing a simple script together in Python, and someone said &amp;quot;Hopefully it&#39;s Python 2, but if not I&#39;ll just rewrite it in Python 2&amp;quot;, so I threw together the &lt;a href=&quot;https://git.sr.ht/~boringcactus/gotbruh/tree/main/item/LICENSE.md&quot;&gt;Why The Fuck Would You Even Do That Holy Shit Public License&lt;/a&gt; to prohibit exactly that.)
Third, god &lt;em&gt;damn&lt;/em&gt; libertarians hate when anyone tells them that some actions are bad.
If I was going to buy a pen, and I saw that it had a &amp;quot;hey, don&#39;t use this pen to violate human rights&amp;quot; sticker on it, I would go &amp;quot;fair enough I guess&amp;quot; and just buy the damn pen.&lt;/p&gt;
&lt;p&gt;The libertarian position here is that toolmakers should have no say in how those tools are used, and the only way they should control what can be done with their tools is by directly adding or removing capabilities from the tools.
Unsurprisingly, the libertarian position here is bullshit.
A tool is not a morally neutral thing, and toolsmithing is not an act which must by necessity end when the tool leaves the smith&#39;s hands.&lt;/p&gt;
&lt;p&gt;Let me now list some of the ethical principles from the &lt;a href=&quot;https://ethics.acm.org&quot;&gt;ACM Code of Ethics&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A computing professional should...&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Contribute to society and to human well-being, acknowledging that all people are stakeholders in computing.&lt;/li&gt;
&lt;li&gt;Avoid harm.&lt;/li&gt;
&lt;li&gt;Be honest and trustworthy.&lt;/li&gt;
&lt;li&gt;Be fair and take action not to discriminate.&lt;/li&gt;
&lt;li&gt;Respect the work required to produce new ideas, inventions, creative works, and computing artifacts.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Points 1 and 2 are broadly aligned with the idea that software should be used for good, and point 5 suggests that authors should be compensated for their work.
As such, ethical restrictions in software licenses are perfectly compatible with this code of ethics, as are restrictions on commercial use.
It is possible to go even further than this, and I think I will, because I&#39;m in the mood to start some fights:&lt;/p&gt;
&lt;p&gt;In my view, &lt;strong&gt;it conflicts with the ACM Code of Ethics to release software under a license which permits ethical misconduct and economic exploitation.&lt;/strong&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Crowbar: Turns out, language development is hard</title>
    <link href="https://www.boringcactus.com/2020/10/19/crowbar-3-this-is-tough.html" />
    <updated>2020-10-19T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2020/10/19/crowbar-3-this-is-tough.html</id>
    <content type="html">&lt;p&gt;(Previously in Crowbar: &lt;a href=&quot;https://www.boringcactus.com/2020/09/28/crowbar-1-defining-a-c-replacement.html&quot;&gt;Defining a good C replacement&lt;/a&gt;, &lt;a href=&quot;https://www.boringcactus.com/2020/10/13/crowbar-2-simplifying-c-type-names.html&quot;&gt;Simplifying C&#39;s type names&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Originally, I hadn&#39;t decided whether Crowbar should be designed with an eye towards compiling to C or with an eye towards compiling directly.
Compiling to C massively cuts down the scope of Crowbar as a project, but compiling directly gives me more comprehensive control over what all happens.&lt;/p&gt;
&lt;p&gt;I figured I wouldn&#39;t need comprehensive control over everything, so I chose compiling to C, and then almost immediately ran into a pile of issues that compiling to C brings with it.&lt;/p&gt;
&lt;h2 id=&quot;libc-is-part-of-the-problem&quot; tabindex=&quot;-1&quot;&gt;libc is part of the problem&lt;/h2&gt;
&lt;p&gt;One of the goals I had for Crowbar was memory safety - most of the footguns in C are of the dubious-memory-operations variety - but it turns out you can&#39;t just duct tape memory safety to an existing language and call it a day.
Among the most easily-exploited class of dubious memory operations is the buffer overflow, and the most straightforward fix is bounds checking.&lt;/p&gt;
&lt;p&gt;However, most of the C standard library doesn&#39;t perform bounds checking, because the C standard library was designed in the 1600s when every CPU cycle took six months and compiler optimizations hadn&#39;t been invented yet.&lt;/p&gt;
&lt;p&gt;The C11 specification actually defines bounds-checking-performing alternatives to some of the standard library APIs, but it&#39;s optional (filed away in Annex K) and fuckin nobody can be bothered to implement it.
Some of the spec authors &lt;a href=&quot;http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1969.htm&quot;&gt;wrote a whole investigation&lt;/a&gt; of why nobody uses Annex K, and it all boils down to &amp;quot;error handling in C is broken-by-default&amp;quot; - which is the other big issue Crowbar will have to solve, and I haven&#39;t even begun to think about how to do that.&lt;/p&gt;
&lt;p&gt;But.
We live in a world where C already exists in all its mixed glory, and in that world, nothing supports Annex K.
There exist some tacked-on implementations, but the &lt;a href=&quot;https://github.com/sbaresearch/slibc&quot;&gt;most complete-looking one&lt;/a&gt; is licensed under the regular GPL, not even the LGPL, which would be a problem for a lot of software.
So if I want Crowbar to be designed to compile into C, I need to either reimplement Annex K my damn self or design some equivalent APIs and implement those.&lt;/p&gt;
&lt;p&gt;In either case, now Crowbar has a runtime library on top of libc, and nobody&#39;s going to already have it so it&#39;ll have to figure out how to ensure that its runtime library is available wherever needed.
And that&#39;s a pain in the ass.&lt;/p&gt;
&lt;h2 id=&quot;you-can&#39;t-win&quot; tabindex=&quot;-1&quot;&gt;you can&#39;t win&lt;/h2&gt;
&lt;p&gt;Okay, so if compiling to C would still require a runtime library, and ensuring that the runtime library still worked in mixed-Crowbar-and-C and fully-Crowbar projects without requiring system-wide installation or anything would be a nuisance, why not just simplify some things and skip over C?&lt;/p&gt;
&lt;p&gt;Well, for an ordinary language, that&#39;d work rather well, which is why fucking nothing uses C as a compile target.
But one of the nonnegotiable goals of Crowbar is to have low- or no-effort C interoperability.
As such, you need to be able to include regular C headers.
But loading and parsing regular C headers means the Crowbar compiler needs to be able to understand all of C, and either implement or shell out to a C preprocessor that can use conventional command line arguments to define include directories.
And if the compiler has to encompass all of the complexity of C, then Crowbar just went from a subset of C to a superset of C.
I do not want to write a C compiler.
If Crowbar is supposed to be simpler than C, then writing a Crowbar compiler should be simpler than writing a C compiler, not more complicated.&lt;/p&gt;
&lt;p&gt;So compiling to C creates problems, and compiling not-to-C creates problems.
This sucks.
Send help.&lt;/p&gt;
&lt;p&gt;No, seriously, if you have advice, even if it&#39;s just &amp;quot;well I&#39;d probably do this because it seems more intuitive to me&amp;quot;, &lt;a href=&quot;mailto:~boringcactus/crowbar-lang-devel@lists.sr.ht&quot;&gt;send it in&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Crowbar: Simplifying C&#39;s type names</title>
    <link href="https://www.boringcactus.com/2020/10/13/crowbar-2-simplifying-c-type-names.html" />
    <updated>2020-10-13T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2020/10/13/crowbar-2-simplifying-c-type-names.html</id>
    <content type="html">&lt;p&gt;(Previously in Crowbar: &lt;a href=&quot;https://www.boringcactus.com/2020/09/28/crowbar-1-defining-a-c-replacement.html&quot;&gt;Defining a good C replacement&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;I&#39;ve been working intermittently on drawing up a specification for &lt;a href=&quot;https://sr.ht/~boringcactus/crowbar-lang/&quot;&gt;Crowbar&lt;/a&gt;, a C replacement aiming to be both simpler and safer.
I&#39;m still nowhere near done, but I&#39;m proud of the concept I&#39;ve reached for type names, and I want to explain it in depth here.&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot; tabindex=&quot;-1&quot;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;C declarations are known to be a nuisance in nontrivial cases.
There&#39;s a mid-90s &lt;a href=&quot;http://c-faq.com/decl/spiral.anderson.html&quot;&gt;&amp;quot;clockwise/spiral rule&amp;quot;&lt;/a&gt; that I&#39;ve seen referenced a few times, but three steps are two too many for reading a declaration.
Function pointers in particular have a reputation for being legendarily impossible to visually parse.
I don&#39;t know what &lt;code&gt;void (*signal(int, void (*fp)(int)))(int);&lt;/code&gt; is declaring, but it&#39;s the most complicated example listed on the spiral rule page, and I&#39;m pretty sure just pasting it into this blog post has already summoned some eldritch abomination.&lt;/p&gt;
&lt;h2 id=&quot;a-solution&quot; tabindex=&quot;-1&quot;&gt;A Solution&lt;/h2&gt;
&lt;p&gt;So we have this syntax which is well-established, and for simple cases well-understood, but in complex cases quickly becomes unmanageable.
Ideally, we can preserve the syntax as is for simple cases, while cutting down on that complexity in the more difficult cases.&lt;/p&gt;
&lt;p&gt;As of right now, the Crowbar specification gives the syntax as a &lt;a href=&quot;https://en.wikipedia.org/wiki/Parsing_expression_grammar&quot;&gt;parsing expression grammar&lt;/a&gt;, which I&#39;ll give an excerpt from here:&lt;/p&gt;
&lt;pre class=&quot;language-peg&quot;&gt;&lt;code class=&quot;language-peg&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Type&lt;/span&gt;      &lt;span class=&quot;token operator&quot;&gt;←&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;const&#39;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BasicType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;BasicType&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;*&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;BasicType&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;[&#39;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Expression&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;]&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;BasicType&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;function&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;(&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;BasicType&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;,&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;)&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;BasicType&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;BasicType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;←&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;void&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&#39;int&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&#39;float&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&#39;bool&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&#39;(&#39;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Type&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;)&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So essentially, basic types can be used as-is, and pointers-to or arrays-of those basic types require no additional syntax.
But if you want to do something nontrivial, you&#39;ll need to parenthesize the inner type.&lt;/p&gt;
&lt;p&gt;I didn&#39;t think this would wind up being quite as elegant as it turned out to be, but it handles a lot of edge cases gracefully and intuitively.&lt;/p&gt;
&lt;h2 id=&quot;in-motion&quot; tabindex=&quot;-1&quot;&gt;In Motion&lt;/h2&gt;
&lt;p&gt;I&#39;ll just lift some examples straight from the Spiral Rule page.&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;str&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Evidently this means &amp;quot;str is an array 10 of pointers to char&amp;quot;.
How would we express that in Crowbar (as it hypothetically exists so far)?&lt;/p&gt;
&lt;pre class=&quot;language-crowbar&quot;&gt;&lt;code class=&quot;language-crowbar&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that&#39;s more like it.
We can look at it and tell right away that the array is the outermost piece and so &lt;code&gt;str&lt;/code&gt; is an array.
In C, I&#39;m not sure how we&#39;d express a pointer-to-arrays-of-10-chars, but in Crowbar it&#39;s also straightforward:&lt;/p&gt;
&lt;pre class=&quot;language-crowbar&quot;&gt;&lt;code class=&quot;language-crowbar&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let&#39;s kick it up a notch, and look at those legendarily-awful aspects of C&#39;s syntax, function pointers.
The Spiral Rule offers up&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;fp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which supposedly means &amp;quot;fp is a pointer to a function passing an int and a pointer to float returning a pointer to a char&amp;quot;.
That&#39;s not extremely dreadful, merely somewhat off-putting, but let&#39;s see how it looks in Crowbar.&lt;/p&gt;
&lt;pre class=&quot;language-crowbar&quot;&gt;&lt;code class=&quot;language-crowbar&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; fp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hate that way less.
It&#39;s less terse, certainly, but it&#39;s more explicit.
The variable name is where it belongs, instead of nestled three layers deep inside the declaration.
The fact that the &lt;code&gt;char *&lt;/code&gt; and &lt;code&gt;float *&lt;/code&gt; need to be parenthesized here is probably unnecessary, but you could imagine situations where those parentheses would be vital.
And introducing &lt;code&gt;function&lt;/code&gt; as a keyword means you can look at it and know instantly that it&#39;s a pointer-to-a-function, instead of going &amp;quot;wait what&#39;s that syntax where there are more parentheses than you&#39;d think you&#39;d want? oh yeah it&#39;s function pointers.&amp;quot;&lt;/p&gt;
&lt;p&gt;So let&#39;s take a look at the worst thing C can offer.
The Spiral Rule calls it the &amp;quot;ultimate&amp;quot;, and I don&#39;t think that&#39;s a misnomer:&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;fp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That fractal mess is &amp;quot;a function passing an int and a pointer to a function passing an int returning nothing (void) returning a pointer to a function passing an int returning nothing (void)&amp;quot;.
My eyes glaze over reading that description even more than they do reading the original C.
Can we make this not look awful?&lt;/p&gt;
&lt;pre class=&quot;language-crowbar&quot;&gt;&lt;code class=&quot;language-crowbar&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is beautiful.
(Well, no it isn&#39;t, but it&#39;s way less ugly than the original.)
It&#39;s clear which things are functions and which things are not, the nesting is all transparent and visible, and &lt;em&gt;you can tell what the return type is without a PhD in Deciphering C Declarations&lt;/em&gt;.
Plus, importantly, it&#39;s clear that this is a function prototype and not a function pointer declaration, which is a massive improvement over the original.&lt;/p&gt;
&lt;h2 id=&quot;bonus-round&quot; tabindex=&quot;-1&quot;&gt;Bonus Round&lt;/h2&gt;
&lt;p&gt;Just for kicks, another less-awful-but-still-not-great thing about C type syntax is the pointer-to-constant vs constant-pointer dichotomy.&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; points_to_const&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// can never do *points_to_const = 8;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; const_pointer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// can never do const_pointer = &amp;amp;x;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You have to remember which is which.
And why memorize when you can read?&lt;/p&gt;
&lt;pre class=&quot;language-crowbar&quot;&gt;&lt;code class=&quot;language-crowbar&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; points_to_const&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; const_pointer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Much, much better.&lt;/p&gt;
&lt;h2 id=&quot;looking-forwards&quot; tabindex=&quot;-1&quot;&gt;Looking Forwards&lt;/h2&gt;
&lt;p&gt;This syntax is simpler than C&#39;s without losing any expressive power.
That makes me very happy.&lt;/p&gt;
&lt;p&gt;If you&#39;re curious what&#39;s coming next for Crowbar, watch this space for when I eventually write another Crowbar-related blog post, or &lt;a href=&quot;https://sr.ht/~boringcactus/crowbar-lang/lists&quot;&gt;join the mailing list&lt;/a&gt;.
(But don&#39;t get your hopes up; Crowbar is a project I&#39;m working on in my spare time on a when-I-feel-like-it basis.)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Crowbar: Defining a good C replacement</title>
    <link href="https://www.boringcactus.com/2020/09/28/crowbar-1-defining-a-c-replacement.html" />
    <updated>2020-09-28T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2020/09/28/crowbar-1-defining-a-c-replacement.html</id>
    <content type="html">&lt;p&gt;I like Rust a lot.
That said, the always-opinionated, &lt;s&gt;often-correct&lt;/s&gt; &lt;a href=&quot;https://web.archive.org/web/20201020022457if_/https://cmpwn.com/@sir/104946211496582150&quot;&gt;occasionally-correct&lt;/a&gt; Drew DeVault raises some good points in his blog post &lt;a href=&quot;https://drewdevault.com/2019/03/25/Rust-is-not-a-good-C-replacement.html&quot;&gt;Rust is not a good C replacement&lt;/a&gt;.
He names some attributes that C has and Rust lacks which he thinks are required in a good C replacement.
So what can we say are some features of a hypothetical good C replacement?&lt;/p&gt;
&lt;h2 id=&quot;portability&quot; tabindex=&quot;-1&quot;&gt;Portability&lt;/h2&gt;
&lt;p&gt;Per Drew, &amp;quot;C is the most portable programming language.&amp;quot;
Rust, via LLVM, supports a lot of target architectures, but C remains ubiquitous.
Anything less portable than LLVM is, therefore, incapable of being a good C replacement, and to be a truly great C replacement a language should be as portable as C.&lt;/p&gt;
&lt;p&gt;The lifehack that makes sense here to me, difficult as it might be to implement, is to have the compiler for your C replacement be able to emit C as a compile target.
It might not be the same C that a competent C programmer would write by hand, the result might not initially be as fast as the compiler would be if it were running the full toolchain directly, but in principle a self-hosting compiler that could also emit C would be extremely easy to bootstrap on any new architecture.&lt;/p&gt;
&lt;h2 id=&quot;specification&quot; tabindex=&quot;-1&quot;&gt;Specification&lt;/h2&gt;
&lt;p&gt;The C specification, while large and unwieldy and expensive, does at least exist.
This creates a clear definition for how a compiler &lt;em&gt;should&lt;/em&gt; work, whereas the behavior of Rust-the-language is defined to be whatever rustc-the-compiler does.
Rust&#39;s lack of a specification allows it to move fast and not break things, which is cool for Rust (shout out to async/await), but there&#39;s virtue in moving slow, too.
I don&#39;t think Rust itself necessarily needs a specification, but I do think that a good C replacement should be formally specified, and move at the speed of bureaucracy to ensure only the best changes get actually made.&lt;/p&gt;
&lt;p&gt;The C99 specification is 538 pages and costs more money than I&#39;d want to spend to get a legitimate copy of.
A good C replacement should probably have a specification that fits in half the size - hell, call it 200 pages for a nice round number - and that specification should be released free of charge and under a permissive license.&lt;/p&gt;
&lt;h2 id=&quot;diverse-implementations&quot; tabindex=&quot;-1&quot;&gt;Diverse Implementations&lt;/h2&gt;
&lt;p&gt;I think this is a result of having a specification more than a cause of its own, but Drew points out that the various C compilers keep each other and the spec honest and well-tested.
I think in principle a good C replacement could start out with only one compiler, but it would need to be fairly straightforward to write a new compiler for - which is certainly not the case for C.
It might be helpful to have several different reference compilers, themselves written in different languages and for different contexts.
(It would be a fun party trick to have a WebAssembly-based compiler that people could poke around at in their browsers.)&lt;/p&gt;
&lt;h2 id=&quot;zero-effort-ffi&quot; tabindex=&quot;-1&quot;&gt;Zero-Effort FFI&lt;/h2&gt;
&lt;p&gt;Drew calls this point &amp;quot;a consistent and stable ABI,&amp;quot; but I think the context in which that matters is being able to use other libraries from your code, and being able to compile your code as a library that other people can use in turn, without needing to jump through extra hoops like writing glue layers manually or recompiling everything from source.
The easy (&amp;quot;easy&amp;quot;) solution is to just build libraries that C code can just use directly, and use the same ABI that C libraries in turn would export, so that there&#39;s no such thing as FFI because no functions exist which are foreign.&lt;/p&gt;
&lt;h2 id=&quot;composable-build-tools&quot; tabindex=&quot;-1&quot;&gt;Composable Build Tools&lt;/h2&gt;
&lt;p&gt;I don&#39;t give a shit about this one, but in fairness to Drew I could understand why he would.
If I have a project that starts out being entirely C, and I&#39;m like &amp;quot;oh lemme try out Rust for some of this&amp;quot;, I don&#39;t want to start by swapping out my build system and everything, I want to keep my Makefiles and make like one change to them and call it a day.
(Makefiles also suck, but they don&#39;t dual-wielding-footguns suck, so it doesn&#39;t matter.)
But cargo is very decidedly not built to be used that way, and rustc is very decidedly not built to be called from things that aren&#39;t cargo.
So a good C replacement should have a compiler that works the way C compilers tend to, i.e. you pass in a file or several and a bunch of arguments and it builds the file you need it to build.&lt;/p&gt;
&lt;h2 id=&quot;fart-noises&quot; tabindex=&quot;-1&quot;&gt;&lt;em&gt;fart noises&lt;/em&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Concurrency is generally a bad thing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;safety-is-just-another-feature&quot; tabindex=&quot;-1&quot;&gt;Safety Is Just Another Feature&lt;/h2&gt;
&lt;p&gt;In light of all the problems he has with Rust, Drew declares&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Yes, Rust is more safe. I don’t really care. In light of all of these problems, I’ll take my segfaults and buffer overflows.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And like.
On one hand, those are all extremely avoidable problems, and the way to avoid them is to start using a memory safe language.
And they do cause &lt;em&gt;rather more substantial problems&lt;/em&gt; than not having a specification.&lt;/p&gt;
&lt;p&gt;But also.
On the other hand, I don&#39;t eat my vegetables, I only comment my code half the time, I use Windows and I don&#39;t hate it.
We&#39;re all of us creatures of habit, we do what&#39;s familiar, and we&#39;ll keep doing things in dangerous ways if switching to safe ways would have too high a learning curve.
So as much of a bruh moment as this is in the abstract, I&#39;ve been there, I&#39;ll likely be there again, and I can&#39;t really judge all that harshly.&lt;/p&gt;
&lt;h2 id=&quot;crowbar&quot; tabindex=&quot;-1&quot;&gt;Crowbar&lt;/h2&gt;
&lt;p&gt;The conclusion to Drew&#39;s blog post opens like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;C is far from the perfect language - it has many flaws.
However, its replacement will be simpler - not more complex.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And those sentences have been rattling around in my brain for a while now.
Because he&#39;s right about that much.
If we want C programmers to stop using C, we have to give them something that&#39;s only as different from C as it actually needs to be.
We need C: The Good Parts Plus Barely Enough To Add Memory Safety And Good Error Handling.&lt;/p&gt;
&lt;p&gt;And as someone with extraordinarily poor time management skills, I think I kinda want to work on building that.&lt;/p&gt;
&lt;p&gt;Naming things is widely known to be the hardest problem in computer science, but since the goal is to pry C diehards away from C, I think a good name for this project would be&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sr.ht/~boringcactus/crowbar-lang/&quot;&gt;Crowbar: the good parts of C, with a little bit extra&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I don&#39;t know if this will get any of my time and attention.
It really shouldn&#39;t - I&#39;ve got so much other shit I should be doing instead - but also this is fun so who knows.
Join the mailing lists if you&#39;re interested in participating.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A Survey of Rust Embeddable Scripting Languages</title>
    <link href="https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html" />
    <updated>2020-09-16T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html</id>
    <content type="html">&lt;p&gt;Rust is a nice programming language.
But it&#39;s only ever compiled, so if you want behavior that can be edited at runtime, you have to either anticipate and implement every single knob you think might need adjusting or let users write code in some other language that you then call from Rust.
Lua is (to my knowledge) the most common language for this use case in general, but I don&#39;t really like using Lua all that much, so let&#39;s take a more comprehensive look at some of our options.&lt;/p&gt;
&lt;p&gt;We&#39;ll need a use case in mind before we can really get going here, so here&#39;s our motivating example.
We&#39;ve got an event enum that our host can send to our script:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Word&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we&#39;ve got some operation that makes sense in the context of our host, which the script can call:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token macro property&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;✨{}✨&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;d be nice to reuse most of this structure, so let&#39;s define a consistent interface:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;ScriptHost&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it&#39;d be nice if almost all the logic was generic over &lt;code&gt;ScriptHost&lt;/code&gt; so our individual examples could just call it as is:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;run_main&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptHost&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; stdin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stdin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token macro property&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Type a number or some text to fire an event, or nothing to exit.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        stdin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read_line&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;is_empty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; event &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Err&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token constant&quot;&gt;SH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And specifically, where possible, we&#39;d like to have the script simply export a function we can call and pass in an event.
That way, the script can maintain its own long-running state if it wants to.&lt;/p&gt;
&lt;p&gt;So that&#39;s the context in which we are working.
If your context is not like this, then some libraries that don&#39;t work here might work for you.
(If you&#39;re curious, you can check out the full source code for these examples &lt;a href=&quot;https://git.sr.ht/~boringcactus/rust-scripting-languages&quot;&gt;here&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Our contenders will be pulled from the top crates.io results for the tags &amp;quot;script&amp;quot;, &amp;quot;scripting&amp;quot;, and &amp;quot;lua&amp;quot;, sorted by Recent Downloads as of when I&#39;m starting this blog post and with a cutoff of whenever I feel like stopping.
Specifically (these are links to the sections where I discuss them, if you&#39;ve got one in mind you&#39;re curious about) we have&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html#mlua-and-rlua&quot;&gt;mlua&lt;/a&gt; (31,454 recent downloads)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html#mlua-and-rlua&quot;&gt;rlua&lt;/a&gt; (30,877 recent downloads)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html#duckscript&quot;&gt;duckscript&lt;/a&gt; (24,901 recent downloads)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html#rhai&quot;&gt;rhai&lt;/a&gt; (5,884 recent downloads)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html#dyon&quot;&gt;dyon&lt;/a&gt; (1,539 recent downloads)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html#gluon&quot;&gt;gluon&lt;/a&gt; (946 recent downloads)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html#ketos&quot;&gt;ketos&lt;/a&gt; (497 recent downloads)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html#rune&quot;&gt;rune&lt;/a&gt; (495 recent downloads)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.html#ruwren&quot;&gt;ruwren&lt;/a&gt; (451 recent downloads)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That seems like a lot.
Let&#39;s hope I don&#39;t regret this.&lt;/p&gt;
&lt;h2 id=&quot;mlua-and-rlua&quot; tabindex=&quot;-1&quot;&gt;mlua and rlua&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://crates.io/crates/mlua&quot;&gt;mlua&lt;/a&gt; and &lt;a href=&quot;https://crates.io/crates/rlua&quot;&gt;rlua&lt;/a&gt; are both Lua bindings - mlua is apparently a fork of rlua - so we&#39;ll look at them together.&lt;/p&gt;
&lt;p&gt;Reading through the mlua README, we see something unfortunate:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;On Windows &lt;code&gt;vendored&lt;/code&gt; mode is not supported since you need to link to a Lua dll.
Easiest way is to use either MinGW64 (as part of MSYS2 package) with &lt;code&gt;pkg-config&lt;/code&gt; or MSVC with &lt;code&gt;LUA_INC&lt;/code&gt; / &lt;code&gt;LUA_LIB&lt;/code&gt; / &lt;code&gt;LUA_LIB_NAME&lt;/code&gt; environment variables.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I would really rather not have to go to all that work and expect any potential contributors to do the same.
So there goes that one.&lt;/p&gt;
&lt;p&gt;Since mlua is an rlua fork, I was guessing &amp;quot;not a pain in the ass on Windows&amp;quot; isn&#39;t a feature rlua has either, but apparently rlua works just fine on Windows with zero extra pain.
So that&#39;s interesting.&lt;/p&gt;
&lt;p&gt;Oh hey, less than a hundred lines of glue and we&#39;re fuckin set!
The rlua crate has its own script host, so our choice of ScriptContext is really simple:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;LuaScriptHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptHost&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LuaScriptHost&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;ScriptContext&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Lua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we make a new context, we want to create a binding to our &lt;code&gt;print_fancy&lt;/code&gt; method.
Aside from having to use a &lt;code&gt;String&lt;/code&gt; instead of &lt;code&gt;&amp;amp;str&lt;/code&gt; it&#39;s pretty straightforward:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Lua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; lua &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Lua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        lua&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;lua_ctx&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; print_fancy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lua_ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create_function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            lua_ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;globals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;print_fancy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; print_fancy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            lua_ctx
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read_to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scripts/rlua-sample.lua&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rlua-sample.lua&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lua&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(We&#39;re hard coding the path to the script here, because autodiscovering those is beyond the scope of this blog post, but you could totally iterate a directory and either run all those scripts in the same context or give them each their own context.)
We&#39;re going to need a little more glue on the &lt;code&gt;Event&lt;/code&gt; enum, though since Lua doesn&#39;t exactly have anything like enums natively.
(And that glue is more complicated if we&#39;re using Cargo examples, because they aren&#39;t technically the same crate, so Rust&#39;s orphan rules kick in.)
The easiest way to do that, I think, is to just add &lt;code&gt;get_number&lt;/code&gt; and &lt;code&gt;get_text&lt;/code&gt; methods that will return &lt;code&gt;nil&lt;/code&gt; if the &lt;code&gt;Event&lt;/code&gt; wasn&#39;t of that type:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;EventForLua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserData&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventForLua&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;add_methods&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token lifetime-annotation symbol&quot;&gt;&#39;lua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserDataMethods&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token lifetime-annotation symbol&quot;&gt;&#39;lua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;methods&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        methods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add_method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;get_number&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; this&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; this&lt;span class=&quot;token number&quot;&gt;.0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;number&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;number&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_lua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                _ &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        methods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add_method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;get_text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; this&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;this&lt;span class=&quot;token number&quot;&gt;.0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_lua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                _ &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Armed with all these tools, we can now actually write our Lua script:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; number &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; number &lt;span class=&quot;token operator&quot;&gt;~=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;number!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; number&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;~=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;got an event!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now that we have our function, since we already ran this script, to handle an event all we need to do is call that function:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Lua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;lua_ctx&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; handle_event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Function&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lua_ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;globals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;handle_event&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            handle_event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;EventForLua&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&#39;s all there is to it!
That was surprisingly painless, all things considered.
The only part that hurt a bit was the glue from &lt;code&gt;Event&lt;/code&gt; into Lua, which wasn&#39;t even that bad for my case, but depending on what you&#39;re doing it might be.&lt;/p&gt;
&lt;h2 id=&quot;duckscript&quot; tabindex=&quot;-1&quot;&gt;duckscript&lt;/h2&gt;
&lt;p&gt;So Lua is a language I know I don&#39;t like all that much, and the gnarliest part of using it here was getting Rust types into it.
Let&#39;s see if &lt;a href=&quot;https://crates.io/crates/duckscript&quot;&gt;Duckscript&lt;/a&gt;, a &amp;quot;simple, extendable and embeddable scripting language&amp;quot;, will be even simpler.&lt;/p&gt;
&lt;p&gt;Well, this is a minor nitpick, but if I want to write a custom command (which, I think, is the right way to get &lt;code&gt;print_fancy&lt;/code&gt; available to our script) the trait for that requires that I manually write&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;clone_and_box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;dyn&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Command&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;even though &lt;em&gt;anything that is &lt;code&gt;Clone&lt;/code&gt; can already be cloned into a box&lt;/em&gt;.
So that&#39;s a (very) minor nuisance.&lt;/p&gt;
&lt;p&gt;The less-minor nuisance, it turns out, is that I can&#39;t figure out how to actually run a function defined in a script.
So I guess in Duckscript you can&#39;t do that, and we&#39;ll have to just rerun the whole script when the event gets fired.&lt;/p&gt;
&lt;p&gt;But once we&#39;ve discovered and adapted to those issues, there&#39;s not much actual code that needs writing.
Conveniently, since we can&#39;t have any persistent state anyway, there&#39;s no script context to store:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;DuckscriptHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptHost&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DuckscriptHost&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;ScriptContext&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The glue to expose our &lt;code&gt;print_fancy&lt;/code&gt; is pretty terse as well:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;FancyPrintCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Command&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FancyPrintCommand&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;fancy_print&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;clone_and_box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;dyn&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Command&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; arguments&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CommandResult&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;arguments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;CommandResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once we&#39;ve got this, we can handle events by setting up everything from scratch:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_context&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Make a context&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Add the standard library&lt;/span&gt;
        &lt;span class=&quot;token namespace&quot;&gt;duckscriptsdk&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;commands&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Add our print_fancy command&lt;/span&gt;
        context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PrintFancyCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Pick some strings to put in the context to represent our event&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;number&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;format!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;variables&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;event.type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event_type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;variables&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;event.value&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Run the script&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;run_script_file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scripts/duckscript-sample.ds&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s a lot of steps there.
As you may notice, we have to put the number into a string before we can add it to the context.
This is because all values in Duckscript are strings.&lt;/p&gt;
&lt;p&gt;Well, now that we&#39;ve got all the code to pass in variables and run it, let&#39;s write our event handler script:&lt;/p&gt;
&lt;pre class=&quot;language-duckscript&quot;&gt;&lt;code class=&quot;language-duckscript&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;eq&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;number&quot;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;number!&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token operator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text!&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token operator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Got an event&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Technically, this all works.
But I don&#39;t like it.
It might be enough for your use case, though, and if it is, it&#39;ll work.&lt;/p&gt;
&lt;h2 id=&quot;rhai&quot; tabindex=&quot;-1&quot;&gt;rhai&lt;/h2&gt;
&lt;p&gt;We&#39;re still in search of a Rust-centric scripting language that&#39;s good for our use case, so let&#39;s see if &lt;a href=&quot;https://crates.io/crates/rhai&quot;&gt;Rhai&lt;/a&gt;, &amp;quot;an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application&amp;quot;, will be that.
It&#39;s got &lt;a href=&quot;https://schungx.github.io/rhai/&quot;&gt;an entire documentation book&lt;/a&gt;, which is always nice to see, and WebAssembly support.
So hopefully this one is as cool as it looks.&lt;/p&gt;
&lt;p&gt;Page four of said documentation book gives some really helpful clarity:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Rhai’s purpose is to provide a dynamic layer over Rust code, in the same spirit of zero cost abstractions. It doesn’t attempt to be a new language.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That also sounds like exactly what we need.&lt;/p&gt;
&lt;p&gt;Just take a minute and &lt;em&gt;look at how fucking gorgeous this code is&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;RhaiScriptHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptHost&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RhaiScriptHost&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;ScriptContext&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Engine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token lifetime-annotation symbol&quot;&gt;&#39;static&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;AST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;EvalAltResult&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; engine &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Engine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        engine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register_fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;print_fancy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; print_fancy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; scope &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; ast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; engine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;compile_file_with_scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; scope&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PathBuf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scripts/rhai-sample.rhai&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// if there&#39;s some global state or on-boot handling, make sure it runs&lt;/span&gt;
        engine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;consume_ast_with_scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; scope&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;engine&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scope&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;engine&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scope&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; argument &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;number&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Dynamic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;number&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Dynamic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        engine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call_fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;handle_event&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;argument&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s all of it.
That&#39;s the whole damn thing.
Our script is pretty darn straightforward, too, unsurprisingly:&lt;/p&gt;
&lt;pre class=&quot;language-rhai&quot;&gt;&lt;code class=&quot;language-rhai&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;type_of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;i64&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token builtin&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;number! &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token builtin&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text! &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;got an event!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rhai doesn&#39;t have pattern matching, so we can&#39;t use our &lt;code&gt;Event&lt;/code&gt; enum directly, but it does have runtime type introspection, so we can simply pass in either a number or a string and let the script sort it out.
(If this is possible in Lua, too, then we could&#39;ve made our Lua implementation a little simpler, but I&#39;m not as familiar with Lua.)&lt;/p&gt;
&lt;p&gt;There&#39;s so much to love here, but my favorite part is that we don&#39;t need any extra glue at all around &lt;code&gt;print_fancy&lt;/code&gt; to be able to expose it to our Rhai script.
This is &lt;em&gt;excellent&lt;/em&gt;.
Well done to Stephen Chung, JT, and &lt;a href=&quot;https://github.com/rhaiscript/rhai/graphs/contributors&quot;&gt;the rest of the contributors&lt;/a&gt;.
This is gonna be tough to beat.&lt;/p&gt;
&lt;h2 id=&quot;dyon&quot; tabindex=&quot;-1&quot;&gt;dyon&lt;/h2&gt;
&lt;p&gt;Ooh, &lt;a href=&quot;https://crates.io/crates/dyon&quot;&gt;dyon&lt;/a&gt; was built by one of the two big Rust game engine projects.
(In fairness, though, rlua was built by the other one.)&lt;/p&gt;
&lt;p&gt;On the upside, this is almost as straightforward as Rhai was:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;DyonScriptHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptHost&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DyonScriptHost&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;ScriptContext&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Arc&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token macro property&quot;&gt;dyon_fn!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;dyon_print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add_str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;print_fancy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dyon_print_fancy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Dfn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token macro property&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scripts/dyon-sample.dyon&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; module&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Arc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; call &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;handle_event&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; call &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Runtime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our script is likewise pretty straightforward:&lt;/p&gt;
&lt;pre class=&quot;language-dyon&quot;&gt;&lt;code class=&quot;language-dyon&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;number&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token builtin&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;number! &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token builtin&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text! &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;got an event!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But the API took a really long time for me to actually figure out, and there are a few language features that I extremely don&#39;t get.
Like, they&#39;ve got &lt;a href=&quot;http://www.piston.rs/dyon-tutorial/secrets.html&quot;&gt;secrets&lt;/a&gt; that can be attached to a bool or number, so you can ask for a retroactive explanation for why a function returned the value it did, or where a value came from.
And that&#39;s neat and all, but like. why.
Honestly I&#39;m just not as impressed by this as I am by Rhai.
Maybe you are, though, in which case this might work well for you.&lt;/p&gt;
&lt;h2 id=&quot;gluon&quot; tabindex=&quot;-1&quot;&gt;gluon&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://crates.io/crates/gluon&quot;&gt;gluon&lt;/a&gt;, unlike our previous contestants, is a statically typed functional language.
Static types, for this context, are nice to have.
Functional programming... may or may not be, depending on how it&#39;s implemented.&lt;/p&gt;
&lt;p&gt;Oh hey, at time of writing their website is down.
That&#39;s always a good sign.&lt;/p&gt;
&lt;p&gt;An even better sign, of course, is the fact that trying to declare our Event type in Gluon causes a stack overflow.
In theory, since it&#39;s got algebraic data types, we could just declare it as is and bring it right on in, but unfortunately the code to do that 100% automatically appears to not compile, and the code to do that only almost automatically still requires us to write the type definition on the Gluon side manually:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;vm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load_script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rust-scripting-languages.event&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;type Event = Number Int | Text String&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m not sure why this causes a stack overflow either.
But it does.
As such, I have no clue how good this actually would be to use.&lt;/p&gt;
&lt;h2 id=&quot;ketos&quot; tabindex=&quot;-1&quot;&gt;ketos&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://crates.io/crates/ketos&quot;&gt;ketos&lt;/a&gt; describes itself as &amp;quot;a Lisp dialect functional programming language&amp;quot;.
So that ought to be interesting.&lt;/p&gt;
&lt;p&gt;The Rust function glue all assumes your functions return a &lt;code&gt;Result&lt;/code&gt; and that&#39;s a minor nuisance, but that&#39;s not insurmountable.
Overall, this is pretty terse:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;KetosScriptHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptHost&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KetosScriptHost&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;ScriptContext&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Interpreter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; interp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Interpreter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; scope &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; interp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;print_fancy_fallible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token macro property&quot;&gt;ketos_fn!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; scope &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;print-fancy&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;print_fancy_fallible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        interp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run_file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PathBuf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scripts/ketos-sample.ket&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;handle-event&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our script itself is... definitely Lisp-y.&lt;/p&gt;
&lt;pre class=&quot;language-ketos&quot;&gt;&lt;code class=&quot;language-ketos&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle-event&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;do&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;~a ~a&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;type-of&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;print-fancy&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;got an event!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This all works, and if you love Lisp and want your users to write a bunch of Lisp then this&#39;ll work perfectly for you.
But it&#39;s not really to my tastes.&lt;/p&gt;
&lt;h2 id=&quot;rune&quot; tabindex=&quot;-1&quot;&gt;rune&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://crates.io/crates/rune&quot;&gt;rune&lt;/a&gt; lists pattern matching in their README as a feature, so that&#39;s a good sign.
And they&#39;ve got a Book that starts by giving a shout out to Rhai as a major inspiration, which is also neat.&lt;/p&gt;
&lt;p&gt;Turns out the pattern matching doesn&#39;t let you match on enum variants even though Rune &lt;em&gt;has enums&lt;/em&gt;.
So we&#39;ve gotta do the same dynamic typing stuff we&#39;ve been doing before.
But this is pretty clean:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;RuneScriptHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptHost&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RuneScriptHost&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;ScriptContext&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Vm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;dyn&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token lifetime-annotation symbol&quot;&gt;&#39;static&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Vm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;rune&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;runestick&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;print_fancy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; print_fancy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; script &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;read_to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scripts/rune-sample.rn&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; warnings &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;rune&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Warnings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; unit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;rune&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load_source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scripts/rune-sample.rn&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;script&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; warnings&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; vm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Vm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Arc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Arc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;unit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vm&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vm&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Vm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; arg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        vm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;handle_event&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, we have zero glue required for our &lt;code&gt;print_fancy&lt;/code&gt; function, which is nice.
Our script looks pretty decent too:&lt;/p&gt;
&lt;pre class=&quot;language-rune&quot;&gt;&lt;code class=&quot;language-rune&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        n &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; n &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;`Number! {n}`&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        t &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;`Text! {t}`&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;got an event!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The one issue I notice is that &lt;code&gt;Vm::call&lt;/code&gt; takes &lt;code&gt;self&lt;/code&gt; by value, which means we&#39;re forced to &lt;code&gt;clone()&lt;/code&gt; our VM every time we want to handle a new event instead of handling them all in the same VM.
This probably means a more complicated script with some global state to maintain wouldn&#39;t be able to do that.
So if that&#39;s something you know you&#39;ll want to support, Rune might not be for you, unless there&#39;s a different way to do this that I didn&#39;t find in the docs.
But if you know you don&#39;t need that, Rune might work well for you.&lt;/p&gt;
&lt;h2 id=&quot;ruwren&quot; tabindex=&quot;-1&quot;&gt;ruwren&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://crates.io/crates/ruwren&quot;&gt;ruwren&lt;/a&gt; provides bindings to the &lt;a href=&quot;https://wren.io/&quot;&gt;Wren&lt;/a&gt; embeddable scripting language, which looks kinda neat:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Think Smalltalk in a Lua-sized package with a dash of Erlang and wrapped up in a familiar, modern syntax.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think the example presented in the README at time of writing is incomplete and buggy, but I figured out how to fix it and sent in a pull request.&lt;/p&gt;
&lt;p&gt;We do have to write some glue for our &lt;code&gt;print_fancy&lt;/code&gt; method; since Wren is inspired by Smalltalk, everything is objects, so we need a class within which &lt;code&gt;print_fancy&lt;/code&gt; can be a static method:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Demo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Demo&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Demo&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Demo&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vm&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;get_slot_checked!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vm &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; string &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token macro property&quot;&gt;create_module!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Demo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;crate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Demo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; foo &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;print_fancy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; print_fancy
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    module &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; demo
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Armed with this, the actual integration is pretty straightforward:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;RuwrenScriptHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScriptHost&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RuwrenScriptHost&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;ScriptContext&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;VMWrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;dyn&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token lifetime-annotation symbol&quot;&gt;&#39;static&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new_context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScriptContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; modules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ModuleLibrary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token namespace&quot;&gt;demo&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publish_module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; modules&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; vm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;VMConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;library&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;modules&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        vm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;interpret&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;demo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r##&quot;
        foreign class Demo {
            foreign static print_fancy(string)
        }
        &quot;##&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; script &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;read_to_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scripts/ruwren-sample.wren&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        vm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;interpret&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;script&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vm&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vm&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;VMWrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        vm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;vm&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            vm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ensure_slots&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            vm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_variable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;EventHandler&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;event &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; vm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_slot_double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;n &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; vm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_slot_string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        vm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FunctionSignature&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new_function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;handleEvent&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And our test script in Wren looks nice:&lt;/p&gt;
&lt;pre class=&quot;language-wren&quot;&gt;&lt;code class=&quot;language-wren&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;demo&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Demo&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Num&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Number! &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;%(&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Text! &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;%(&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Demo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;print_fancy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;got an event&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Overall, I think this is pretty good.
This specific use case isn&#39;t particularly well suited to Smalltalk-style object orientation, but if yours is then this will work well for you.
Plus, since Wren isn&#39;t Rust-specific, it might be slightly more portable, if that&#39;s a concern for you.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;summary&lt;/h2&gt;
&lt;p&gt;So those are our options.
A convenient bullet points tl;dr for you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;a href=&quot;https://crates.io/crates/rlua&quot;&gt;rlua&lt;/a&gt; if you specifically want a scripting language that some of your users might already know.&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&quot;https://crates.io/crates/ruwren&quot;&gt;ruwren&lt;/a&gt; if you specifically want a Smalltalk-like (object-oriented) scripting language.&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&quot;https://crates.io/crates/ketos&quot;&gt;ketos&lt;/a&gt; if you specifically want a LISP-like (parentheses-oriented) scripting language.&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&quot;https://crates.io/crates/rhai&quot;&gt;rhai&lt;/a&gt; if you just want a good scripting language.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>A Survey of Rust GUI Libraries</title>
    <link href="https://www.boringcactus.com/2020/08/21/survey-of-rust-gui-libraries.html" />
    <updated>2020-08-21T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2020/08/21/survey-of-rust-gui-libraries.html</id>
    <content type="html">&lt;p&gt;(Note from the future: I did this again &lt;a href=&quot;https://www.boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.html&quot;&gt;in 2021&lt;/a&gt; and &lt;a href=&quot;https://www.boringcactus.com/2025/04/13/2025-survey-of-rust-gui-libraries.html&quot;&gt;in 2025&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;a popular trend in the Rust community is to ask &amp;quot;Are We X Yet&amp;quot; for various things that it would be nice to be able to develop easily in Rust - &lt;a href=&quot;https://arewegameyet.rs/&quot;&gt;game&lt;/a&gt; and &lt;a href=&quot;https://www.arewewebyet.org/&quot;&gt;web&lt;/a&gt; are the most prominent ones as far as i can tell - and one such question is &lt;a href=&quot;https://areweguiyet.com/&quot;&gt;Are We GUI Yet&lt;/a&gt;.
that&#39;s a good question; &lt;em&gt;are&lt;/em&gt; we GUI yet?
Are We GUI Yet has a list of libraries for building GUIs: let&#39;s go through them in alphabetical order and see if we can build a simple to-do list with them without too much struggle.&lt;/p&gt;
&lt;p&gt;some notes before we get started.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;this is all extremely subjective.&lt;/li&gt;
&lt;li&gt;the only ui toolkits i have used and not hated are Swing (i know), Electron (&lt;em&gt;i know&lt;/em&gt;), and wxWidgets, which doesn&#39;t have Rust bindings because Rust bindings to C++ libraries are generously described as a nuisance to create (i&#39;ve tried, and i&#39;m writing this post instead of trying harder).
as such, i might be using some of these wrong, who knows.&lt;/li&gt;
&lt;li&gt;i use windows, and so anything that&#39;s a nuisance to set up on windows is not going to fare well regardless of how cool it is once you get it working.
it could be the best thing since sliced bread or Meteor on release and i wouldn&#39;t care.
do not @ me.&lt;/li&gt;
&lt;li&gt;the people who wrote these libraries have done more than i have to make the rust gui ecosystem not suck, and i don&#39;t want any of this to come across as suggesting that they suck and their work is bad.
the strongest thing i want to say is that a library is not designed in a way that i would want it to be designed, or that it doesn&#39;t work for me.
doing this shit at all is really goddamn difficult, and i don&#39;t want to minimize that by being unhappy with the results.&lt;/li&gt;
&lt;li&gt;i started drafting this post in early July 2020, and finished it in late August 2020.
some things may have changed in the meantime while i wasn&#39;t paying attention.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;azul&quot; tabindex=&quot;-1&quot;&gt;azul&lt;/h2&gt;
&lt;p&gt;first on our list is &lt;a href=&quot;https://azul.rs/&quot;&gt;azul&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A free, functional, immediate-mode GUI framework for rapid development of desktop applications written in Rust, supported by the Mozilla WebRender rendering engine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;the wiki says we need cmake installed, which is never a good sign, but conveniently, i&#39;ve already got that set up on my computer, for reasons i forget but probably didn&#39;t enjoy.
the runtime dependencies on linux are a mile long, but fortunately i don&#39;t have to care.
azul isn&#39;t currently available on crates.io for reasons that presumably exist but are difficult to explain, so we have to add it directly as a git dependency.&lt;/p&gt;
&lt;p&gt;once we&#39;ve got it added as a dependency, we can attempt to run our test crate, just to make sure everything&#39;s not on fire. unfortunately:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;error: failed to run custom build command for `servo-freetype-sys v4.0.5`

Caused by:
  process didn&#39;t exit successfully: `D:&#92;Melody&#92;Projects&#92;we-are-not-gui-yet&#92;target&#92;debug&#92;build&#92;servo-freetype-sys-1fae054761ff82c5&#92;build-script-build` (exit code: 101)
--- stdout
running: &quot;cmake&quot; &amp;lt;snip&amp;gt;

--- stderr
CMake Error: Could not create named generator Visual Studio 16 2019&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;this is an inauspicious beginning.
one version history crawl later and it looks like my cmake is from April 2019, which is not all that old but maybe they hadn&#39;t caught up on the latest visual studio yet, who knows.
this is already more work than i was prepared to do, but i&#39;ve come this far, so it&#39;s time to update my cmake.&lt;/p&gt;
&lt;p&gt;okay one installer later and it&#39;s time to try again.
armed with a cmake from May 2020, let&#39;s give this another shot:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;error[E0433]: failed to resolve: could not find `IoReader` in `bincode`
   --&gt; C:&#92;Users&#92;Melody&#92;.cargo&#92;registry&#92;src&#92;github.com-1ecc6299db9ec823&#92;webrender_api-0.60.0&#92;src&#92;display_list.rs:270:35
    |
270 |             let reader = bincode::IoReader::new(UnsafeReader::new(&amp;mut self.data));
    |                                   ^^^^^^^^ could not find `IoReader` in `bincode`&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;welp.
the cmake update fixed things, i guess, but now we&#39;ve got a whole other pile of mess.
this might be fixable, it may have been fixed by the time you read this.
regardless, this library does not work for me.&lt;/p&gt;
&lt;h2 id=&quot;conrod&quot; tabindex=&quot;-1&quot;&gt;conrod&lt;/h2&gt;
&lt;p&gt;next up is &lt;a href=&quot;https://github.com/pistondevelopers/conrod&quot;&gt;conrod&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An easy-to-use, 2D GUI library written entirely in Rust.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;i&#39;ve actually used this one before in a couple of projects, but it&#39;s been a minute, so i forget the details.&lt;/p&gt;
&lt;p&gt;they do not have a real tutorial, which is unfortunate, but they do have some examples.
unfortunately, step one is to pick which of the half dozen backends i want.
do i want glium or vulkano or rendy or piston?
do i look like i know what a vulkano is?
i just want a picture of a &lt;a href=&quot;https://youtu.be/EvKTOHVGNbg&quot;&gt;god dang hot dog&lt;/a&gt;.
okay that&#39;s not quite fair, i recognize three of those and can infer from context what the fourth one is, but that&#39;s only because i&#39;ve been down this road before, and i still have no clue which one is the right one to pick.
all the examples live under glium, though, so let&#39;s go with that.&lt;/p&gt;
&lt;p&gt;wait actually i&#39;m staring at these examples and there&#39;s an entire ass event loop in the support code for the examples.
something in here mentions a &lt;code&gt;GliumDisplayWinitWrapper&lt;/code&gt; and i&#39;m scared.
i have literally used this library before - on two different projects - and i&#39;m at a loss.
i don&#39;t want to just copy and paste the examples without actually understanding what&#39;s going on, i can&#39;t understand what&#39;s going on in the examples, and there&#39;s nowhere else to get started.
so there goes that i guess.&lt;/p&gt;
&lt;h2 id=&quot;core-foundation&quot; tabindex=&quot;-1&quot;&gt;core-foundation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Bindings to Core Foundation for macOS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;oh hey, it&#39;s an OS i don&#39;t have access to at all.
next.&lt;/p&gt;
&lt;h2 id=&quot;druid&quot; tabindex=&quot;-1&quot;&gt;druid&lt;/h2&gt;
&lt;p&gt;our next contender is &lt;a href=&quot;https://linebender.org/druid/&quot;&gt;druid&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Druid is a framework for building simple graphical applications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;apparently this sprung out of that vi-like text editor a couple googlers were working on, so apparently it&#39;s at least possible to use it for real software.
there&#39;s a tutorial, they&#39;re on crates.io, they&#39;re describing it as &amp;quot;conceptually simple and largely non-magical&amp;quot; which i am always a fan of, i am cautiously optimistic.
if we throw it in our dependencies and just see if anything breaks, we find the surprising result that everything just works.&lt;/p&gt;
&lt;p&gt;oh hey the first real chapter in the tutorial starts with&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;this is outdated, and should be replaced with a walkthrough of getting a simple app built and running.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;you love to see it.
fortunately, we can just ignore that and skip to the hello world example, reproduced here in its entirety:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;druid&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AppLauncher&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WindowDesc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Widget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlatformError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;druid&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;build_ui&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Widget&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello world&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlatformError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;AppLauncher&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WindowDesc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;build_ui&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and somehow, this actually works.&lt;/p&gt;
&lt;p&gt;the tutorial ends here, which is unfortunate, but there&#39;s more documentation, including explanations of core concepts with examples that are... todo lists!
with even more features than what i was planning to include here!
so that&#39;s convenient.
the UI hierarchy is based on CSS Flexbox, which i also appreciate.
this is what peak UI layout API looks like:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;build_new_todo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Widget&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TodoState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; textbox &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TextBox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_placeholder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;New todo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TodoState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;next_todo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; button &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Add&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on_click&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TodoState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create_todo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Flex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_flex_child&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;textbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;expand_width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_child&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;button&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1 hour and 80 lines of code later, we&#39;ve got ourselves a perfectly valid and working todo list!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-08-21-survey-of-rust-gui-libraries-1.png&quot; alt=&quot;our sample druid application, showing a todo list&quot;&gt;&lt;/p&gt;
&lt;p&gt;i&#39;m not quite happy with this, though: we can type text and hit the button and it adds the todo, but pressing enter in the text field doesn&#39;t do anything.
there&#39;s no way out-of-the-box to make that happen; let&#39;s see if we can build that ourselves.&lt;/p&gt;
&lt;p&gt;~30 lines of code later, we&#39;ve got it!
the &lt;code&gt;Controller&lt;/code&gt; trait is designed for exactly this sort of thing, when you need to wrap the behavior of an existing component and intercept some events to inject your own logic.&lt;/p&gt;
&lt;p&gt;if you&#39;re curious, you can take a look at &lt;a href=&quot;https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/druid-test&quot;&gt;the source for our druid example&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;so apparently druid is actually pretty darn usable.
i only have a couple tiny issues with it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;it doesn&#39;t use platform native UI widgets, so it doesn&#39;t look quite like a windows app should, and it won&#39;t look quite like a mac or linux app should either if i test it there.
this one is a feature as far as some people are concerned, but i am not on that list.&lt;/li&gt;
&lt;li&gt;accessibility features like being able to tab between UI widgets (&lt;em&gt;update 2020-12-14&lt;/em&gt;: and also literally any screen reader support) are missing, so you&#39;d have to roll those yourself in a real application.
maybe they&#39;ll add that by default in future versions, maybe not, but it would be neat if it existed.&lt;/li&gt;
&lt;li&gt;high-level documentation is incomplete.
the individual struct/function docs are really good, but at a high level you don&#39;t really have a convenient place to jump in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;i was about to add &amp;quot;no support for web&amp;quot; to that list, but even though the high-level docs don&#39;t mention it, the crate root docs and the examples do.
on the plus side, it just works, and i didn&#39;t have to make any changes to my code because i use &lt;a href=&quot;https://github.com/rustwasm/wasm-pack/pull/736&quot;&gt;this patch to wasm-pack that lets you just use binary crates in wasm-pack&lt;/a&gt; even though it hasn&#39;t been merged yet upstream.
on the minus side, it points everything at a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; tag, which means you get none of the accessibility features of actually using the DOM.
so that one&#39;s a mixed bag.&lt;/p&gt;
&lt;p&gt;but yeah, overall druid is perfectly usable for gui development.
i was originally calling this post &amp;quot;we are not gui yet&amp;quot; but i guess we are at least a little bit gui already.
pleasant surprises are the best kind.&lt;/p&gt;
&lt;h2 id=&quot;fltk&quot; tabindex=&quot;-1&quot;&gt;fltk&lt;/h2&gt;
&lt;p&gt;following up that success is &lt;a href=&quot;https://github.com/MoAlyousef/fltk-rs&quot;&gt;fltk&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The FLTK crate is a crossplatform lightweight gui library which can be statically linked to produce small, self-contained and fast gui applications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;cross-platform and statically linked are both good things.
the upstream FLTK website makes my eyes bleed, which is never a good sign for a UI library, but that doesn&#39;t mean much one way or the other.
the simple hello world example is once again a mere handful of lines:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;fltk&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; wind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello from rust&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    wind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    wind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;a downside i&#39;m noticing already, at least compared to druid, is that everything has to be positioned manually, and we don&#39;t get any layout stuff calculated for free.&lt;/p&gt;
&lt;p&gt;a lot of wrestling later, we have a technically working implementation (&lt;a href=&quot;https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/fltk-test&quot;&gt;source code&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-08-21-survey-of-rust-gui-libraries-2.png&quot; alt=&quot;our sample fltk application, showing a todo list&quot;&gt;&lt;/p&gt;
&lt;p&gt;it&#39;s half as much code as the druid implementation, but part of that&#39;s because the druid implementation also preserves state information, so we could easily have added persistence without all that much work, but our fltk version does not do that and is just a pile of ui widgets.
some of that code, i will say, fails to spark joy:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;add_todo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; next_todo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    next_todo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set_value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; done &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CheckButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; top&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    wind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;done&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    wind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redraw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    top &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;we have to drag that position and size around manually.
i don&#39;t like that.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2020-12-14&lt;/em&gt;: also, it doesn&#39;t expose anything to screen readers.&lt;/p&gt;
&lt;p&gt;overall, this technically works i guess, but i think the code is ugly and the style of the resulting application is also ugly.
we do get tab and space and everything working out of the box on buttons, which is always appreciated, though.
not broken or anything, not something i&#39;d be likely to choose to use though either.&lt;/p&gt;
&lt;h2 id=&quot;gtk&quot; tabindex=&quot;-1&quot;&gt;gtk&lt;/h2&gt;
&lt;p&gt;next on our list is another pile of bindings to an existing ui library, &lt;a href=&quot;https://gtk-rs.org/&quot;&gt;gtk&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Rust bindings and wrappers for GLib, GDK 3, GTK+ 3 and Cairo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;however.
the second meaningful sentence in the README says&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;gtk expects GTK+, GLib and Cairo development files to be installed on your system.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and i have been down that road before and mother of god once is enough.
maybe on things-that-are-not-windows this isn&#39;t a nightmare, but i do not use things that are not windows.
the windows instructions are a nightmare even in the happy path that their instructions explain, which last time around i failed to hit, making the whole process even more nightmarish.
so i think i will pass.&lt;/p&gt;
&lt;h2 id=&quot;iced&quot; tabindex=&quot;-1&quot;&gt;iced&lt;/h2&gt;
&lt;p&gt;our next contestant is &lt;a href=&quot;https://github.com/hecrj/iced&quot;&gt;iced&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A cross-platform GUI library for Rust focused on simplicity and type-safety. Inspired by Elm.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;cross-platform and simple are good.
inspired by elm is a tentative &amp;quot;nice&amp;quot; - my experiment with elm way back in the day had mixed results, but it&#39;s not clear how much of that was my fault.&lt;/p&gt;
&lt;p&gt;iced compiles just fine, and it looks like we&#39;ve got a vaguely MVC-ish architecture here.
it looks like you write your logic in a highly portable way and then glue it together in ways that vary based on whether you&#39;re building for native or for Web.&lt;/p&gt;
&lt;p&gt;conveniently, there&#39;s a todo list example!
but we don&#39;t even need it; the example given in the README, with some of the details elided there, is enough context to have an entire todo list application in &lt;a href=&quot;https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/iced-test&quot;&gt;100 lines of Rust&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-08-21-survey-of-rust-gui-libraries-3.png&quot; alt=&quot;our sample iced application, showing a todo list&quot;&gt;&lt;/p&gt;
&lt;p&gt;notably, our checkboxes aren&#39;t aligned to the right of the window.
i couldn&#39;t figure out how to make that happen.
however, we do have built-in support for &amp;quot;do a thing when the user presses enter in the text area,&amp;quot; which we had to write ourself in other frameworks.
so that one is nice.&lt;/p&gt;
&lt;p&gt;compared to druid, i&#39;d say the logic is a little more intuitive, the layout controls are less intuitive, and the web support is way better.
the native build once again doesn&#39;t use native widgets and so once again doesn&#39;t get tab-between-fields or other accessibility features, but the web build uses actual HTML elements and so gets tab-between-fields for free.
high-level documentation is a little more robust here, plus the concepts are less complicated in the first place.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2020-12-14&lt;/em&gt;: screen reader support in the native build is again nonexistent, but the web build works alright, at least with the one i&#39;m testing with.&lt;/p&gt;
&lt;p&gt;so it&#39;s a little easier to get off the ground than with druid, and the results on the web are way better, but it&#39;s more difficult to make it look decent.
maybe that&#39;s just a documentation issue, but it&#39;s not ideal.
regardless, yet again we have a perfectly usable library.&lt;/p&gt;
&lt;h2 id=&quot;imgui&quot; tabindex=&quot;-1&quot;&gt;imgui&lt;/h2&gt;
&lt;p&gt;up next, another binding to an existing library, &lt;a href=&quot;https://github.com/Gekkio/imgui-rs&quot;&gt;imgui&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Rust bindings for Dear ImGui&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;further down the readme, we see&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Almost every application that uses imgui-rs needs two additional components in addition to the main imgui crate: a backend platform, and a renderer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and immediately i no longer give a shit.
i&#39;m pretty sure imgui is designed for, like, diy game engines etc where you already have a backend and a renderer set up, which is a really specific use case that i don&#39;t currently meet.
goodbye.&lt;/p&gt;
&lt;h2 id=&quot;kas&quot; tabindex=&quot;-1&quot;&gt;kas&lt;/h2&gt;
&lt;p&gt;this one is not a binding to something else, it&#39;s new from scratch, it&#39;s &lt;a href=&quot;https://github.com/kas-gui/kas&quot;&gt;kas&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;KAS, the toolKit Abstraction System, is a general-purpose GUI toolkit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;the readme has a lot of screenshots, which is always nice to see.
no tutorial, apparently, but several examples.&lt;/p&gt;
&lt;p&gt;the guts of kas are mostly macro-based, which doesn&#39;t combine well with the lack of high-level documentation, but the examples are enough to let me bullshit my way towards something almost usable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-08-21-survey-of-rust-gui-libraries-4.png&quot; alt=&quot;our sample kas application, showing a todo list&quot;&gt;&lt;/p&gt;
&lt;p&gt;why almost?
because clicking in the text entry field to give it focus causes an explosion:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;thread &#39;main&#39; panicked at &#39;called `Option::unwrap()` on a `None` value&#39;, C:&#92;Users&#92;Melody&#92;.cargo&#92;registry&#92;src&#92;github.com-1ecc6299db9ec823&#92;kas-text-0.1.3&#92;src&#92;prepared.rs:465:9&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;that&#39;s bad.
and i don&#39;t feel like chasing down why that happens, especially because my gut says &lt;a href=&quot;https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/kas-test&quot;&gt;my code&lt;/a&gt; isn&#39;t the problem.
shame, though, the widgets sure look pretty.&lt;/p&gt;
&lt;h2 id=&quot;neutrino&quot; tabindex=&quot;-1&quot;&gt;neutrino&lt;/h2&gt;
&lt;p&gt;next up we have &lt;a href=&quot;https://github.com/alexislozano/neutrino&quot;&gt;neutrino&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Neutrino is a MVC GUI framework written in Rust.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ah, &lt;a href=&quot;https://knowyourmeme.com/memes/ol-reliable&quot;&gt;ol&#39; reliable&lt;/a&gt;, MVC.
the wiki has an actual tutorial, too, which you love to see.&lt;/p&gt;
&lt;p&gt;most of the other libraries have not made me throw around &lt;code&gt;Rc&amp;lt;RefCell&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; everywhere myself, though.
but neutrino has that just all over the place.
and it gets worse than you&#39;d think.
building the same example to-do list required a &lt;code&gt;Rc&amp;lt;RefCell&amp;lt;Vec&amp;lt;Rc&amp;lt;RefCell&amp;lt;TodoItem&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; and i feel like that&#39;s bad.
it definitely makes &lt;a href=&quot;https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/neutrino-test&quot;&gt;my code&lt;/a&gt; look terrible.&lt;/p&gt;
&lt;p&gt;excitingly, we now have a demo that looks bad and also doesn&#39;t work:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-08-21-survey-of-rust-gui-libraries-5.png&quot; alt=&quot;our sample neutrino application, showing a todo list&quot;&gt;&lt;/p&gt;
&lt;p&gt;excitingly, when we type some text and hit the &amp;quot;add&amp;quot; button, the text gets lost in the created todo, and i have no goddamn clue where it&#39;s going or what to do to fix it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2020-12-14&lt;/em&gt;: also, a first so far, the native build actually provides some information to the screen reader! only some information - the checkbox labels come through, but the fact that they&#39;re attached to checkboxes doesn&#39;t - but still, it&#39;s closer than most of the other frameworks have managed to get.&lt;/p&gt;
&lt;p&gt;the approach is interesting, though.
as i&#39;m writing this neutrino is unmaintained and seeking a new maintainer, so hopefully somebody has the time and energy to steer it forwards.&lt;/p&gt;
&lt;h2 id=&quot;orbtk&quot; tabindex=&quot;-1&quot;&gt;orbtk&lt;/h2&gt;
&lt;p&gt;our next contestant is &lt;a href=&quot;https://github.com/redox-os/orbtk&quot;&gt;OrbTK&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Orbital Widget Toolkit is a cross-platform (G)UI toolkit for building scalable user interfaces with the programming language Rust.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;apparently this is attached to Redox, the OS written in Rust.
so that&#39;s neat.&lt;/p&gt;
&lt;p&gt;again, no tutorial, some examples that are far from self-explanatory.&lt;/p&gt;
&lt;p&gt;it does let us build a working todo list, and one that looks pretty nice:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-08-21-survey-of-rust-gui-libraries-6.png&quot; alt=&quot;our sample orbtk application, showing a todo list&quot;&gt;&lt;/p&gt;
&lt;p&gt;i can&#39;t for the life of me figure out how to make the text field take up the entire width available to it.
but everything works, and we get built-in support for adding the todo on Enter in the text field, which is nice.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2020-12-14&lt;/em&gt;: screen reader support is, once again, nonexistent.&lt;/p&gt;
&lt;p&gt;in theory, there&#39;s web support, but when i tried it it very loudly didn&#39;t work:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;error[E0405]: cannot find trait `StdError` in module `serde::de`
   --&gt; C:&#92;Users&#92;Melody&#92;.cargo&#92;registry&#92;src&#92;github.com-1ecc6299db9ec823&#92;serde_json-1.0.46&#92;src&#92;error.rs:317:17
    |
317 | impl serde::de::StdError for Error {
    |                 ^^^^^^^^ not found in `serde::de`&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;so yeah, we&#39;ve got some nice-looking widgets, with unintuitive layout settings, broken web support, and a &lt;em&gt;lot&lt;/em&gt; of glue i had to write by hand that makes &lt;a href=&quot;https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/orbtk-test&quot;&gt;the source code&lt;/a&gt; cluttered and messy.
don&#39;t think i&#39;d use it for anything more serious, at least as it exists right now.&lt;/p&gt;
&lt;h2 id=&quot;qmetaobject&quot; tabindex=&quot;-1&quot;&gt;qmetaobject&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The qmetaobject crate is a crate which is used to expose rust object to Qt and QML.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;i don&#39;t want to install Qt.
that sounds like a nuisance, and more importantly, if i want Travis or whatever to give me automated CI builds, i don&#39;t think it&#39;s easy to make sure Qt exists on all platforms on Travis.&lt;/p&gt;
&lt;h2 id=&quot;qt_widgets&quot; tabindex=&quot;-1&quot;&gt;qt_widgets&lt;/h2&gt;
&lt;p&gt;oh hey, more Qt API bindings!
i still don&#39;t want to install Qt.&lt;/p&gt;
&lt;h2 id=&quot;relm&quot; tabindex=&quot;-1&quot;&gt;relm&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Asynchronous, GTK+-based, GUI library, inspired by Elm, written in Rust.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;as established, GTK+ setup on Windows is a scary nightmare hellscape.&lt;/p&gt;
&lt;h2 id=&quot;rust-qt-binding-generator&quot; tabindex=&quot;-1&quot;&gt;rust-qt-binding-generator&lt;/h2&gt;
&lt;p&gt;i am so tired.&lt;/p&gt;
&lt;h2 id=&quot;sciter-rs&quot; tabindex=&quot;-1&quot;&gt;sciter-rs&lt;/h2&gt;
&lt;p&gt;i think sciter is a thing actual programs use, which is nice.
however, we need not only the sciter sdk installed and available, but also GTK+, and god damn i do not want to do that.&lt;/p&gt;
&lt;h2 id=&quot;webrender&quot; tabindex=&quot;-1&quot;&gt;WebRender&lt;/h2&gt;
&lt;p&gt;last, but hopefully not least, we have &lt;a href=&quot;https://github.com/servo/webrender&quot;&gt;webrender&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;WebRender is a GPU-based 2D rendering engine written in Rust.
Firefox, the research web browser Servo, and other GUI frameworks draw with it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;pour one out for Servo, btw.&lt;/p&gt;
&lt;p&gt;unfortunately, the &lt;a href=&quot;https://github.com/servo/webrender/blob/master/examples/basic.rs&quot;&gt;&amp;quot;basic&amp;quot; example&lt;/a&gt; is still 300+ lines of code.
so i doubt that&#39;s gonna be useful.&lt;/p&gt;
&lt;h2 id=&quot;so-are-we-gui-yet%3F&quot; tabindex=&quot;-1&quot;&gt;so &lt;em&gt;are&lt;/em&gt; we GUI yet?&lt;/h2&gt;
&lt;p&gt;well, kinda.
druid works well if you want a straightforward layout experience.
iced works well if you want a straightforward render-update architecture, or actual HTML elements on Web.
everything else is, as of today, broken and/or more complex than i want.
and if you want native ui widgets to match your platform&#39;s look and feel (&lt;em&gt;update 2020-12-14&lt;/em&gt;: or be accessible at all), that&#39;s gonna be like a year away at least.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Post-Open Source</title>
    <link href="https://www.boringcactus.com/2020/08/13/post-open-source.html" />
    <updated>2020-08-13T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2020/08/13/post-open-source.html</id>
    <content type="html">&lt;p&gt;i&#39;m writing this like a day after &lt;a href=&quot;https://www.fastcompany.com/90539632/mozilla-vows-mdn-isnt-going-anywhere-as-layoffs-cause-panic-among-developers&quot;&gt;big mozilla layoffs&lt;/a&gt; that included a lot of people working on cool and important shit.
the consensus i&#39;m seeing is that it reflects mozilla&#39;s search for profit over impact, mismanagement, and disproportionate executive compensation.
this is taking place in a larger trend of corporatization of open source over the past several years, an ongoing open source sustainability crisis, and of course COVID-19, the all-consuming crisis that makes all our other crises worse.
all of this was summed up most concisely by &lt;a href=&quot;https://twitter.com/zkat__/status/1293626135142477825&quot;&gt;Kat Marchán&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Imo, open source as a community endeavor is falling apart right before our eyes, and being replaced by open source as Big Corp entrenchment strategy.&lt;/p&gt;
&lt;p&gt;I mean it&#39;s been happening for a while, but seeing Mozilla sinking like this is just driving the point home for me.&lt;/p&gt;
&lt;p&gt;FOSS is dead&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;how did we get here?
where even are we?
what happens next?&lt;/p&gt;
&lt;p&gt;i am incredibly unqualified to answer any of this - i didn&#39;t show up until right around the peak of SourceForge, i wasn&#39;t there for most of this - but i&#39;m not gonna let that stop me.&lt;/p&gt;
&lt;h2 id=&quot;names&quot; tabindex=&quot;-1&quot;&gt;names&lt;/h2&gt;
&lt;p&gt;to start this funeral service for FOSS, we have to unpack the term itself.
&amp;quot;free and open source software&amp;quot; as a term already contains multitudes.
on one hand, &amp;quot;free software&amp;quot;, an explicitly political movement with a decidedly anti-charismatic leader.
on the other hand, &amp;quot;open source software&amp;quot;, defanged and corporate-friendly by design.
the free software people (correctly) criticize &amp;quot;open source&amp;quot; as milquetoast centrism.
the open source people (correctly) criticize &amp;quot;free software&amp;quot; as stubborn idealism fighting tooth and nail to reject the real world as it actually exists.
they have as much in common as leftists and liberals (but they&#39;re more prepared to work together), and although their short-term goals were similar enough that it made sense to lump them together (hence the cooperation), now that the movement is dead i think there&#39;s more to gain from considering them separately.
most software licenses that i&#39;m going to bring up technically qualify as both, but they&#39;re popular with one or the other, so i&#39;ll refer to &amp;quot;free software licenses&amp;quot; and &amp;quot;open source licenses&amp;quot; as licenses that are more directly tied to those movements, even though any given license likely meets both definitions.&lt;/p&gt;
&lt;p&gt;i&#39;d say free software died a while ago, and open source went horribly right.&lt;/p&gt;
&lt;h2 id=&quot;freedom&quot; tabindex=&quot;-1&quot;&gt;freedom&lt;/h2&gt;
&lt;p&gt;the free software movement, for all its faults, has always known &lt;a href=&quot;https://www.gnu.org/philosophy/free-sw.html.en&quot;&gt;what it&#39;s about&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;The freedom to run the program for any purpose.&lt;/li&gt;
&lt;li&gt;The freedom to study how the program works, and change it to make it do what you wish.&lt;/li&gt;
&lt;li&gt;The freedom to redistribute and make copies so you can help your neighbour.&lt;/li&gt;
&lt;li&gt;The freedom to improve the program, and release your improvements (and modified versions in general) to the public, so that the whole community benefits.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;it&#39;s concise, it&#39;s understandable, and it&#39;s… kinda useless.
this point was &lt;a href=&quot;https://lu.is/blog/2016/03/23/free-as-in-my-libreplanet-2016-talk/&quot;&gt;raised better by actual lawyer Luis Villa&lt;/a&gt; (Karl Marx slander notwithstanding), but those freedoms don&#39;t actually mean shit to the average end user.
only programmers care if they have access to the source code, and most people aren&#39;t programmers.
and i &lt;em&gt;am&lt;/em&gt; a programmer, and i don&#39;t give a shit.
the freedom to not think about my operating system and just get work done overrules all of those for me, so i use windows.
like, yeah, those things are all in principle nice to have, and between two otherwise equally good programs i&#39;d take the free one.
but they&#39;re really fuckin specific things, and even if i have the freedom to do them i&#39;m not likely to have the ability or desire to do them, so there&#39;s no good reason for me as a user to use software that&#39;s worse in other ways because it gives me freedoms i don&#39;t need.&lt;/p&gt;
&lt;p&gt;the free software movement is explicitly political, but its politics suck.
it&#39;s a movement by and for ideological diehards but the ideology is extremely esoteric.
theirs was a losing battle from day one.
so what was it that actually killed them?
i think in a very real way it was the GPLv3.&lt;/p&gt;
&lt;h2 id=&quot;losing&quot; tabindex=&quot;-1&quot;&gt;losing&lt;/h2&gt;
&lt;p&gt;the flagship projects of the free software movement are probably Linux and the GNU pile of tools.
the Linux kernel being released under a free software license doesn&#39;t directly create more free software, though, since even things that tie closely to the kernel aren&#39;t obligated to also be free software, and of course user-level applications can have whatever license they want.
and also most of the people using Linux right now are using it by accident, distributed as ChromeOS or Android, neither of which is free software.
so Linux is a win for the free software movement but a useless one.&lt;/p&gt;
&lt;p&gt;the GNU userland tools are, for the most part, even more underwhelming.
it may be technically more accurate to call it GNU/Linux, but the only time i remember my linux userland tools are GNU or free software at all is when there&#39;s &lt;a href=&quot;https://twitter.com/boring_cactus/status/1166408436386430976&quot;&gt;some weird inconsistency between a GNU tool and its BSD equivalent&lt;/a&gt;, and that&#39;s not exactly ideal.
gcc had, as far as i can tell, been basically &lt;em&gt;the&lt;/em&gt; C compiler for a while, if you weren&#39;t stuck with MSVC or something worse.
the free software movement were stubborn ideologues with weird priorities, but they still had one big technical advantage.
then the GPLv3 happened.&lt;/p&gt;
&lt;p&gt;the GPLv2 was pretty popular at the time, but there were a couple notable loopholes some big corporations had been taking advantage of, which the free software people wanted to close.
a whole bunch of people thought the GPLv2 was fine the way it was, though - closing the loopholes as aggressively as the GPLv3 did cut off some justifiable security measures, and some people said that it could do more harm than good.
the linux kernel, along with a lot more stuff, declared it was sticking with the GPLv2 and not moving to the GPLv3.
when your movement says &amp;quot;here is the new version of The Right Way To Do Things&amp;quot; and several of your largest adherents say &amp;quot;nah fuck you we&#39;re going with the old version&amp;quot; that is not a good sign.
around the same time, free software organizations were starting to successfully sue companies who were using free software but not complying with the license.
so big companies, like Apple, saw new restrictions coming in at the same time as more aggressive enforcement, and said &amp;quot;well shit, we want to base our software on these handy convenient tools like GCC but we can&#39;t use GPLv3 software while keeping our hardware and software as locked together as we&#39;d like.&amp;quot;
so they started pouring money into a new C compiler, LLVM, that was instead open source.&lt;/p&gt;
&lt;p&gt;and LLVM became at least as good as GCC, and a less risky decision for big companies, and easier to use to build new languages.
so the free software movement&#39;s last technical advantage was gone.
its social advantages also kinda went up in flames with the GPLv3, too: the software that was the foundation for the GPL enforcement lawsuits stuck with the GPLv2.
the discourse over that decision was so nasty that the lead maintainer (Rob Landley; he&#39;ll come up later) started an identical project which he wound up relicensing under an open source license because the lawsuits had completely backfired: instead of complying with the terms of the GPL, companies were just avoiding GPL software.&lt;/p&gt;
&lt;p&gt;the free software movement, in the end, burned itself out, by fighting for a tiny crumb of success and then turning around and lighting that success on fire.
the death of free software tells us that we can&#39;t use a license to trick corporations into sharing our values: they want to profit, and if good software has a license that puts a limit on how much they can do that, they&#39;ll put more resources into writing their own alternative than they would spend complying with the license in the first place.&lt;/p&gt;
&lt;h2 id=&quot;openness&quot; tabindex=&quot;-1&quot;&gt;openness&lt;/h2&gt;
&lt;p&gt;the open source movement manages to share the same short term goals as the free software movement but be bad in almost entirely disjoint ways.
the &lt;a href=&quot;https://opensource.org/about&quot;&gt;mission of the Open Source Initiative&lt;/a&gt; says&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Open source enables a development method for software that harnesses the power of distributed peer review and transparency of process.
The promise of open source is higher quality, better reliability, greater flexibility, lower cost, and an end to predatory vendor lock-in.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;this is so profoundly different from the free software definition that it&#39;s almost comical.
where free software says &amp;quot;we value freedom, which we define in these ways,&amp;quot; open source says &amp;quot;your code will get better.&amp;quot;
the free software movement was prepared to start fights with corporations that used their work but didn&#39;t play by their rules.
the open source movement was invented to be a friendly, apolitical, pro-corporate alternative to the free software movement.&lt;/p&gt;
&lt;p&gt;the contrast between &amp;quot;use free software because it preserves your freedom&amp;quot; and &amp;quot;use open source software because it&#39;s better&amp;quot; is profound and honestly a little disappointing to revisit this explicitly.
free software preserves freedoms i don&#39;t need or care about as a user, but it does at least do that.
open source software is frequently not in fact better than closed source alternatives, and &amp;quot;use open source software because on rare occasions it manages to be almost as good&amp;quot; is an even more underwhelming sales pitch than anything free software can give.&lt;/p&gt;
&lt;p&gt;where free software is misguided and quixotic, open source is spineless and centrist.
and as tends to happen with spineless centrism, it has eaten the world.&lt;/p&gt;
&lt;h2 id=&quot;winning&quot; tabindex=&quot;-1&quot;&gt;winning&lt;/h2&gt;
&lt;p&gt;if there&#39;s anything corporations love more than rewriting software so it lets them make all the money they can dream of, it&#39;s letting other people do that work for them.
it took a while to take off, because the conservative approach of &amp;quot;keep things closed source&amp;quot; was pretty solidly entrenched in a lot of places, but now even the once conservative holdouts have accepted the gospel of centrism.
corporations have little to nothing to lose by publishing existing source code, and can gain all sorts of unpaid volunteer labor.
if they start a new internal project, important enough that they&#39;re prepared to put effort into it but not so important that someone could run off with it and compete with them, then now they&#39;ll likely open source it.
worst case scenario, they do all the work they were already prepared to do.
best case scenario, their library turns into the single most popular library of its type, with thousands of unpaid volunteers donating their time to you.
more labor for free, community goodwill for having started the project everybody uses, the benefits if it goes well are countless.
free software is not in principle anti-corporate, but corporations are very cautious getting caught up in the free software movement, because that actually creates obligations for them.
open source gives corporations a shot at improving their code for free, so as long as they don&#39;t share so much someone could start a competitor, so there&#39;s zero reason for a corporation to not get into open source.&lt;/p&gt;
&lt;p&gt;the best part for corporations is they don&#39;t even have to be the ones to start a project.
if you&#39;re just some random small time developer, they can just show up.
you made a cool database server that&#39;s under an open source license?
amazon&#39;s selling it as a service now, and they&#39;re not paying you a fuckin dime.
you want to change your license to stop them from doing that?
now the open source people are yelling at you, because when they say they&#39;re apolitical they mean they support the status quo.
and the free software people are also yelling at you, because you didn&#39;t do it their way with their license, you did it a different way with a different license, and that goes against amazon&#39;s freedom to screw you over.&lt;/p&gt;
&lt;p&gt;github itself is arguably the epitome of the open source movement.
the platform itself is closed source, because they don&#39;t want people to compete with them running their code, and also they sell the very expensive self-hosted version to corporations.
opening up the source for github itself would take a chunk out of github&#39;s profits.
can&#39;t have that.
but they don&#39;t even need to start or adopt an open source component to profit off other people&#39;s labor: &lt;em&gt;literally every project on github&lt;/em&gt; makes github more valuable.
popular projects get people in, network effects bring their colleagues in, and then when it&#39;s time for something that you&#39;d rather have closed source you and everyone else are already on github so you might as well spring for the paid tier.
if they believed open source was in principle better, they&#39;d be open source themselves.
they believe open source is profitable for them, and corporate profit is by definition value generated by labor but not paid to the laborer.&lt;/p&gt;
&lt;p&gt;what&#39;s good for corporations is, of course, bad for people.
random individual contributors almost never get paid for their work, even when a corporation or several will profit substantially from those changes.
maintainers of vital infrastructure libraries generally only get paid if they wrote the library for or under the control of the company they worked for anyway.
professional, corporate maintainers can offer more to the community since they&#39;re getting paid for it, which heightens expectations on independent maintainers and leads to maintainer burnout.
and if a company runs off with some existing open source software, they can build their secret competitive advantage around it without giving any of that work back to the original authors.&lt;/p&gt;
&lt;p&gt;all of these individual crises are by design: this was always the endgame of the open source movement.
the free software movement was transparent with its greatest value: &amp;quot;we believe users should have the freedom to mess with and contribute to the source code of the programs they use.&amp;quot;
the open source movement had a far subtler value: &amp;quot;we believe corporations should have the freedom to exploit the labor of developers.&amp;quot;
the fact that individual developers were ever on board with the open source movement speaks to the pernicious branding it employs.
but people are starting to notice that this isn&#39;t actually good at all.&lt;/p&gt;
&lt;p&gt;the free software movement was on occasion writing actually good software; corporations saw that and wanted to get in on it without having to actually have principles.
so they embraced the nominal goals of the free software movement and extended it into a more corporate-friendly movement with a larger pile of software to draw from.
the conventional step after embrace and extend is, &lt;a href=&quot;https://en.wikipedia.org/wiki/Embrace,_extend,_and_extinguish&quot;&gt;naturally&lt;/a&gt;, extinguish.
the free software movement died long ago, in no small part due to its own mistakes, so there&#39;s not much left to extinguish.
that which is being extinguished, that which died with mozilla, is the idea that the open source movement could have any other principles than corporate exploitation.&lt;/p&gt;
&lt;p&gt;i wouldn&#39;t say that the open source movement died per se.
it was undead from the moment it began; it won, and with its victory it has stopped pretending to be anything other than a lich.
the only meaningful lesson to learn from the open source movement is that letting corporations do whatever the hell they want ends poorly, which is not exactly news.&lt;/p&gt;
&lt;h2 id=&quot;not-learning&quot; tabindex=&quot;-1&quot;&gt;not learning&lt;/h2&gt;
&lt;p&gt;open source won, and nothing got better.
in an effort to fix this feature of the open source movement, some people have chosen to repeat the mistakes of the free software movement.
as &lt;a href=&quot;https://en.wikipedia.org/wiki/Karl_Marx&quot;&gt;some smart german dude&lt;/a&gt; once said, everything in history happens twice, first as tragedy, then as farce.&lt;/p&gt;
&lt;p&gt;the free software movement declared that the user&#39;s freedom to tinker with and contribute to the software they use is supreme, and they wrote a license specifically built to preserve that in software applied to it, and to spread that freedom to software based on it.
an uninspiring but at least well-defined goal, pursued somewhat decently, with at least some lasting success.&lt;/p&gt;
&lt;p&gt;the &amp;quot;ethical source&amp;quot; movement declares that the UN&#39;s Universal Declaration of Human Rights is supreme, with relevant laws in whatever jurisdiction is relevant a close second, and the Hippocratic License says &amp;quot;if the software author says you&#39;re violating human rights you have to go through public arbitration or the license is void.&amp;quot;
the goal is at least in principle better, so that&#39;s something, at least.
although i will say, if someone releases a data visualization library under the Hippocratic License and someone else uses that library to display leaked personal information of police officers who got away with murder, there are several articles of the Universal Declaration of Human Rights that&#39;d arguably be violated, so the library author would likely have grounds to make a nuisance of themself.
and that sucks shit.
the fact that the website for the Hippocratic License is &lt;code&gt;firstdonoharm.dev&lt;/code&gt; kinda gives the whole thing away, because sometimes a little harm in one way prevents a much greater harm in some other way.
there&#39;s a reason doctors don&#39;t use the hippocratic oath anymore.&lt;/p&gt;
&lt;p&gt;even setting that aside, there&#39;s a far greater issue with the Hippocratic License.
show me a corporate lawyer who&#39;ll look at a license that says &amp;quot;i can drag you into arbitration proceedings that have to be public whenever i want and there&#39;s no consequences for me doing that in bad faith&amp;quot; and say &amp;quot;yeah that looks good, we can use this library&amp;quot; and i&#39;ll show you a corporate lawyer who&#39;s gonna get fired tomorrow.
the free software movement tried and failed to use a license to trick corporations into sharing their values.
the ethical source movement appears to be trying to use a worse license to trick corporations into sharing less concretely defined values.&lt;/p&gt;
&lt;p&gt;until all the talented people in that community start doing more useful things with their time, we can at least learn a few things from this preemptive failure.
one, trying to bake the complexity of an ethical system into your license is a fool&#39;s errand that will not go well.
two, if you&#39;re writing a license to coerce companies into behaving differently, don&#39;t scare them off right out of the gate with a poorly considered enforcement system.&lt;/p&gt;
&lt;h2 id=&quot;options&quot; tabindex=&quot;-1&quot;&gt;options&lt;/h2&gt;
&lt;p&gt;the term &amp;quot;post-open source&amp;quot; apparently was used by a couple people in like 2012 to refer to just not giving your code a license.
it&#39;s got a &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Post_open_source&amp;amp;oldid=890953566&quot;&gt;wikipedia page&lt;/a&gt; that&#39;s had the &amp;quot;this might not be notable enough for wikipedia&amp;quot; box applied to it since 2013.
i am declaring that Basically Dead and so i&#39;m using that term in a broader way now.&lt;/p&gt;
&lt;p&gt;so what do we do after open source has eaten the world?
the retro option, apparently, is to skip the license entirely.
it&#39;ll scare off the corporations, since they technically can&#39;t safely use your work if you maintain full copyright.
and as actual lawyer Luis Villa &lt;a href=&quot;https://lu.is/blog/2013/01/27/taking-post-open-source-seriously-as-a-statement-about-copyright-law/&quot;&gt;pointed out at the time&lt;/a&gt;, the idea that you need to give other people permission to do things like modify your code for themselves is something we shouldn&#39;t automatically take for granted.
(although i must say, for someone who claims to hate &amp;quot;permission culture&amp;quot; so much, Nina Paley sure does seem concerned with giving people permission to count as women.
TERFs fuck off, now and forever.)
not using a license at all can be interpreted as a conscious rejection not just of copyright but also of the endeavor to wield copyright as a tool for justice at all.&lt;/p&gt;
&lt;p&gt;however, not using a license at all also makes it complicated for actual human beings who want to use your software.
Villa points to a favorite of mine, the &lt;a href=&quot;http://www.wtfpl.net/&quot;&gt;Do What The Fuck You Want To Public License&lt;/a&gt;, as a way to make the implicit permissiveness of rejecting licensing altogether explicit while preserving the anti-serious aspect.
however, once the corporations realize that they&#39;re allowed to use software that says fuck, they can and will exploit the shit out of WTFPL software, so this does not provide a long term solution for the problems with open source.
(it is, however, really good, so i will count it as post-open source at heart even though it is essentially just open source).
its nominally equivalent but more serious cousin, &lt;a href=&quot;http://landley.net/toybox/license.html&quot;&gt;zero-clause BSD&lt;/a&gt;, was written by the same Rob Landley whose experience navigating GPLv2 vs GPLv3 was so unpleasant back in the day; it&#39;s no fun, and i wouldn&#39;t call it a post-open source license, but it is in a very real way a post-free software license, and the exact opposite of the GPL.
and in fairness i&#39;d be trying to write the opposite of the GPL after that mess too.&lt;/p&gt;
&lt;p&gt;the ethical source people are trying to use the hippocratic license to make it illegal to use certain software if you&#39;re doing bad things.
the issues with that were the broad definition of &amp;quot;bad things&amp;quot; and the weird enforcement provisions.
you can take both of those to the other extreme and get the &lt;a href=&quot;https://www.json.org/license.html&quot;&gt;JSON License&lt;/a&gt;.
it&#39;s just a regular MIT/BSD/X11/whatever permissive license but with an extra caveat:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Software shall be used for Good, not Evil.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;now, this is basically decorative (although evidently IBM paid the author for permission to do evil with that software, which is &lt;em&gt;fucking beautiful&lt;/em&gt;), but it does also scare off corporations while letting normal people do whatever.
i actually had a &lt;a href=&quot;https://twitter.com/boring_cactus/status/1090803883230679040&quot;&gt;brief twitter exchange&lt;/a&gt; with the unparalleled jenn schiffer about the effectiveness of the json license a while back, but she understandably doesn&#39;t let ancient tweets linger forever, so whatever actual points were made there are lost to time.
it does at least manage to solve the problems with the hippocratic license, though: the definition of evil is left completely implicit anyway, and the mechanism for enforcement is just copyright law like with any old license.
now, since the vagueness is left implicit, there&#39;s room to argue that the clause is unenforceable.
nobody has tested it, but that&#39;s a loophole waiting to be exploited, and also it&#39;s not as fun as the WTFPL.
as such, right before i started writing this blog post i wrote the &lt;a href=&quot;https://git.sr.ht/~boringcactus/fafol/tree/master/LICENSE-v0.1.md&quot;&gt;fuck around and find out license v0.1&lt;/a&gt; (or FAFOL for short), which replaces the json license&#39;s ethics disclaimer with something more clear:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;the software shall be used for Good, not Evil. the original author of the software retains the sole and exclusive right to determine which uses are Good and which uses are Evil.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;now it is unambiguous in its intent, and also, it says fuck in the title.
as such, it is the only good software license.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2020-08-17&lt;/em&gt;: i have set up &lt;a href=&quot;https://git.sr.ht/~boringcactus/fafol/tree/master/README.md&quot;&gt;broader infrastructure around the Fuck Around and Find Out License&lt;/a&gt;, if you&#39;re interested.&lt;/p&gt;
&lt;p&gt;on a more sincere note, some licenses are trying to solve the problem of corporate exploitation by bringing back into fashion the idea of public-private licenses, where the default license is principled and corporations can simply pay for an exception and be covered by a different license instead.
the most interesting of these projects, at least as of August 2020, is &lt;a href=&quot;https://licensezero.com/&quot;&gt;license zero&lt;/a&gt;, run by actual lawyer Kyle E. Mitchell, which offers two different public licenses, one standard private license template, and infrastructure for automatically selling exceptions.
their Parity license is a share-alike license that allows any use that is also published under an open license.
their Prosperity license allows any use as long as it is not commercial in nature; as such, it technically doesn&#39;t satisfy the Open Source Definition and is thus in a very concrete sense a post-open source license.
their Patreon license, which isn&#39;t linked on their homepage at all, grants an automatic license exception for certain financial supporters.
Kat Marchán, whose tweet i opened this blog post with, has a &lt;a href=&quot;https://dev.to/zkat/a-system-for-sustainable-foss-11k9&quot;&gt;blog post of their own&lt;/a&gt; explaining one approach to using license zero&#39;s tools as a solution to the open source sustainability crisis.
license zero the project appears to be currently working through some branding issues, and might have a different name and structure by the time you&#39;re reading this.
but as it stands it&#39;s at least relevant.&lt;/p&gt;
&lt;p&gt;an additional concept comes once more from actual lawyer Luis Villa, who was &lt;a href=&quot;https://lu.is/blog/2016/09/26/public-licenses-and-data-so-what-to-do-instead/&quot;&gt;talking about data and not code&lt;/a&gt; when he said all this, but i think it can be applied to code too.
i&#39;ll let him summarize his post himself:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;tl;dr: say no to licenses, say yes to norms.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;a license is a tool of the law, but the law is not actually very good at delineating the exact boundaries of ethical behavior (in either direction).
as such, the approach Villa describes is to tell the law to mind its own damn business and use a maximally permissive license, and then use social norms to delineate what behavior you do and do not find acceptable.
norms are tough to start from scratch, but sociologically they can fill a similar role in principle to laws while maintaining flexibility.
i&#39;m not quite sure what a normative approach to post-open source software would look like - i&#39;m not aware of anyone attempting to implement it, and i&#39;m not sure i&#39;m ready to be the first - but most likely it&#39;d combine the WTFPL (or, more plausibly, zero-clause BSD) with an ecosystem of standard sets of norms similar to the current varieties in codes of conduct.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2020-08-15&lt;/em&gt;: actual lawyer Kyle E. Mitchell proposed an implementation of this approach in early 2019 and i think everyone should go read &lt;a href=&quot;https://writing.kemitchell.com/2019/03/15/Ethical-Subcommons.html&quot;&gt;that proposal&lt;/a&gt; right now and then come back to this blog post.&lt;/p&gt;
&lt;p&gt;and since i&#39;ve been quoting him the whole time, i should probably also give a shout out to actual lawyer Luis Villa&#39;s current project, &lt;a href=&quot;https://tidelift.com/&quot;&gt;Tidelift&lt;/a&gt;, which is trying to address open source funding at both ends.
for corporate clients, (it looks like) tidelift is selling known-good, actively maintained, secure dependency subscriptions, and for open source maintainers, they&#39;re (i think) offering not just a proportional cut of the subscription revenue but also resources for keeping projects good, maintained, and secure.
i haven&#39;t used it on either end, i&#39;m just paraphrasing their marketing copy, but they do exist.&lt;/p&gt;
&lt;h2 id=&quot;evaluation&quot; tabindex=&quot;-1&quot;&gt;evaluation&lt;/h2&gt;
&lt;p&gt;so those are some options for what we do next.
which ones are good?
here we venture even deeper into my arbitrary, poorly informed, untrustworthy opinion than we already were.&lt;/p&gt;
&lt;p&gt;rejecting licenses altogether is fun but feels kinda halfhearted.
like, if you think licenses are a waste of time, you can just use the WTFPL and get in principle the same effect but with more gratuitous profanity.
also, you can&#39;t refer to software that doesn&#39;t have a license as &amp;quot;unlicensed&amp;quot; because some chucklefucks decided to make a license called The Unlicense and so it&#39;s ambiguous now.
&amp;quot;license-free&amp;quot; just doesn&#39;t have the same ring to it.
and more concretely, if you have a real organization that wants to do actual good with your work, they probably still have a lawyer telling them copyright exists, and it&#39;d be good to give them explicit permission to be doing good things.&lt;/p&gt;
&lt;p&gt;the zero-clause BSD license is the most open of open source licenses, and as such it inherits all the issues of the open source movement it is attached to.
it&#39;s essentially the same as releasing your software to the public domain, skipping copyright entirely and giving basically unlimited permissions, and therefore providing no protection from exploitation.
the WTFPL is in practice probably equivalent, but in theory scares off lawyers by being impossible to take seriously.
however, i remember when i got into dogecoin back when dogecoin was a Thing i thought it was impossible to take seriously and therefore immune from the cryptocurrency true believers, and that wound up pretty decidedly not happening.
so a surface-level anti-serious tone is not foolproof protection against bad things.
i&#39;ve released software under the WTFPL, and i&#39;d likely do it again for code i have no interest in maintaining for other people&#39;s benefit, but i would never use it as a license for a library i wanted other people to use.&lt;/p&gt;
&lt;p&gt;if i recall correctly, the JSON license was explicitly intended as a jab at corporate lawyers, and so it is close in spirit to the WTFPL.
and my FAFOL makes that harmony even greater.
but as we (or at least some of us) learned from the free software movement, using a license as a tool of enacting corporate ethics by proxy is essentially impossible; corporations interpret ethics as damage and route around them.
so as funny as it is, it&#39;s not actually useful.&lt;/p&gt;
&lt;p&gt;license zero&#39;s work has a chance to succeed at creating funding opportunities for software maintainers.
if you&#39;re going to want to cut into a corporation&#39;s profits, the best way that can go for you is if they can budget in the cost of your exception.
i&#39;m not aware of instances of that actually working super well for anybody, but maybe it&#39;s happened and i haven&#39;t heard, or maybe it&#39;ll happen as time goes on.
same goes for tidelift, honestly.
trying to solve specifically the maintainer-side economic issues is an approach i&#39;m not qualified to evaluate, but it definitely feels more like a medium-term patch to the open source movement than a long-term fix.&lt;/p&gt;
&lt;p&gt;i think the normative approach is at least in principle worth exploring, and maybe the next project i release will be stapled to an experiment with that.
it definitely feels like it has potential, and could actually supplant the open source model if it works well.
plus the idea of replacing laws with explicit but informal expectations and letting the community self-regulate warms my anarchist heart.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;conclusion&lt;/h2&gt;
&lt;p&gt;FOSS is dead.
free software died long ago, and open source software was a lich the whole time, only now claiming victory and beginning to pull up the ladder behind it.
what will come next?&lt;/p&gt;
&lt;p&gt;i can&#39;t predict every aspect of the post-open source movement, but i can tell you one thing it&#39;ll absolutely require if it&#39;s going to be meaningful.&lt;/p&gt;
&lt;p&gt;what really killed mozilla?
what really killed free software?
what really gave us the already-dead open source movement?&lt;/p&gt;
&lt;p&gt;optimizing for profit at the expense of any other consideration.
chasing short-term gains and ignoring long-term sustainability or justice.
squeezing every drop of surplus value out of every person within reach and putting it in the hands of a dozen investors and overpaid executives.&lt;/p&gt;
&lt;p&gt;in a word, capitalism.&lt;/p&gt;
&lt;p&gt;if post-open source wants to not die the same death, it will need to explicitly and aggressively fight its greatest existential threat.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Monads, Explained Without Bullshit</title>
    <link href="https://www.boringcactus.com/2020/07/18/monads-without-the-bullshit.html" />
    <updated>2020-07-18T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2020/07/18/monads-without-the-bullshit.html</id>
    <content type="html">&lt;p&gt;&lt;em&gt;edit 2025-03-04&lt;/em&gt;: this explanation is bad in the same way that most monad explanations are bad: it doesn’t address why the abstraction of “monad” is valuable.
the first explainer i’ve seen that actually explains why it’s worth understanding is &lt;a href=&quot;https://sunshowers.io/posts/monads-through-pbt/&quot;&gt;Demystifying monads in Rust through property-based testing&lt;/a&gt;; go read that instead.&lt;/p&gt;
&lt;p&gt;there&#39;s a CS theory term, &amp;quot;monad,&amp;quot; that has a reputation among extremely online programmers as being a really difficult to understand concept that nobody knows how to actually explain.
for a while, i thought that reputation was accurate; i tried like six times to understand what a monad is, and couldn&#39;t get anywhere.
but then a friend sent me &lt;a href=&quot;https://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf&quot;&gt;Philip Wadler&#39;s 1992 paper &amp;quot;Monads for functional programming&amp;quot;&lt;/a&gt; which turns out to be a really good explanation of what a monad is (in addition to a bunch of other stuff i don&#39;t care about).
so i&#39;m gonna be repackaging the parts i like of that explanation here.&lt;/p&gt;
&lt;p&gt;math jargon is pretty information-dense for me, though, and my eyes tend to glaze over pretty quickly, so i&#39;ll be using &lt;a href=&quot;https://www.rust-lang.org&quot;&gt;Rust&lt;/a&gt; (or an idealized version thereof) throughout this post instead of math.&lt;/p&gt;
&lt;p&gt;so, a monad is a specific kind of type, so we can think of it like a &lt;code&gt;trait&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Monad&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// TODO&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rust has two types that will be helpful here, because (spoilers) it turns out they&#39;re both monads: &lt;code&gt;Vec&lt;/code&gt; and &lt;code&gt;Option&lt;/code&gt;.
now, if you&#39;ve worked with Rust before, you might be thinking &amp;quot;wait, don&#39;t you mean &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt;?&amp;quot; and that&#39;s a reasonable question to ask, since Rust doesn&#39;t really let you just say &lt;code&gt;Vec&lt;/code&gt; or &lt;code&gt;Option&lt;/code&gt; by themselves.
but as it happens, the monad-ness applies not to a specific &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; but to &lt;code&gt;Vec&lt;/code&gt; itself, and the same goes for &lt;code&gt;Option&lt;/code&gt;.
which means what we&#39;d like to do is say&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Monad&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Vec&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Monad&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Option&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but Rust won&#39;t let us do that because we can&#39;t talk about &lt;code&gt;Vec&lt;/code&gt;, only &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt;.
this is (part of) why Rust doesn&#39;t have monads.
so let&#39;s just kinda pretend that&#39;s legal Rust and move on.
what operations make a monad a monad?&lt;/p&gt;
&lt;h2 id=&quot;new&quot; tabindex=&quot;-1&quot;&gt;new&lt;/h2&gt;
&lt;p&gt;Wadler calls this operation &lt;code&gt;unit&lt;/code&gt;, and Haskell calls it &lt;code&gt;return&lt;/code&gt;, but i think it is easier to think of it as &lt;code&gt;new&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Monad&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;new&lt;/code&gt; takes a &lt;code&gt;T&lt;/code&gt; and returns an instance of whatever monad that contains that &lt;code&gt;T&lt;/code&gt;.
it&#39;s pretty straightforward to implement for both &lt;code&gt;Option&lt;/code&gt; and &lt;code&gt;Vec&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Monad&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Option&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Monad&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Vec&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;we started out with a stuff, and we made an instance of whatever monad that contains that stuff.&lt;/p&gt;
&lt;h2 id=&quot;flat_map&quot; tabindex=&quot;-1&quot;&gt;flat_map&lt;/h2&gt;
&lt;p&gt;Wadler calls it &lt;code&gt;*&lt;/code&gt;, Haskell calls it &amp;quot;bind&amp;quot; and spells it &lt;code&gt;&amp;gt;&amp;gt;=&lt;/code&gt;, but i think &lt;code&gt;flat_map&lt;/code&gt; is the best name for it.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Monad&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;flat_map&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; operation&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;we have an instance of our monad containing data of some type &lt;code&gt;T&lt;/code&gt;, and we have an operation that takes in a &lt;code&gt;T&lt;/code&gt; and returns the same kind of monad containing a different type &lt;code&gt;U&lt;/code&gt;.
we get back our monad containing a &lt;code&gt;U&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;as you may have guessed by how i named it, &lt;code&gt;flat_map&lt;/code&gt; is basically just &lt;code&gt;Iterator::flat_map&lt;/code&gt;, so implementing it for &lt;code&gt;Vec&lt;/code&gt; is fairly straightforward.
for &lt;code&gt;Option&lt;/code&gt; it&#39;s literally just &lt;code&gt;and_then&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Monad&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Option&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;flat_map&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; operation&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;and_then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;operation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Monad&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Vec&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;flat_map&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; operation&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;into_iter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flat_map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;operation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;so in theory, we&#39;re done.
we&#39;ve shown the operations that make a monad a monad, and we&#39;ve given their implementations for a couple of trivial monads.
but not every type implementing this trait is really a monad: there are some guarantees we need to make about the behavior of these operations.&lt;/p&gt;
&lt;h2 id=&quot;monad-laws&quot; tabindex=&quot;-1&quot;&gt;monad laws&lt;/h2&gt;
&lt;p&gt;(written with reference to &lt;a href=&quot;https://wiki.haskell.org/Monad_laws&quot;&gt;the relevant Haskell wiki page&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;like how there&#39;s nothing in Rust itself to ensure that your implementation of &lt;code&gt;Add&lt;/code&gt; doesn&#39;t instead multiply, print a dozen lines of nonsense, or delete System32, the type system is not enough to guarantee that any given implementation of &lt;code&gt;Monad&lt;/code&gt; is well-behaved.
we need to define what a well-behaved implementation of &lt;code&gt;Monad&lt;/code&gt; does, and we&#39;ll do that by writing functions that assert our &lt;code&gt;Monad&lt;/code&gt; implementation is reasonable.
we&#39;re going to have to also cheat a bit here and deviate from actual Rust by using &lt;code&gt;assert_eq!&lt;/code&gt; to mean &amp;quot;assert equivalent&amp;quot; and not &amp;quot;assert equal&amp;quot;; that is, the two expressions should be interchangeable in every context.&lt;/p&gt;
&lt;p&gt;first off, we have the &amp;quot;left identity,&amp;quot; which says that passing a value into a function through &lt;code&gt;new&lt;/code&gt; and &lt;code&gt;flat_map&lt;/code&gt; should be the same as passing that value in directly:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;assert_left_identity_holds&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Monad&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7u8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// this should hold for any value&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; f &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;i16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// this should hold for any function&lt;/span&gt;
    &lt;span class=&quot;token macro property&quot;&gt;assert_eq!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flat_map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;next, we have the &amp;quot;right identity,&amp;quot; which says that &amp;quot;and then make a new monad instance&amp;quot; should do nothing to a monad instance:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;assert_right_identity_holds&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Monad&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; m &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;&#39;z&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// this should hold for any instance of M&lt;/span&gt;
    &lt;span class=&quot;token macro property&quot;&gt;assert_eq!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flat_map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;new&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; m&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and last but by no means least we have associativity, which says it shouldn&#39;t matter the sequence in which we apply &lt;code&gt;flat_map&lt;/code&gt; as long as the arguments stay in the same order:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;assert_associativity_holds&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Monad&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; m &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// this should hold for any instance of M&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; f &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; data &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3usize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7usize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// this should hold for any function&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; g &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token macro property&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// this should hold for any function&lt;/span&gt;
    &lt;span class=&quot;token macro property&quot;&gt;assert_eq!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flat_map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flat_map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flat_map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flat_map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;so now we can glue all those together and write a single function that ensures any given monad actually behaves as it should:&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;assert_well_behaved_monad&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Monad&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assert_left_identity_holds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assert_right_identity_holds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assert_associativity_holds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;but.-why&quot; tabindex=&quot;-1&quot;&gt;but. why&lt;/h2&gt;
&lt;p&gt;well.
monads exist in functional programming to encapsulate state in a way that doesn&#39;t explode functional programming (among other things, please do not @ me).
Rust isn&#39;t a functional programming language, so we have things like &lt;code&gt;mut&lt;/code&gt; to handle state.&lt;/p&gt;
&lt;p&gt;there&#39;s a bit of discussion in Rust abt how monads would be actually implemented - the hypothetical extended Rust that i use here is not actually what anyone advocates for, you can look around for yourself if you care - but even the people in that discussion seem to not really explain why Rust needs monads.
so all of this doesn&#39;t really build up to anything.
but hey, now (with luck) you understand what monads are!
i hope you find that rewarding for its own sake.
i hope i do, too.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>What, Then, Shall We Do?</title>
    <link href="https://www.boringcactus.com/2020/07/15/what-then-shall-we-do.html" />
    <updated>2020-07-15T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2020/07/15/what-then-shall-we-do.html</id>
    <content type="html">&lt;p&gt;america is at a crossroads of sorts.
widespread dissatisfaction with how governments at all levels handled the COVID-19 pandemic, decades of police brutality overwhelmingly targeting POC, two presidential candidates nobody (or at least nobody worth a damn) really likes.
we (by which i mean the american left, broadly speaking) see these futures unfold before us, and ask ourselves &amp;quot;what, then, shall we do?&amp;quot;
i&#39;m just some random dipshit, i&#39;m not qualified to give an answer, but i can think about what we shall do specifically when it comes to the upcoming presidential election.&lt;/p&gt;
&lt;h2 id=&quot;the-very-marginally-lesser-of-two-evils&quot; tabindex=&quot;-1&quot;&gt;the very marginally lesser of two evils&lt;/h2&gt;
&lt;p&gt;obviously voting for donald trump is inexcusable.
he&#39;s a deranged senile incestuous racist rapist whose occasional halfhearted gestures towards the working class are transparently borne of a desire for self-preservation rather than any actual good will.
he may occasionally claim to be an ally for queer people, or people of color, or women, but his record stands in contrast to any of these claims - he has done more material harm to these groups than he has even bothered to acknowledge - and so only profound gullibility could lead anyone to believe this.
even the most negligible support of him is also a tacit acceptance of the entire political system that allowed him to gain what power he already has, and trusting that system to hold him in check is foolish.&lt;/p&gt;
&lt;p&gt;oh shit did i say donald trump there?
i meant joe biden.
and also donald trump.&lt;/p&gt;
&lt;h2 id=&quot;the-least-of-a-dozen-evils&quot; tabindex=&quot;-1&quot;&gt;the least of a dozen evils&lt;/h2&gt;
&lt;p&gt;so joe biden sucks, and we cannot start ridin with biden.
the democratic primary was what could generously be described as a mess, with the maybe-depending-on-who-you-ask winner of the first primary dropping out early to try to donate his momentum to an even more useless, even more racist moderate.
the party establishment threatened to punish states that postponed their primaries in light of an ongoing pandemic that made it unsafe to vote, contributing to widespread voter suppression.
clearly the democratic party is no friend to the left, and cannot be trusted.
so what other parties can we support?&lt;/p&gt;
&lt;p&gt;well, about that.
let&#39;s look at the &lt;a href=&quot;https://en.wikipedia.org/wiki/Third-party_and_independent_candidates_for_the_2016_United_States_presidential_election&quot;&gt;third parties that were around in 2016&lt;/a&gt;.
the libertarian party is - setting aside the libsoc caucus, who are probably cool but wasting their time - just for conservatives who want to smoke weed, and the constitution party is for religious conservatives who are too cool for the republican party, so neither of those is worth a damn.
we can also ignore the tiny parties that only exist in like three states.
that leaves us with like two third parties that could conceivably be worth caring about.&lt;/p&gt;
&lt;p&gt;most notably, we&#39;ve got the green party.
on the ballot almost everywhere, which is nice.
they got like 2.5% of national votes back in 2000, and they cleared 1% in 2016.
their 2020 nominee is Howie Hawkins, who apparently worked with Murray Bookchin for a while, and as a communalist myself i gotta say that&#39;s pretty epic.
however.
according to somebody who was running in the green primary and lost, there were some party-level shenanigans pulled to swing the process towards hawkins - local parties officially hosting hawkins&#39; campaign events before the primary was held, party officials also being hawkins campaign staff, etc.
considering that a lot of ppl are mad at the democratic party for pulling the same shit, that&#39;s not great.
and also hawkins has been kinda both sidesy abt antivaxx stuff, which is cringe.
you&#39;re allowed to not care if you want, but i&#39;m not sure &amp;quot;get the greens past the 5% threshold so they get federal funding in 2024&amp;quot; is really a capital-S Solution.&lt;/p&gt;
&lt;p&gt;in the other corner we have the Party for Socialism and Liberation.
socialism and liberation are both cool things to have.
also their vice presidential candidate is Leonard Peltier, a political prisoner allegedly involved in the murder of two FBI agents, so that&#39;s pretty based.
however, they were only on the ballot in a handful of states in 2016, so they may or may not even have enough reach to be worth caring about even if they nominated Marx and Engels themselves.
also even in the states where they exist they haven&#39;t managed to achieve any meaningful electoral results.
and one of their most prominent members, Michael Prysner, is a military intelligence war crimes doer; transformative justice is a cool thing and all but for fuck&#39;s sake assume military intelligence dudes are compromised six ways from sunday and don&#39;t let them have major influence in your vanguard.
again, feel free to not care, but i do.&lt;/p&gt;
&lt;p&gt;there are non-political-party organizations like the DSA, which i&#39;m a dues-paying member of, but i don&#39;t think pivoting one of them into a political party or spinning up something new would really help either.
ballot access is key to making a case for having a chance to win despite being a third party, and even the DSA couldn&#39;t get that in most states on a shorter than several years scale.&lt;/p&gt;
&lt;p&gt;so.
the green party is recreating the favoritism that was part of why we&#39;re pissed off at the democratic party, and the PSL is hanging out with war criminals while being missing or useless everywhere, and anything else will take eons to get off the ground and so doesn&#39;t give us anything to do in the meantime.
what, then, shall we do?&lt;/p&gt;
&lt;h2 id=&quot;no-fate-but-what-we-make-for-ourselves&quot; tabindex=&quot;-1&quot;&gt;no fate but what we make for ourselves&lt;/h2&gt;
&lt;p&gt;maybe electoralism is a waste of energy.
maybe voting is a waste of time.
when we vote, we don&#39;t get shit.
when we take to the streets and burn shit down, we are heard.&lt;/p&gt;
&lt;p&gt;politicians already don&#39;t feel obligated to try to earn your vote.
make them earn peace.
they should be afraid of us, them and the cops and everyone who works to defend and prop up the indefensible on a daily basis.
there are more of us than there are of them.
violence is bad but complacency is worse.
in the 60s it was &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Ballot_or_the_Bullet&quot;&gt;the ballot or the bullet&lt;/a&gt;, and comrades, i don&#39;t think the ballot has done what it needed to.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setting Up A Police Scanner With An RTL-SDR</title>
    <link href="https://www.boringcactus.com/2020/06/26/police-scanner-setup.html" />
    <updated>2020-06-26T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2020/06/26/police-scanner-setup.html</id>
    <content type="html">&lt;p&gt;so my city has a community-run police scanner broadcast on the internet, but
the person who runs it is a bootlicker who&#39;s been threatening to shut it down
if people are using it to make trouble for the cops. so i figured i&#39;d set up my
own. this is how i did it, hope it&#39;s useful.&lt;/p&gt;
&lt;h2 id=&quot;shopping&quot; tabindex=&quot;-1&quot;&gt;shopping&lt;/h2&gt;
&lt;p&gt;you&#39;ll need &lt;a href=&quot;https://www.rtl-sdr.com/buy-rtl-sdr-dvb-t-dongles/&quot;&gt;an RTL-SDR unit&lt;/a&gt;. i recommend the dipole antenna kit as
well, so you don&#39;t need to make any additional purchases. if you&#39;re a radio
enthusiast already, you might have a better antenna available, but if you&#39;re
like me you do not and it&#39;s worth the US$10. mine took a bit over a week to
arrive. if you&#39;re extremely unlucky, you might need two of them, but i was fine
with just one.&lt;/p&gt;
&lt;h2 id=&quot;basic-setup&quot; tabindex=&quot;-1&quot;&gt;basic setup&lt;/h2&gt;
&lt;p&gt;once your RTL-SDR arrives, you&#39;ll want to put together your antenna. if you&#39;re
lucky, like i am, you can just extend the antennas arbitrarily and it&#39;ll work
fine; if you&#39;re cursed, the RTL-SDR website has resources on how long is ideal
for various frequencies.&lt;/p&gt;
&lt;p&gt;connect the antenna to the RTL-SDR unit, plug it in, and follow the RTL-SDR
&lt;a href=&quot;https://www.rtl-sdr.com/rtl-sdr-quick-start-guide/&quot;&gt;quick start guide&lt;/a&gt;. SDRSharp will work, or any of the other Windows
options. some of what we&#39;ll need is only available on windows.&lt;/p&gt;
&lt;p&gt;once your RTL-SDR&#39;s drivers are sorted out, find the specifications for police
radio in your area on &lt;a href=&quot;https://www.radioreference.com/apps/db/&quot;&gt;RadioReference&lt;/a&gt;. click your state, click your county,
scroll down and see if there&#39;s a link above a frequency table for you. if
you&#39;re lucky, there is, and if you click it there&#39;s a page with a table with
System Type and System Voice entries at the top. mine has a system type of
EDACS Networked Standard and a system voice of ProVoice and Analog, so the rest
of this assumes that&#39;s what you&#39;ve got as well. if not, good luck.&lt;/p&gt;
&lt;p&gt;there should be a table for System Frequencies on your RadioReference page.
start up SDRSharp and tune your radio to the first frequency listed there.
you&#39;ll probably hear a bunch of static and the UI will look something like
this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-06-26-police-scanner-setup-1.png&quot; alt=&quot;one constant signal and a bunch of other signals coming in and out at other frequencies&quot;&gt;&lt;/p&gt;
&lt;p&gt;see how there&#39;s one constant signal and a bunch of other signals that appear
and disappear all over the place? well, that&#39;s trunking, and the constant
signal is our &lt;em&gt;control channel&lt;/em&gt;. if you don&#39;t see it, you can click and drag on
the bottom axis of the top panel to change the view. once you&#39;ve found that
constant signal, click on it to get the approximate frequency, go back to your
frequency table and the closest thing to that will be the exact frequency.
it should sound like a series of weird beeps instead of static. remember that
frequency, it&#39;ll be important later.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2020-07-31&lt;/em&gt;: that control channel can change between the frequencies
listed on RadioReference. if things randomly quit working, come back to this
step, and see if the control channel has moved. i&#39;ll mark down below the
places that need changing accordingly.&lt;/p&gt;
&lt;h2 id=&quot;specific-setup&quot; tabindex=&quot;-1&quot;&gt;specific setup&lt;/h2&gt;
&lt;p&gt;EDACS is a trunked system, so we&#39;re using &lt;a href=&quot;https://www.rtl-sdr.com/rtl-sdr-tutorial-following-trunked-radio-unitrunker/&quot;&gt;RTL-SDR&#39;s trunked radio tutorial&lt;/a&gt;
as our guide, mostly. that guide assumes we have two RTL-SDRs, but there&#39;s a
piece at the end explaining how to do it with just one. that sucks. i&#39;m going
to paraphrase it here.&lt;/p&gt;
&lt;p&gt;first, we&#39;re going to download the software we need: &lt;a href=&quot;http://www.unitrunker.com/&quot;&gt;Unitrunker&lt;/a&gt;,
&lt;a href=&quot;http://www.vb-cable.com/&quot;&gt;VB-Cable&lt;/a&gt;, and &lt;a href=&quot;https://www.dsdplus.com/&quot;&gt;DSD+&lt;/a&gt; (extract both the regular and DLL downloads to the
same folder). install unitrunker and VB-Cable and extract dsd+ somewhere
convenient. you might need to reboot after installing VB-Cable because
computers are bad. VB-Cable might set your default input and output devices to
the wrong things when you install it, so switch them back if it does.&lt;/p&gt;
&lt;p&gt;open up dsd+. it&#39;ll open four different windows, one of them should have a list
of audio input and output devices. check the number in the input list that goes
with CABLE Output - for me it&#39;s 3. pull up notepad and make a new file. since
my input was number 3, i&#39;m typing&lt;/p&gt;
&lt;pre class=&quot;language-batch&quot;&gt;&lt;code class=&quot;language-batch&quot;&gt;&lt;span class=&quot;token command&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;DSDPlus&lt;/span&gt;.exe -i3M&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in that file: if yours is not 3, put whatever the correct number is for you
instead of 3. then, save the file, find your DSDPlus folder, make sure the type
is set to &amp;quot;All Files&amp;quot;, and name the file &lt;code&gt;run.bat&lt;/code&gt;. close dsd+, go to that
folder, and open that &lt;code&gt;run.bat&lt;/code&gt; file you just created. it should pull up dsd+
and if you&#39;re lucky it&#39;ll print&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;audio input device #3 (CABLE Output (VB-Audio Virtual ) initialized&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or something like that. leave that open.&lt;/p&gt;
&lt;p&gt;open up unitrunker. click the &lt;code&gt;+&lt;/code&gt; to add a new receiver, and click the RTL2832
button to add your RTL-SDR. set your settings around like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-06-26-police-scanner-setup-2.png&quot; alt=&quot;the RTL-SDR settings&quot;&gt;&lt;/p&gt;
&lt;p&gt;the most important things are the RTL Device, the sample rate (2.56 msps), and
the VCOs (2 VCOs). i do not know what a VCO is and i do not care enough to find
out. we should now have two VCO tabs next to our info tab. the first one needs
to look kinda like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-06-26-police-scanner-setup-3.png&quot; alt=&quot;the RTL-SDR VCO 1 settings&quot;&gt;&lt;/p&gt;
&lt;p&gt;the important things are the Role being Signal, the Park frequency being the
control channel we found earlier (mine is 851.7625), and the Mute box being
checked.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2020-07-31&lt;/em&gt;: if the control channel changes, this Park frequency is one
of the two things you&#39;ll need to update.&lt;/p&gt;
&lt;p&gt;the second VCO should look kinda like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-06-26-police-scanner-setup-4.png&quot; alt=&quot;the RTL-SDR VCO 2 settings&quot;&gt;&lt;/p&gt;
&lt;p&gt;the important things are the Role being Voice, the Deemphasis box being
unchecked, and the Digital Output being set to your CABLE Input. this means it
will connect up with dsd+ listening to our CABLE Output.&lt;/p&gt;
&lt;p&gt;press Play now; it should pull up a window with a Channels tab. the Channels
tab should look something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2020-06-26-police-scanner-setup-5.png&quot; alt=&quot;the unitrunker channels tab&quot;&gt;&lt;/p&gt;
&lt;p&gt;but the Frequency column will all be zeroes except for the control frequency we
found earlier. you&#39;ll need to copy over the rest of the frequencies manually
from the RadioReference site.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2020-07-31&lt;/em&gt;: if the control channel changes, you&#39;ll need to uncheck the
Control box on the old control channel, and check the Control box on the new
control channel.&lt;/p&gt;
&lt;p&gt;press the stop button and the play button again,
and everything should in theory be working. ideally, the Call History tab will
be crowded and updating pretty frequently, and unitrunker will be passing
things along to dsd+ which will give us the audio we want. technically, this is
enough.&lt;/p&gt;
&lt;h2 id=&quot;groups&quot; tabindex=&quot;-1&quot;&gt;groups&lt;/h2&gt;
&lt;p&gt;the thing, though, is we don&#39;t have context for any of this. for now, at least.
RadioReference should have a table or several of talkgroups - the &amp;quot;list all in
one table&amp;quot; button may come in handy - and we can use that information to figure
out who we&#39;re hearing, and have at least some control over who takes priority
if multiple people in different contexts are talking at once.&lt;/p&gt;
&lt;p&gt;find the main unitrunker window - it&#39;s titled &amp;quot;Universal Trunker&amp;quot; and if you
don&#39;t have it open just click the home button a bunch until it opens - and then
open the Systems tab and double-click the one that exists. open the Groups tab
in that window, and it should give you a massive list with columns for ID,
Label, and a bunch of stuff we don&#39;t care about right now. the ID matches up
with the DEC column in the RadioReference table, and the Label can be either
&amp;quot;Description&amp;quot; or &amp;quot;Alpha Tag&amp;quot; or something you make up yourself if you feel
creative. if you pay RadioReference $15 for a Premium subscription then
unitrunker can import that data automatically.&lt;/p&gt;
&lt;p&gt;once you&#39;ve filled that all in, open the Sites tab and double-click the entry
you see there, then open the Call History tab. the group labels you added
should now be appearing in the Audience column; the LCN and Frequency should
turn green for what unitrunker is currently listening to.&lt;/p&gt;
&lt;p&gt;back in the Groups tab, you can edit the Priority values to control which
groups will be chosen more often - as far as i can tell, higher priority groups
will interrupt lower priority groups, and equal priority groups will just play
whoever started talking first.&lt;/p&gt;
&lt;h2 id=&quot;broadcasting&quot; tabindex=&quot;-1&quot;&gt;broadcasting&lt;/h2&gt;
&lt;p&gt;this setup lets you listen to things locally, but what if you want your
comrades with no hardware to be able to also listen? the laziest option is to
just stream the Call History window on Twitch or something, but in theory there
are better options. RadioReference runs Broadcastify, which is designed for
hosting police scanner livestreams, but they have to manually approve your
broadcast, which is annoying for short term activity. you could run an icecast
server yourself or something, but that takes effort to configure. honestly all
of those kinda suck but those are your options as far as i know.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2020-07-31&lt;/em&gt;: you can also let your friendly neighborhood succulent run
an icecast server for you; reach out to me if you need something like this. if
you&#39;ve got an icecast server, you&#39;ll need to pay for (or otherwise obtain)
&lt;a href=&quot;https://www.vb-audio.com/Cable/index.htm#DownloadCable&quot;&gt;VB-CABLE A+B&lt;/a&gt;, set up VB-CABLE A, and grab &lt;a href=&quot;https://danielnoethen.de/butt/&quot;&gt;butt (broadcast using this tool)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;you&#39;ll need to set DSD+ to output to &amp;quot;CABLE-A Input&amp;quot; like how you set it to
input from &amp;quot;CABLE Output&amp;quot; - Cable A is the fourth output in DSD+, so my run.bat
now looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-batch&quot;&gt;&lt;code class=&quot;language-batch&quot;&gt;&lt;span class=&quot;token command&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;DSDPlus&lt;/span&gt;.exe -i3M -o4&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;run butt, pull up the settings, and under the Audio tab set the Input Device
to &amp;quot;CABLE-A Output&amp;quot;. (for bonus points, set the Streaming Codec to AAC+.) under
the Main tab, Add a new Server and put in whatever info your icecast server
admin told you to use. now restart your DSD+ and hit butt&#39;s play button to
start streaming, and you should be running a livestream of your police scanner
that is accessible over the internet.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Lifehack: Running An Entire Desktop Session Remotely With MobaXterm</title>
    <link href="https://www.boringcactus.com/2020/03/20/mobaxterm-desktop-session.html" />
    <updated>2020-03-20T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2020/03/20/mobaxterm-desktop-session.html</id>
    <content type="html">&lt;p&gt;Since my university has gone as remote as possible due to coronavirus, I was looking at ways to run an entire desktop session remotely over SSH, using MobaXterm because it is very cool.
Here are the two steps to doing that.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open your MobaXterm settings, go to the X11 tab, and make sure that the server display mode is set to windowed mode.
If you run individual programs over X11 forwarding, this is worse, but for an entire desktop session it is better.&lt;/li&gt;
&lt;li&gt;Duplicate your regular command line session that already works, and under the &amp;quot;Advanced SSH settings&amp;quot; tab, set &amp;quot;Execute command&amp;quot; to &lt;code&gt;env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu&lt;/code&gt;.
(If you&#39;re not running the same setup I am, look around in &lt;code&gt;/usr/share/xsessions/&lt;/code&gt;, pick something that looks reasonable, and use everything after &lt;code&gt;Exec=&lt;/code&gt; on the line with that.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point, you should be set.
You&#39;ll need to hit the &amp;quot;log out&amp;quot; button to smoothly exit the connection.
For me, this is extraordinarily slow, but that could easily be just because the machines I&#39;m connecting to are being used a lot.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rust 2020: Write Once, Run Anywhere</title>
    <link href="https://www.boringcactus.com/2019/11/03/rust-2020.html" />
    <updated>2019-11-03T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2019/11/03/rust-2020.html</id>
    <content type="html">&lt;p&gt;thing that is cool: writing the same codebase and having it run on desktop, mobile, and web&lt;/p&gt;
&lt;p&gt;thing that is lame: JavaScript is the only language where people really do that right now, outside of big commercial game engines&lt;/p&gt;
&lt;p&gt;things that need to happen for Rust to get there:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;promote more platforms to tier 1, or maybe introduce a &amp;quot;tier 1.5&amp;quot; where std is guaranteed to work but rustc and cargo are not (although it&#39;d be cool for rustc to work on WebAssembly)
&lt;ul&gt;
&lt;li&gt;iOS: &lt;code&gt;aarch64-apple-ios&lt;/code&gt;, &lt;code&gt;armv7-apple-ios&lt;/code&gt;, &lt;code&gt;i386-apple-ios&lt;/code&gt;, &lt;code&gt;x86_64-apple-ios&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Android: &lt;code&gt;aarch64-linux-android&lt;/code&gt;, &lt;code&gt;arm-linux-androideabi&lt;/code&gt;, &lt;code&gt;i686-linux-android&lt;/code&gt;, &lt;code&gt;thumbv7neon-linux-androideabi&lt;/code&gt;, &lt;code&gt;x86_64-linux-android&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;WebAssembly: &lt;code&gt;wasm32-unknown-unknown&lt;/code&gt; (or one of the other &lt;code&gt;wasm32&lt;/code&gt; targets)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;test platform abstractions (graphics libraries, game engines, UI frameworks) on all of those&lt;/li&gt;
&lt;li&gt;get some high-level examples together of how to use Rust to write performant cross-platform code&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry>
    <title>Email Notifications for SSH Logins From Scratch</title>
    <link href="https://www.boringcactus.com/2019/03/09/email-on-ssh-auth.html" />
    <updated>2019-03-09T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2019/03/09/email-on-ssh-auth.html</id>
    <content type="html">&lt;p&gt;I just spent a while trying to make this happen, so I&#39;m putting this here so I don&#39;t have to redo all that research next time.&lt;/p&gt;
&lt;h3 id=&quot;configuring-email&quot; tabindex=&quot;-1&quot;&gt;Configuring Email&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linode.com/docs/email/postfix/postfix-smtp-debian7/&quot;&gt;This guide from Linode&lt;/a&gt; explains how to install and configure Postfix, which you&#39;ll need.
Be careful, though: when it says &lt;code&gt;[mail.isp.example]&lt;/code&gt; the &lt;code&gt;[]&lt;/code&gt; aren&#39;t just to indicate placeholders.
You do need a literal &lt;code&gt;[]&lt;/code&gt; around your hostname in your Postfix configuration.&lt;/p&gt;
&lt;p&gt;Also, if your setup is like mine, if you try to send email from &lt;code&gt;x@&amp;lt;your domain&amp;gt;&lt;/code&gt; to &lt;code&gt;y@&amp;lt;your domain&amp;gt;&lt;/code&gt;, Postfix will unhelpfully try to deliver it locally.
&lt;a href=&quot;https://serverfault.com/a/433305&quot;&gt;This Server Fault answer&lt;/a&gt; explains how to tell Postfix to not do that.&lt;/p&gt;
&lt;p&gt;If you&#39;re really unlucky, you may also need to create local users &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; (with &lt;code&gt;useradd -M -N -s /bin/false &amp;lt;username&amp;gt;&lt;/code&gt;).
I did that before I fixed the Postfix config, so fixing the Postfix config may be enough.&lt;/p&gt;
&lt;h3 id=&quot;configuring-ssh&quot; tabindex=&quot;-1&quot;&gt;Configuring SSH&lt;/h3&gt;
&lt;p&gt;Thankfully, by the time you&#39;ve got the email configuration out of the way, &lt;a href=&quot;https://www.vpsinfo.com/tutorial/email-alert-ssh-login/&quot;&gt;this guide from VPSInfo&lt;/a&gt; fully explains how to set up SSH to send emails on login.
This will send emails even if no login shell is run on the ssh connection.&lt;/p&gt;
&lt;p&gt;Since you need to store your credentials in the Postfix configuration, a sufficiently motivated attacker could probably retrieve them.
As such, if you&#39;re using email notifications to detect security breaches, I would suggest not sending them to the same address that they&#39;re being sent from.&lt;/p&gt;
&lt;p&gt;As a security measure, this is purely reactive; you can know that someone has illegitimately connected, but whatever they&#39;re trying to do has already been done.
A proactive measure would be to implement 2FA on SSH logins, as per &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-set-up-multi-factor-authentication-for-ssh-on-ubuntu-16-04&quot;&gt;this guide from DigitalOcean&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Announcing vidslice</title>
    <link href="https://www.boringcactus.com/2019/02/27/vidslice.html" />
    <updated>2019-02-27T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2019/02/27/vidslice.html</id>
    <content type="html">&lt;p&gt;I just released version 1.0 of &lt;a href=&quot;https://github.com/boringcactus/vidslice&quot;&gt;vidslice&lt;/a&gt;, a wxPython GUI that wraps ffmpeg and youtube-dl to make &amp;quot;give me from 1:03-1:15 of this youtube video&amp;quot; really easy to do.
More details are in the project README.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Futures</title>
    <link href="https://www.boringcactus.com/2018/08/31/futures.html" />
    <updated>2018-08-31T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2018/08/31/futures.html</id>
    <content type="html">&lt;p&gt;I&#39;m going to be graduating from college in December and I&#39;m a little bit freaking out.
Not because I don&#39;t have a plan, but because I have six plans.&lt;/p&gt;
&lt;h2 id=&quot;melody-the-grad-student&quot; tabindex=&quot;-1&quot;&gt;Melody the grad student&lt;/h2&gt;
&lt;p&gt;I may hate college, but that doesn&#39;t mean I&#39;m not good at it.
I&#39;m still not entirely sure if it&#39;s college in general I hate or just my college in particular, but I&#39;ve got a hunch it&#39;s the latter.
In that case, I could absolutely try to get a master&#39;s in computer science at a halfway decent university.&lt;/p&gt;
&lt;p&gt;This would be the next step towards getting a Ph.D. and becoming a computer science professor, which is a thing that I think would be super cool.
However, the step itself would take a whole bunch of work and I don&#39;t necessarily have all that much interest in CS theory.
Someone suggested that I could pursue a master&#39;s in philosophy instead, which is an intriguing but counterintuitive prospect that I haven&#39;t necessarily thought all the way through yet.&lt;/p&gt;
&lt;p&gt;I don&#39;t think I could take another semester of college right now, but most places are probably set up for people to start in the fall and not the spring, so that works out.&lt;/p&gt;
&lt;h2 id=&quot;melody-the-indie-miscellany-dev&quot; tabindex=&quot;-1&quot;&gt;Melody the indie miscellany dev&lt;/h2&gt;
&lt;p&gt;I am a walking collection of half-baked project ideas waiting to be built.
Web application that bridges Twitter content into the ActivityPub/OStatus fediverse (Mastodon et al).
No-code-required Discord bot toolkit.
&lt;a href=&quot;http://todomvc.com/&quot;&gt;TodoMVC&lt;/a&gt; but for desktop GUI frameworks.&lt;/p&gt;
&lt;p&gt;If I had the time, I could actually build some of those things.
I&#39;d need to make money somehow, though; if this were me full time I&#39;d probably be on Patreon or trying to figure out non-shady ways to monetize this stuff.
However, being solely responsible for things that people actually use would be super stressful.
Plus I don&#39;t think I have the discipline to do anything if there&#39;s nobody to make me do it.&lt;/p&gt;
&lt;h2 id=&quot;melody-the-indie-game-dev&quot; tabindex=&quot;-1&quot;&gt;Melody the indie game dev&lt;/h2&gt;
&lt;p&gt;Games are cool.
I&#39;ve made a handful already for game jams and whatnot.
Actually, the first thing I actually did as boringcactus was make a game for a game jam.&lt;/p&gt;
&lt;p&gt;The trouble is that I don&#39;t have a big-I Idea that I would pursue if I decided I wanted to be an indie dev.
Most of my jam games are either finished or not worth finishing.
I&#39;d love to make something that&#39;s an indie success story like Night in the Woods or Gunpoint, but those games already exist.
Plus the odds of taking off like that are kinda one-in-a-million.&lt;/p&gt;
&lt;h2 id=&quot;melody-the-musician&quot; tabindex=&quot;-1&quot;&gt;Melody the musician&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;This&lt;/em&gt; is a long shot.
But music production and performance is a thing that&#39;s always been important to me.
Early in high school I made some garbage tier EDM that didn&#39;t go anywhere.&lt;/p&gt;
&lt;p&gt;What I&#39;d really like to do is live looping covers of things that weren&#39;t really designed to be replicated like that.
I think something like &lt;a href=&quot;https://youtu.be/uc9Bi52ZQ7A?t=30s&quot;&gt;this live performance of Marian Hill&#39;s &lt;em&gt;Down&lt;/em&gt;&lt;/a&gt; would be super amazing to do with more complicated songs.
I wrote a &lt;a href=&quot;https://www.reaper.fm/&quot;&gt;REAPER&lt;/a&gt; plugin that would help with stuff like that, but I haven&#39;t actually used it for anything yet.&lt;/p&gt;
&lt;h2 id=&quot;melody-the-small-tech-co-employee&quot; tabindex=&quot;-1&quot;&gt;Melody the small tech co employee&lt;/h2&gt;
&lt;p&gt;This is a rant that I&#39;ll probably give in more detail some other time, but I think the Silicon Valley venture capital model is directly responsible for how shitty the tech industry is.&lt;/p&gt;
&lt;p&gt;If I wanted traditional employment, the best environment for it would be a small company that isn&#39;t beholden to a VC firm.
A company that can keep its standards and leave growth on the table if it&#39;d be unethical to pursue it is a company that I&#39;d probably be comfortable working for.&lt;/p&gt;
&lt;h2 id=&quot;melody-the-sellout&quot; tabindex=&quot;-1&quot;&gt;Melody the sellout&lt;/h2&gt;
&lt;p&gt;Of course, small companies don&#39;t get that way by hiring every rando with a résumé.
Big companies with bad ethics are almost always hiring.
And they&#39;re the most likely to still be around several years from now.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Some Thoughts About Work</title>
    <link href="https://www.boringcactus.com/2018/07/17/some-thoughts-about-work.html" />
    <updated>2018-07-17T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2018/07/17/some-thoughts-about-work.html</id>
    <content type="html">&lt;p&gt;I think there are three things that can make work rewarding:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Working with cool people&lt;/li&gt;
&lt;li&gt;Working on cool things&lt;/li&gt;
&lt;li&gt;Working in cool ways&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Right now (summer of 2018) I am working with cool people but on boring things in less-than-ideal ways.
I think that&#39;s the main reason I don&#39;t really enjoy this internship.
But hey, it&#39;ll be over in two weeks.&lt;/p&gt;
&lt;h2 id=&quot;working-with-cool-people&quot; tabindex=&quot;-1&quot;&gt;Working with cool people&lt;/h2&gt;
&lt;p&gt;It&#39;s nice to work with cool people.
People I&#39;m more comfortable working with are people I&#39;ll work better with.
People whose judgment I trust are people whose leadership I&#39;ll find easy to respect.&lt;/p&gt;
&lt;h2 id=&quot;working-on-cool-things&quot; tabindex=&quot;-1&quot;&gt;Working on cool things&lt;/h2&gt;
&lt;p&gt;If I can explain the thing I&#39;m working on to somebody and get them excited about it, that&#39;s great.
If I&#39;m excited about it but nobody else is, that&#39;s kinda enough.
If even I think the project is kinda boring, I&#39;ll still get it done, but I won&#39;t enjoy the process.&lt;/p&gt;
&lt;h2 id=&quot;working-in-cool-ways&quot; tabindex=&quot;-1&quot;&gt;Working in cool ways&lt;/h2&gt;
&lt;p&gt;I&#39;m not sure there is a right way to run big projects, but I know there are a bunch of wrong ways.
The more people are going to wind up touching your project, the more important it is to get the architecture right.
If your project is going to be big, don&#39;t put all the important guts of it in the same 4000-line &lt;code&gt;switch&lt;/code&gt; statement.&lt;/p&gt;
&lt;p&gt;Line-by-line code reviews with the entire team can be helpful if everybody needs to know exactly what&#39;s going on, or if everybody might have something valuable to say or to learn.
Line-by-line reviews of automatically generated XML are...less likely to be helpful.&lt;/p&gt;
&lt;h2 id=&quot;what-that-might-mean&quot; tabindex=&quot;-1&quot;&gt;What that might mean&lt;/h2&gt;
&lt;p&gt;I don&#39;t know that I&#39;d function well if I were self-employed.
I doubt I have the motivation or discipline to pull it off, plus then I wouldn&#39;t get to work with cool people.&lt;/p&gt;
&lt;p&gt;I know &amp;quot;cool&amp;quot; is a relative term, but I think I might be a little too picky about the sort of thing I&#39;d want to work on.
I think Silicon Valley venture capital culture is such that the money has gotten so dense it&#39;s formed a black hole from which no ethics and no perspective can escape.
Advertising sucks, just about every blockchain was a mistake, and AI is going to spend the rest of the century wobbling between the almost-not-useless of voice assistants today and the actively-making-things-worse that YouTube has running demonetization and recommendations.
Not all growth is worth it; it&#39;s not just OK but good to leave money on the table if you can walk away with your soul (this is the part VC tends to ruin).
I don&#39;t know what&#39;s good, but I know what&#39;s bad, and frankly most stuff is bad.&lt;/p&gt;
&lt;p&gt;Doing big projects right is hard.
My solution would be to just not do big projects.
Somebody has to, but it doesn&#39;t have to be me.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Slicing and Dicing Images with GIMP and Python</title>
    <link href="https://www.boringcactus.com/2018/06/23/slicing-images-gimp-python.html" />
    <updated>2018-06-23T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2018/06/23/slicing-images-gimp-python.html</id>
    <content type="html">&lt;p&gt;Let&#39;s say you have one big image (say, a Telegram sticker) and you need to dice it into a bunch of smaller images (say, Discord emoji).
GIMP can let you do that manually, but frankly so can simpler tools.
GIMP also has powerful scripting support with Python (and also Scheme, but miss me with that) that can let us do that automatically.&lt;/p&gt;
&lt;h2 id=&quot;tl%3Bdr-how-do-i-do-the-thing&quot; tabindex=&quot;-1&quot;&gt;TL;DR how do i do the thing&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Save your large image somewhere with a useful filename; this script will chuck &lt;code&gt;_1_1&lt;/code&gt; and &lt;code&gt;_1_2&lt;/code&gt; etc on the end of the existing filename.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open that image in GIMP.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to the Filters menu, open Python-Fu, and hit Console.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set up the width and height of your tiles. For 64x64 tiles, for example, type&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;WIDTH &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;
HEIGHT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Paste in this big ol&#39; block of code and let it build your tiles and print out the text you can enter in Discord to reconstitute your original image:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; gimpfu &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; __future__ &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; print_function
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;crop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_crop&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; height&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    x_idx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    y_idx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_get_filename&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;split&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    root&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;splitext&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    ext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.png&quot;&lt;/span&gt;
    output_root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; root &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;y_idx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x_idx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    output_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output_root &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; ext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    layer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_get_active_layer&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;file_png_save_defaults&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; layer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; output_root &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_delete&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gimp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;image_list&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_get_filename&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; y &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WIDTH&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HEIGHT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        crop&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_file_load&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filename&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WIDTH&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HEIGHT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are two minor issues with actually using this code to convert a Telegram sticker into Discord emoji that I&#39;ll get to later.&lt;/p&gt;
&lt;h2 id=&quot;the-code%2C-splained&quot; tabindex=&quot;-1&quot;&gt;The Code, Splained&lt;/h2&gt;
&lt;p&gt;I&#39;ll walk through each bit of the code segment above and explain why it&#39;s there.&lt;/p&gt;
&lt;p&gt;We need the GIMP libraries, the Python 3 &lt;code&gt;print()&lt;/code&gt; function (because as of GIMP 2.8.22 the GIMP console is still on Python 2), and some path manipulation functions.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; gimpfu &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; __future__ &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; print_function
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;re going to crop an image with an X and Y offset and a width and height.
The first step in generating the tile is telling GIMP to do the actual crop.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;crop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_crop&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; height&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next step is to figure out the filename for this specific tile; here we&#39;re getting an index back from the offsets and width and height.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;    x_idx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    y_idx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_get_filename&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;split&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    root&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;splitext&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    ext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.png&quot;&lt;/span&gt;
    output_root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; root &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;y_idx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x_idx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    output_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output_root &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; ext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once we&#39;ve got a filename, we can save.
For some reason GIMP&#39;s save functions all depend on both the image and the layer, and on two copies of the filename.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;    layer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_get_active_layer&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;file_png_save_defaults&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; layer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the goal is to reconstitute the original image from Discord emoji, we assume that they won&#39;t be renamed.
We need the Python 3 print function here to suppress any characters after the string is printed; the Python 2 &lt;code&gt;print &amp;quot;foo&amp;quot;,&lt;/code&gt; trick still emits a space.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; output_root &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We might as well delete the image from GIMP.
I don&#39;t know if this actually serves an important purpose or not.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;    pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_delete&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want to grab the original filename.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gimp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;image_list&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_get_filename&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we defined WIDTH and HEIGHT manually earlier, now we can loop through the image.
I should probably go back in and make it grab the full image width and height, but fuck it, I don&#39;t want to.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; y &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WIDTH&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HEIGHT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don&#39;t know if GIMP doesn&#39;t expose undo in the Python API or if I just couldn&#39;t find it, but either way we don&#39;t have undo, so we pass in a fresh copy of the image instead.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;        crop&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_file_load&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filename&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WIDTH&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HEIGHT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we&#39;re building up the emoji text for Discord one row at a time, we need to end the row at the end of a row.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is just there so the newline after the &lt;code&gt;for&lt;/code&gt; loop gets pasted successfully.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-plot-thickens&quot; tabindex=&quot;-1&quot;&gt;The Plot Thickens&lt;/h2&gt;
&lt;p&gt;The first issue with this approach is that Discord (at time of writing, at least) sets a total of 2.25 pixels worth of horizontal margin between emoji, so your reconstituted image will have weird stripes.
It might be feasible to adjust for these in the offsets so that the spacing isn&#39;t funky, but honestly that seems like a lot of work.&lt;/p&gt;
&lt;p&gt;The second, and more interesting, issue is that Discord has a 50 emoji limit on each server.
Slicing a 512x512 image into 32x32 tiles for a full size replica would generate 256 tiles, which might work if you had Discord Nitro and six different dummy servers, but nah.
Slicing into 64x64 tiles that&#39;ll be rendered at half size only makes 64 tiles, which works out nicely numerically but is still more than can fit on one server.
Unless we&#39;re clever.&lt;/p&gt;
&lt;p&gt;I&#39;m not sure how well this generalizes, but for the sticker I&#39;m working with, 16 of those 64 tiles are fully transparent, and therefore identical.
If we could detect this when slicing, we could avoid emitting 15 of those, at which point we come in nicely with 49 tiles, one under the Discord emoji limit.
But how can we detect if an image is fully transparent?&lt;/p&gt;
&lt;p&gt;Get histogram info for the alpha channel!
We can use something like this to count how many pixels aren&#39;t fully transparent:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; visible_count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_histogram&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;layer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HISTOGRAM_ALPHA&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So our final code can detect if each tile is fully transparent before it saves and treat all fully transparent tiles as equivalent to the very first one.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; gimpfu &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; __future__ &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; print_function
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path

empty_tile_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;crop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;global&lt;/span&gt; empty_tile_name
    pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_crop&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; height&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    layer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_get_active_layer&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; visible_count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_histogram&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;layer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HISTOGRAM_ALPHA&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    x_idx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    y_idx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_get_filename&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;split&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    root&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;splitext&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    ext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.png&quot;&lt;/span&gt;
    output_root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; root &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;y_idx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x_idx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    output_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output_root &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; ext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; visible_count &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; empty_tile_name &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;file_png_save_defaults&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; layer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; visible_count &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; empty_tile_name &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            empty_tile_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; output_root
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            output_root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; empty_tile_name
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; output_root &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_delete&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gimp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;image_list&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_image_get_filename&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; y &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WIDTH&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; x &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HEIGHT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        crop&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gimp_file_load&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filename&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WIDTH&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HEIGHT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The results are actually fairly impressive, all things considered:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.boringcactus.com/assets/2018-06-23-slicing-images-gimp-python-1.png&quot; alt=&quot;A halfway decent but slightly stripe-y replica as Discord emoji of the Telegram sticker of Pandora&#39;s Fox dabbing.&quot;&gt;&lt;/p&gt;
&lt;p&gt;(that sticker is by &lt;a href=&quot;https://twitter.com/NLDraws&quot;&gt;NL&lt;/a&gt; and of &lt;a href=&quot;https://twitter.com/pandoras_foxo&quot;&gt;Pandora&#39;s Fox&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;But of course anyone with an ounce of sense would just upload the image so this whole project was a complete waste of three hours.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Windows, Vim, and Python: An Unholy Trinity of Pain</title>
    <link href="https://www.boringcactus.com/2018/06/17/windows-vim-python.html" />
    <updated>2018-06-17T00:00:00Z</updated>
    <id>https://www.boringcactus.com/2018/06/17/windows-vim-python.html</id>
    <content type="html">&lt;p&gt;Last summer I figured I&#39;d learn Vim. That did not go well.&lt;/p&gt;
&lt;p&gt;I started by stealing somebody&#39;s &lt;code&gt;.vimrc&lt;/code&gt;, as is natural.
In this case the person from whomst I lifted my &lt;code&gt;.vimrc&lt;/code&gt; was a Python dev, and I was working in Python at the time, so that was a reasonable choice.
But once I opened an actual Python file I got an error message that Vim couldn&#39;t find Python.&lt;/p&gt;
&lt;p&gt;I did some research and it turned out that even though I&#39;d grabbed the latest version of Vim, it was looking for Python 3.5 and I had Python 3.6, which had been out for a while by then.
So I uninstalled Python 3.6 and installed Python 3.5 and started getting a different error message.&lt;/p&gt;
&lt;p&gt;A bit more research revealed that my Python was 64-bit but my Vim was 32-bit.
Apparently Vim didn&#39;t provide official 64-bit Windows builds at that time, so for 64-bit Vim on Windows they just linked to a handful of third party distributions.
I went ahead and uninstalled my 32-bit Vim so I could install 64-bit Vim, and then everything worked fine.
(Except for all the minor Vim papercuts that eventually led me to write &lt;a href=&quot;https://github.com/mathphreak/mfte&quot;&gt;my own Nano clone&lt;/a&gt; instead.)&lt;/p&gt;
&lt;p&gt;To get Vim and Python to play nice with each other, I had to reinstall both of them.&lt;/p&gt;
&lt;p&gt;And that&#39;s basically what developing on Windows is like in a nutshell.&lt;/p&gt;
&lt;p&gt;But it doesn&#39;t have to be this way.
If more people treated Windows as a first class platform, the tools to develop on Windows wouldn&#39;t be so frustrating to use, and then more people would treat Windows as a first class platform.&lt;/p&gt;
</content>
  </entry>
</feed>
