From 2868467137fe193cb48dde9560656dbd6e4bde82 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Mon, 7 Apr 2025 09:47:09 +0200 Subject: Update to blog post --- src/blog/2025-04-04-qt-minimal/qt-minimal.md | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'src/blog') diff --git a/src/blog/2025-04-04-qt-minimal/qt-minimal.md b/src/blog/2025-04-04-qt-minimal/qt-minimal.md index 6f112bb..4cc0e18 100644 --- a/src/blog/2025-04-04-qt-minimal/qt-minimal.md +++ b/src/blog/2025-04-04-qt-minimal/qt-minimal.md @@ -98,3 +98,38 @@ And finally you can enjoy your new app: ``` ./run ``` + +## Update 2025-04-07 - now with AI! + +When I shared this post on LinkedIn, I asked if anyone could prompt an +LLM to build a QT app without QT Creator or CMake. A colleague did, +and the catbot's (pun intended) response can be found +[here](https://chat.mistral.ai/chat/fa3e3a76-0e8c-4135-94a1-ae7735174e93). + +This solution is different from the one I found. The bot opted for using +[qmake](https://doc.qt.io/qt-6/qmake-manual.html), another tool bundled +with QT, like moc and uic. This tool reads a `.pro` file that describes +the project's structure and build configuration, and it generates a +`Makefile`. This method is a bit more black-boxy than the one I found, +but it does not require more external dependencies; I think I might use +this other approach in the future. + +The chat bot's response is great, but it is "only" 99% +accurate. Unfortunately this means that if one blindly follows these +instructions, they won't get a working QT application. The problem is +with the commands: + +``` +qmake -project +qmake +``` + +The first one is completely useless, and actually prevents the project +from being built by generating a second project file. If one simply +ignores the `qmake -project` line, the instructions become correct. + +This story is quite insightful: the chat bot gave me instructions that are +almost correct (so technically they are *incorrect*). With my experience +(a whole 5 hours of CV-worthy QT development) I was able to fix these +instructions easily in a couple of minutes, but someone without the same +experience would have probably struggled for much longer. -- cgit v1.3 From 46e8ca4aa90b92ea8ba857d4b0d5ddde615e6dc0 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Fri, 25 Apr 2025 10:18:37 +0200 Subject: Updated links after repository rename --- src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md | 2 +- src/blog/2025-04-05-learned-rewrite/learned-rewrite.md | 4 ++-- src/git/git.md | 14 +++++--------- src/speedcubing/coordinates/coordinates.md | 4 ++-- 4 files changed, 10 insertions(+), 14 deletions(-) (limited to 'src/blog') diff --git a/src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md b/src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md index 45da3cb..6a2f782 100644 --- a/src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md +++ b/src/blog/2023-04-10-the-big-rewrite/the-big-rewrite.md @@ -93,7 +93,7 @@ projects, or probably it will be never done. And that's OK. My plan is to split the work into the following parts: -* Starting from the [stable branch](https://nissy.tronto.net/download) +* Starting from the [stable branch](https://nissy.tronto.net) of nissy, remove all the code that is needed only by the optimal solver and other unnecessary steps. Work on a GUI and other simple features useful for assisting fewest moves solvers. diff --git a/src/blog/2025-04-05-learned-rewrite/learned-rewrite.md b/src/blog/2025-04-05-learned-rewrite/learned-rewrite.md index 6a7a3ed..0973dd3 100644 --- a/src/blog/2025-04-05-learned-rewrite/learned-rewrite.md +++ b/src/blog/2025-04-05-learned-rewrite/learned-rewrite.md @@ -17,8 +17,8 @@ than as fast as possible. Both the old and the new versions are written in C, but not all the things I discuss in this post are language-specific. You can find the new version of this project, the "Big Rewrite", -in [this repository](https://git.tronto.net/h48) - or -[on github](https://github.com/sebastianotronto/h48), if you prefer. +in [this repository](https://git.tronto.net/nissy-core) - or +[on github](https://github.com/sebastianotronto/nissy-core), if you prefer. I decided to rename it "H48" back when I planned to have separate projects for the different features of the old version, but I may change it back to Nissy at some point. diff --git a/src/git/git.md b/src/git/git.md index 15f32f6..cbfbf80 100644 --- a/src/git/git.md +++ b/src/git/git.md @@ -16,16 +16,12 @@ Here are some highlights of what you can find in my git repositories. * [nissy](https://git.tronto.net/nissy): A Rubik's cube solver, intended primarily as a tool for practicing FMC. Since april 2023 - the project has been split into the FMC trainer - [nissy-fmc](https://git.tronto.net/nissy-fmc) and the optimal solver - [nissy-nx](https://git.tronto.net/nissy-nx). Both are work in - progress. The stable version is available at - [nissy-classic](https://git.tronto.net/nissy-classic), and is - maintained with bugfixes. + the stable version of the project has been moved to + [nissy-classic](https://git.tronto.net/nissy-classic), while I kept + working on the H48 optimal solver. This new solver, which will be + integrated in a future version of nissy, can be found + [here](https://git.tronto.net/nissy-core). Check out also [the project's homepage](https://nissy.tronto.net). -* [h48](https://git.tronto.net/h48): Another Rubik's cube optimal solver, - intended to replace the engine of nissy in the future. Written in - collaboration with [Enrico](https://github.com/enricotenuti). * [scripts](https://git.tronto.net/scripts): Various scripts for Unix-like systems. * [zmodn](https://git.tronto.net/zmodn): A simple C++ library for working diff --git a/src/speedcubing/coordinates/coordinates.md b/src/speedcubing/coordinates/coordinates.md index afae46f..e1e6785 100644 --- a/src/speedcubing/coordinates/coordinates.md +++ b/src/speedcubing/coordinates/coordinates.md @@ -276,5 +276,5 @@ See [fst.c](https://git.tronto.net/nissy-nx/file/src/fst.c.html#l113) for an implementation. For these reasons, I am not using the coordinate approach described here -for my new work-in-progress solver (temporarily named -[h48](https://git.tronto.net/h48)). +for my new work-in-progress solver (which can be found +[here](https://git.tronto.net/nissy-core)). -- cgit v1.3 From 3d70f253ef49b80f58b3009074b889ac833ad542 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Sat, 24 May 2025 15:37:35 +0200 Subject: Fixed typo --- src/blog/2022-06-04-gemini/gemini.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/blog') diff --git a/src/blog/2022-06-04-gemini/gemini.md b/src/blog/2022-06-04-gemini/gemini.md index b04a8ac..6e228c5 100644 --- a/src/blog/2022-06-04-gemini/gemini.md +++ b/src/blog/2022-06-04-gemini/gemini.md @@ -192,4 +192,4 @@ and, more annoyingly for the few gemini users, I am going to use http(s) links even when a gemini counterpart is available. *Update: as of August 2023, my website is still available on gemini, -but new blog posts are no longer mirrore there* +but new blog posts are no longer mirrored there* -- cgit v1.3 From 958502fb3521c274144ce6cc8294ed155207f7f1 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Fri, 6 Jun 2025 15:47:12 +0200 Subject: New blog post --- src/blog/2025-06-06-webdev/blocking.png | Bin 0 -> 29516 bytes src/blog/2025-06-06-webdev/callback.jpg | Bin 0 -> 27472 bytes src/blog/2025-06-06-webdev/callback2.jpg | Bin 0 -> 60575 bytes src/blog/2025-06-06-webdev/hello.png | Bin 0 -> 70582 bytes src/blog/2025-06-06-webdev/logos.png | Bin 0 -> 31951 bytes src/blog/2025-06-06-webdev/spectre.png | Bin 0 -> 28193 bytes src/blog/2025-06-06-webdev/storage.png | Bin 0 -> 74806 bytes src/blog/2025-06-06-webdev/threads.jpg | Bin 0 -> 65580 bytes src/blog/2025-06-06-webdev/wasm.png | Bin 0 -> 8457 bytes src/blog/2025-06-06-webdev/webdev.md | 1186 ++++++++++++++++++++++++++++++ 10 files changed, 1186 insertions(+) create mode 100644 src/blog/2025-06-06-webdev/blocking.png create mode 100644 src/blog/2025-06-06-webdev/callback.jpg create mode 100644 src/blog/2025-06-06-webdev/callback2.jpg create mode 100644 src/blog/2025-06-06-webdev/hello.png create mode 100644 src/blog/2025-06-06-webdev/logos.png create mode 100644 src/blog/2025-06-06-webdev/spectre.png create mode 100644 src/blog/2025-06-06-webdev/storage.png create mode 100644 src/blog/2025-06-06-webdev/threads.jpg create mode 100644 src/blog/2025-06-06-webdev/wasm.png create mode 100644 src/blog/2025-06-06-webdev/webdev.md (limited to 'src/blog') diff --git a/src/blog/2025-06-06-webdev/blocking.png b/src/blog/2025-06-06-webdev/blocking.png new file mode 100644 index 0000000..6d8f1c6 Binary files /dev/null and b/src/blog/2025-06-06-webdev/blocking.png differ diff --git a/src/blog/2025-06-06-webdev/callback.jpg b/src/blog/2025-06-06-webdev/callback.jpg new file mode 100644 index 0000000..21057be Binary files /dev/null and b/src/blog/2025-06-06-webdev/callback.jpg differ diff --git a/src/blog/2025-06-06-webdev/callback2.jpg b/src/blog/2025-06-06-webdev/callback2.jpg new file mode 100644 index 0000000..c59aa7c Binary files /dev/null and b/src/blog/2025-06-06-webdev/callback2.jpg differ diff --git a/src/blog/2025-06-06-webdev/hello.png b/src/blog/2025-06-06-webdev/hello.png new file mode 100644 index 0000000..d10658e Binary files /dev/null and b/src/blog/2025-06-06-webdev/hello.png differ diff --git a/src/blog/2025-06-06-webdev/logos.png b/src/blog/2025-06-06-webdev/logos.png new file mode 100644 index 0000000..4c70d89 Binary files /dev/null and b/src/blog/2025-06-06-webdev/logos.png differ diff --git a/src/blog/2025-06-06-webdev/spectre.png b/src/blog/2025-06-06-webdev/spectre.png new file mode 100644 index 0000000..d86271e Binary files /dev/null and b/src/blog/2025-06-06-webdev/spectre.png differ diff --git a/src/blog/2025-06-06-webdev/storage.png b/src/blog/2025-06-06-webdev/storage.png new file mode 100644 index 0000000..aab5de8 Binary files /dev/null and b/src/blog/2025-06-06-webdev/storage.png differ diff --git a/src/blog/2025-06-06-webdev/threads.jpg b/src/blog/2025-06-06-webdev/threads.jpg new file mode 100644 index 0000000..80befcf Binary files /dev/null and b/src/blog/2025-06-06-webdev/threads.jpg differ diff --git a/src/blog/2025-06-06-webdev/wasm.png b/src/blog/2025-06-06-webdev/wasm.png new file mode 100644 index 0000000..c2222c7 Binary files /dev/null and b/src/blog/2025-06-06-webdev/wasm.png differ diff --git a/src/blog/2025-06-06-webdev/webdev.md b/src/blog/2025-06-06-webdev/webdev.md new file mode 100644 index 0000000..15bc282 --- /dev/null +++ b/src/blog/2025-06-06-webdev/webdev.md @@ -0,0 +1,1186 @@ +# A masochist's guide to web development + +## Table of contents + +* [Introduction](#introduction) +* [Setting things up](#setting-things-up) +* [Hello world](#hello-world) +* [Intermezzo I: What is WebAssembly?](#intermezzo-i-what-is-webassembly) +* [Building a library](#building-a-library) +* [Intermezzo II: JavaScript and the DOM](#intermezzo-ii-javascript-and-the-dom) +* [Loading the library and making it a module](#loading-the-library-and-making-it-a-module) +* [Multithreading](#multithreading) +* [Intermezzo III: Web Workers and Spectre](#intermezzo-iii-web-workers-and-spectre) +* [Don't block the main thread!](#dont-block-the-main-thread) +* [Callback functions](#callback-functions) +* [Persistent storage](#persistent-storage) +* [Closing thoughts](#closing-thoughts) + +## Introduction + +I have recently worked on making a web application out of +[my latest Rubik's cube optimal solver](https://git.tronto.net/nissy-core/file/README.md.html). +This involved building a rather complex C code base (with +multithreading, SIMD, callback functions and whatnot) to +[WebAssembly](https://en.wikipedia.org/wiki/WebAssembly) via +[Emscripten](https://emscripten.org/), and writing a minimal amount of +JavaScript and HTML for the frontend. + +This whole process was complex, tiring and at times frustrating - +but eventually [it was a success](https://tronto.net:48)! Not only +I accomplished my goal, but I have learnt a lot along the way. After +finishing the work, I decided to write down all that I have learnt and +share it with the world with this post. + +You may be wondering why one should do such a thing instead of either +rewriting their code base in a more web-friendly language, or distributing +their app using a native GUI framework. The main reason to use WebAssembly +is that it can provide near-native performance (or so they claim) while +running inside a web browser; this gives you all the portability of a +web app without too much of a performance drawback, something that would +not be possible with an interpreted language such as JavaScript. + +So, what is this blog post? A tutorial for web development? I am not sure +about this, but if it is, it is definitely not a normal one. As the title +suggests, you should not start from this guide unless you just *love* +banging your head against the wall. If you are looking for a *sane* +guide to web development, I strongly advise you head on to the +[Mozilla Developer Network tutorials page](https://developer.mozilla.org/en-US/docs/MDN/Tutorials) +and start from there. + +But if you are a C or C++ developer looking to port a program or library +to the web, then you are in the right place. With this post I am going +to walk you through the process of building an increasingly complex +library that can run in a web browser. Make sure you are +sitting comfortably and be ready to sweat, because I am not going to +shy away from the hard stuff and the complicated details. + +To follow this tutorial you won't need much experience with web +development, but some familiarity with HTML and an idea of what JavaScript +will be useful. It will also help to know that you can access your +browser's JavaScript console and other developer tools by pressing F12, +at least on Firefox or Chrome - but I guess I have literally just taught +you that, if you did not already know it. For all the rest, I'll make +sure to add many hyperlinks throughout the text, so you can follow them +if something is new to you. + +A little disclaimer: although I am a somewhat experienced C developer, +I had very little web development experience before embarking in +this adventure. If you are a web developer, you may find errors in +this post that are going to make you laugh at my ignorance. If you do, +I'd appreciate it if you could report them to me by sending an email to +`sebastiano@tronto.net`! + +With this out of the way, let's get started! + +## Setting things up + +The examples used in this tutorial are all contained in a git repository, +which you can find either on +[my git page](https://git.tronto.net/emscripten-tutorial/file/README.md.html) or +[on github](https://github.com/sebastianotronto/emscripten-tutorial). + +In order to follow them you are going to need: + +* A working installation of [Emscripten](https://emscripten.org/) + (which also includes Node.js). Refer to the official website for + installation instructions. +* A web server such [darkhttpd](https://github.com/emikulic/darkhttpd) + or the Python `http.server` package; the examples will use darkhttpd. + +I have only tested all of this on Linux, but everything should work +exactly the same on any UNIX system. If you are a Windows user, you can +either run everything inside +[WSL](https://learn.microsoft.com/en-us/windows/wsl/), or you can try and +adjust the examples to your system - if you choose this second option, +I'll happily accept patches or pull requests :) + +## Hello world + +Let's start with the classic Hello World program: + +``` +#include + +int main() { + printf("Hello, web!\n"); +} +``` + +You can compile the code above with + +``` +emcc -o index.html hello.c +``` + +And if you now start a web server in the current folder, for example with +`darkhttpd .` (the dot at the end is important), and open a web browser to +[localhost:8080](http://localhost:8080) (or whatever port your web server +uses), you should see something like this: + +![Hello world in a browser](hello.png) + +As you can see, the compiler generated a bunch of extra stuff around +you print statement. You may or may not want this, but for now we can +take it as a convenient way to check that our program works as expected. + +There are other ways to run this compiled code. With the command above, +the compiler should have generated for you 3 files: + +* `index.html` - the web page in the screenshot above. +* `index.wasm` - the actual compiled code of your program; this file contains + WebAssembly bytecode. +* `index.js` - some JavaScript *glue code* to make it possible for `index.wasm` + to actually run in a browser. + +If you don't specify `-o index.html`, or if your specify `-o` followed +by a filename ending in `.js`, the `.html` page is not going to be +generated. In this case (but also if you *do* generate the html page), +you can run the JavaScript code in your terminal with: + +``` +node index.js +``` + +In later examples, the same code may not work seamlessly in both a web +browser and in Node.js - for example, when dealing with persistent data +storage. But until then, we can generate all three files with a single +command and run our code in either way. + +It is also possible to ask Emscripten to generate only the `.wasm` file, +in case you want to write the JavaScript glue code by yourself. To do +this, you can pass the `-sSTANDALONE_WASM` option to `emcc`. However, +in some cases the `.js` file is going to be generated even when this +option is used, for example when building a source file without a `main()` +entry point. Since this is something we'll do soon, we can forget about +this option and just take it as a fact that the `.wasm` files generated +by emscripten require some glue JavaScript code to actually run, +but in case you are interested you can check out +[the official documentation](https://emscripten.org/docs/tools_reference/settings_reference.html#standalone-wasm). + +You can find the code for this example, as well as scripts to +build it and run the web server, in the directory `00_hello_world` +of the git repository +([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html), +[github](https://github.com/sebastianotronto/emscripten-tutorial)). + +Anyway, now we can build our C code to run in a web page. But this is +probably not the way we want to run it. First of all, we don't want to +use the HTML template provided by Emscripten; but more importantly, we +probably don't want to write a program that just prints stuff to standard +output. More likely, we want to write some kind of library of functions +that can be called from the front-end, so that the user can interact with +our program via an HTML + JavaScript web page. Before going into that, +let's take a break to discuss what we are actually compiling our code to. + +## Intermezzo I: What is WebAssembly? + +![The logo of WebAssembly](wasm.png) + +[WebAssembly](https://en.wikipedia.org/wiki/WebAssembly) is a low-level +language meant to run in a virtual machine inside a web browser. The main +motivation behind it is running higher-performance web applications compared +to JavaScript; this is made possible, by its +compact bytecode and its stack-based virtual machine. + +WebAssembly (or WASM for short) is supported by all major browsers +since around 2017. Interestingly, Emscripten, the compiler we are +using to translate our C code to WASM, first appeared in 2011, +predating WASM by a few years. Early on, Emscripten would compile +C and C++ code into JavaScript, or rather a subset thereof called +[asm.js](https://en.wikipedia.org/wiki/Asm.js). + +Just like regular +[assembly](https://en.wikipedia.org/wiki/Assembly_language), WASM +also has a text-based representation. This means that one could write +WASM code directly, assemble it to bytecode, and then run it. We are +not going to do it, but if you are curious here is a simple example +(computing the factorial of a number, taken from Wikipedia): + +``` +(func (param i64) (result i64) + local.get 0 + i64.eqz + if (result i64) + i64.const 1 + else + local.get 0 + local.get 0 + i64.const 1 + i64.sub + call 0 + i64.mul + end) +``` + +As you can see, it looks like a strange mix of assembly and +[Lisp](https://en.wikipedia.org/wiki/Lisp_(programming_language)). +If you want to try and run WASM locally, outside of a web browser, +you could use something like [Wasmtime](https://wasmtime.dev/). + +Until early 2025, the WASM "architecture" was 32-bit only. One big +limitation that this brings is that you cannot use more that 4GB +(232 bytes) of memory, because pointers are only 32 bits +long; moreover, your C / C++ code may need some adjustments if it +relied on the assumption that e.g. `sizeof(size_t) == 8`. At the +time writing a new standard that enables 64 bit pointers, called +WASM64, is supported on Firefox and Chrome, but not on Webkit-based +browsers such as Safari yet. Depending on when you are reading this, +this may have changed - you can check the status of WASM64 support +[here](https://webassembly.org/features/). + +## Building a library + +Back to the main topic. Where were we? Oh yes, we wanted to build +a C *library* to WASM and call it from JavaScript. Our complex, +high-performance, math-heavy library probably looks something like this: + +library.h (actually, we are not going to need this): + +``` +int multiply(int, int); +``` + +library.c: + +``` +int multiply(int a, int b) { + return a * b; +} +``` + +Or maybe it is a bit more complicated than that. But we said we are +going to build up in complexity, and this is just the beginning, so +let's stick to `multiply()`. + +To build this library you can use: + +``` +emcc -o library.js library.c +``` + +As we saw before, this is going to generate both a `library.js` and a +`library.wasm` file. Now we would like to call our library function +with something like this + +program.js: + +``` +var library = require("./library.js"); +const result = library.multiply(6, 7); +console.log("The answer is " + result); +``` + +*(The `require()` syntax above is valid when running this code in Node.js, +but not, for example when running in a browser. We'll see in the next +session what to do in that case, but for now let's stick to this.)* + +Unfortunately, this will not work for a couple of reasons. The reason +first is that Emscripten is going to add an underscore `_` to all our +function names; so we'll have to call `library._multiply()`. But this +still won't work, because by default the compiler does not *export* all +the functions in your code - that is, it does not make them visible to +the outside. To specify which functions you want to +export, you can use the `-sEXPORTED_FUNCTIONS` flag, like so: + +``` +emcc -sEXPORTED_FUNCTION=_multiply -o library.js library.c +``` + +And now we finally have access to our `multiply()` function... + +``` +$ node program.js +Aborted(Assertion failed: native function `multiply` called before runtime initialization) +``` + +...or maybe not. If you are new to JavaScript like I was a few weeks +ago, you may find this error message surprising. Some runtime must be +initialized, but can't it just, like... initialize *before* trying to +run the next instruction? + +Things are not that simple. A lot of things in JavaScript happen +*asynchronously*, and in these situations you'll have to either use +[`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) +or a +[*callback function*](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function). +So we'll have to do something like this: + +``` +var library = require("./build/library.js"); + +library.onRuntimeInitialized = () => { + const result = library._multiply(6, 7); + console.log("The answer is " + result); +}; +``` + +And now we can finally run our program: + +``` +$ node program.js +The answer is 42 +``` + +The code for this example can be found in the `01_library` folder in +the git repository +([git.tronto.net](https://git.tronto.net/emscripten-tutorial/file/README.md.html), +[github](https://github.com/sebastianotronto/emscripten-tutorial)). + +## Intermezzo II: JavaScript and the DOM + +![The logos of HTML, CSS and JavaScript](logos.png) + +If we want to build an interactive web page using JavaScript, we'll +need a way for our script to communicate with the page, i.e. a way +to access the HTML structure from JavaScript code. What we are looking +for is called +*[Document Object Model](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)*, +or DOM for short. + +For example, if you have a paragraph with some text in your HTML: + +``` +

Hello!

+``` + +you can access this text from JavaScript like this: + +``` +var paragraph = document.getElementById("myParagraph"); +paragraph.innerText = "New text!"; +``` + +Here we are selecting the paragraph HTML element using its ID, and we +are changing its text via its `innerText` property, all from JavaScript. + +Let's see a more complex example: + +HTML: + +``` + +``` + +JS: + +``` +var button = document.getElementById("theButton"); +var counter = 0; + +button.addEventListener("click", () => { + counter++; + button.innerText = "I have been pressed " + counter + " times!"; +}); +``` + +In the example above we add an +*[event listener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)* +to a button: the (anonymous) function we defined is going to be called +every time the button is clicked. And since this is a web page, I guess +I can show you what this actually looks like. + +Behold, the dynamic button: + +
+ +
+ + + +If you are completely new to web development, you may be wondering +where you should write this JavaScript code. One option is to write it +in the same HTML file as the rest of the page, inside a ` + + + +

+ x + + +

+ + + +``` + +Besides the `` element, the only important line for us is line +7, which loads the script from a file. Notice that we use the `defer` +keyword here: this is telling the browser to wait until the whole page +has been loaded before executing the script. If we did not do this, we +could run in the situation where we `document.getElementById()` returns +`null`, because the element we are trying to get is not loaded yet (yes, +this happened to me while I was writing this post). If you want to know +more, check out this +[MDN page](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script#defer). + +Now to the JavaScript code. For now we are going to use the built-in +`*` operator to multiply the two numbers, but in the next section we +are going to replace it with our own library. + +script.js (in the same folder as index.html): + +``` +var aInput = document.getElementById("aInput"); +var bInput = document.getElementById("bInput"); +var button = document.getElementById("goButton"); +var resultText = document.getElementById("resultText"); + +button.addEventListener("click", () => { + var a = Number(aInput.value); + var b = Number(bInput.value); + resultText.innerText = a * b; +}); +``` + +The final result will look something like this: + +

+ x + + +

+ + + +In a real-world scenario you would probably want to check that the text +provided in the input fields is actually a number, or perhaps use the +[`type="number"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number) +attribute for the input fields. But we'll ignore these issues here - +we are going to have more serious problems to deal with. + +## Loading the library and making it a module + +With what we have learned in the previous intermezzo (you are not skipping +those, right?) we can finally run our library code in a real web page. The +code is pretty much the same as above; we just need to include both the +library and the script file in the HTML: + +``` + + +``` + +and of course we have to change the line where we perform the multiplication: + +``` + resultText.innerText = Module._multiply(a, b); +``` + +Here `Module` is the default name given to our library by +Emscripten. Apart from being too generic a name, this leads to another +problem: we can't include more than one Emscripten-built library in our +page in this way - otherwise, both are going to be called `Module`. + +Luckily, there is another way: we can build a +[modularized](https://emscripten.org/docs/compiling/Modularized-Output.html) +library, i.e. obtain a +[JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). +This may sound a bit strange, because the name `Module` kind of implies +there is already a module. The way I understand it is that by default +Emscripten produces a *script* that *contains* a module named `Module`; +when building a modularized library, the whole resulting file is a module. + +Modularizing our build is not necessary right now, but +there are a couple of other advantages to it: + +* As mentioned above, we can change the name of our module and include + more than one Emscripten-built library, if we want. +* We will be able to use the module in the same way in Node.js and in + our web page script. This way we can minimize the differences between + the two versions of our code, which can be useful for testing. +* In case we want to build a more complex layer of JavaScript between + our library and our web page, with a modularized build we can easily + include the module in another file, which can then be included in the + main script. + +So let's go ahead and build our library like so: + +``` +emcc -sEXPORTED_FUNCTION=_multiply -sMODULARIZE -sEXPORT_NAME=MyLibrary \ + -o library.mjs library.c +``` + +Notice I have changed the extension from `.js` to `.mjs`. Don't worry, +either extension can be used. And you are going to run into issues with +either choice: + +* If you run your code in Node.js, it will understand that the library + file is a module only if you use the `.mjs` extension. Alternatively, + you can change some settings in a local configuration file to + enforce this. +* If you run your code in a web page, your web server may not be + configured to serve `.mjs` files as JavaScript files. This can + easily be changed by adding a configuration line somewhere. + +In my examples I chose to use the `.mjs` extensions to make Node.js +happy, and I changed the configuration of my web servers as needed. For +example, for darkhttpd I added a file called `mime.txt` with a single +line `text/javascript mjs`, and launched the server with the +`--mimetypes mime.txt` option. + +Now we have to make a couple of changes. Our `program.js`, for running +in node, becomes: + +``` +import MyLibrary from "./library.mjs" + +var myLibraryInstance = away MyLibrary(); + +const result = myLibraryInstance(6, 7); +console.log("The answer is " + result); +``` + +By the way, I have renamed this file to `program.mjs`. This is because +only modules can use the +[static `import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) +statement; alternatively, I could have used the +[dynamic `import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) +and kept the `.js` extension. + +Similary, we have to update our `script.js` (or `script.mjs`) to import +the module and create an instance. Moreover, we have to specify in the +HTML that the script is now a module: + +``` + +``` + +And we can get rid of the other `

- x + x

@@ -467,7 +467,7 @@ button.addEventListener("click", () => { The final result will look something like this:

- x + x

-- cgit v1.3 From ccc759c09c0738efce3d3f6f063b98fc55c27802 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Mon, 9 Jun 2025 09:33:40 +0200 Subject: Added note to old blog post due to feedback --- src/blog/2024-09-20-c-scripting/c-scripting.md | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/blog') diff --git a/src/blog/2024-09-20-c-scripting/c-scripting.md b/src/blog/2024-09-20-c-scripting/c-scripting.md index de70812..49ea366 100644 --- a/src/blog/2024-09-20-c-scripting/c-scripting.md +++ b/src/blog/2024-09-20-c-scripting/c-scripting.md @@ -75,6 +75,11 @@ systems! The command is also included in FreeBSD (since 4.3) and MacOS (since 13), and there are probably workarounds to make the same concept work without it. +*Edit 2025-06-09: as a reader points out, `realpath` is included +in the most recent POSIX standard, +[POSIX.1-2024](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/realpath.html), +so it should be more widely available in the future.* + ## Credits I did not come up with this trick - I read about it in at least -- cgit v1.3 From 86059c69cdd5fc46fee8cb4a0c859d211f4a2058 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Fri, 13 Jun 2025 15:11:45 +0200 Subject: Blog post --- .../cargo-culture-shock.md | 332 +++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md (limited to 'src/blog') diff --git a/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md new file mode 100644 index 0000000..e17f89d --- /dev/null +++ b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md @@ -0,0 +1,332 @@ +# Cargo culture shock 🦀 + +After a long adventure +[porting my cube solver to the web](../2025-06-06-webdev), I decided to +try out something completely different, like learning a new language. +Rust is one on my to-do list, and it has been there for more than 10 +years - I remember reading about it in my first year in university, +so it must have been 2013 or early 2014. + +So I started by quickly reading through the first few chapters of +[the book](https://doc.rust-lang.org/book/) to get an idea of the basic +syntax. Before I move on to implementing something, I thought I could +share my very early impression of the language and the tooling around it. + +*Note: this is a relaxed write-up and I have a very superficial +understanding of the topic. I am going to use strong words for the things +that I did not like, but there are many things I like about Rust so +far. In fact, my first impression of the language, the documentation and +the tools is very positive! Keep this in mind while reading this post.* + +## What is Rust about? + +Before getting started with the Rust book, my impression was that Rust +was mostly about *safety* as in *memory safety*, and that it achieved +this by enforcing strict rules, leading to more correct programs overall. + +But [the foreword](https://doc.rust-lang.org/book/foreword.html) says "the +Rust programming language is fundamentally about *empowerment*". Uh, that's +weird. I was expecting "something something *safety*". And actually, +I was hoping for "something something *correctness*". Definitely not +"something something *empowerment*". *Empowerment* sounds like one of +those meaningless words that managers use when they have nothing to say. + +Anyway, empowerement it is. Let's move on to actually using the thing. + +## Installation + +The officially endorsed way of installing Rust is the following: + +``` +curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh +``` + +The horror, the horror! Is this really what they suggest? The very thing +that *everyone* told you not to do in nerd forums until a few years ago, +even worse than copy-pasting commands from the internet, is now THE +suggested way of installing software? Seriously, piping a random web +page into `sh`? + +I don't know what this script does, is it going to download even more +random shit from the internet? (Answer: yes, it is!) Is it going to fuck +up my environment by puking configuration lines into my .bashrc? (Answer: +yes, it is!) + +But ok, what do I know. Maybe I am just leaving in the past, I should +embrace the future, because package managers and `make && make install` +*are so 1999, man*. So I took a deep breath an run the command. +Luckily the script kindly asked "do you want me to fuck up your +environment? [default: Yes]". I may be paraphrasing this, it probably +mentioned `.bash_rc` and `.profile`. Anyway, you can easily say "no" +and skip this fuckery. And then manually update a couple of environment +variables in your shell configuration file, which is what the script was +trying to do. I guess developers today are not expected to know how to +do this. + +The installation was quick and everything went fine. Moving on! + +## Cargo cult + +[Cargo](https://doc.rust-lang.org/book/ch01-03-hello-cargo.html) is +Rust's build system and all-in-one tool. It can create a new project +with a default folder structure, compile code (running `rustc` under +the hood`), run tests, and more. Oh and it is also a *package manager*, +which apparently is something modern programming languages decided they +needed - more on that later. + +I don't always like these kind of tools, becase they tend to do a lot of +things that you don't understand with the files in the current directory. +Like vomiting generated files that you are supposed to check into your +[VCS](https://en.wikipedia.org/wiki/Version_control) - which kinda +defeats the purpose of *generating* files in the first place, doesn't it? + +Luckily, Cargo does not do too much of this. It generates an `src` folder +with a simple "hello world" program, a pretty minimal `Cargo.toml` +configuration file, and it initializes a git repo. All reasonable, +and most importantly easy to understand. + +On the first run of `cargo build` it also generates a `target` folder +for the build artifacts (already in `.gitignore`) and it generates +only one of those vomit files that I am supposed to check into git +(`Cargo.lock`), which I promptly added to .gitignore. Apparently it is +mainly about dependencies, and I'd rather not use any for now. So all +in all I am satisfied with this process. + +About dependencies, you can specify some packages, or *crates*, that your +projects depends on. These *crates* are going to be downloaded as they are +needed from [crates.io](https://crates.io/). You can of course specify +some constraints on the version for each specific crate, like "at least +2.0" or "at least 0.8.5, but less than 0.9.0". Here is where `Cargo.lock` +comes into play: if there is more than one version available for a given +crate that satisfies the contraints you impose in `Cargo.toml`, the first +time a specific version is used it gets written to `Cargo.lock`, so that +you will keep using that version even if a new one becomes available. +You are supposed to check in this file to git so other developers working +on the same project will use exactly the same version of each dependency. + +I can't quite understand this. If I don't want to update to a later +(minor) version of the dependency, I can already specify this in +`Cargo.toml`. If instead I impose more relaxed requirements, then it +means that any version satisfying those requirements is fine, and I +in this case *I do want* to try different versions within the allowed +range, to make sure that my assumptions are correct. So I am confused +about why this Cargo.lock business is needed at all. But I guess if +the time ever comes that I need to include dependencies in my project - +probably in the far future, *right?* - then I can just specify an exact +version and happily ignore `Cargo.lock`. + +Anyway, coming from a mostly C and C++ background where there is no +standard way of including dependencies, all of this is certainly quite +interesting. + +## Dependencies? + +Chapter 2 contains a simple code example. +[This part](https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#generating-a-secret-number) +caught my attention: + +"Rust doesn't yet include random number functionality in its standard library." + +Uh, ok. And then people complain about C having a small standard library. +It then continues: + +"However, the Rust team does provide a +[`rand` crate](https://crates.io/crates/rand) with said functionality." + +Ok, but... if this is provided by the Rust team, why not including it in +the standard library? Maybe it is still in beta, Rust is not a "finished" +project as far as I understand. I would think 15 years is enough to ship +a random number generator, though. + +The tutorial tell us to add `rand` to our dependencies in `Cargo.toml`. +Simple enough. But then, if we launch `cargo build` again: + +``` +cargo build + Updating crates.io index + Locking 15 packages to latest Rust 1.85.0 compatible versions + Adding rand v0.8.5 (available: v0.9.0) + Compiling proc-macro2 v1.0.93 + Compiling unicode-ident v1.0.17 + Compiling libc v0.2.170 + Compiling cfg-if v1.0.0 + Compiling byteorder v1.5.0 + Compiling getrandom v0.2.15 + Compiling rand_core v0.6.4 + Compiling quote v1.0.38 + Compiling syn v2.0.98 + Compiling zerocopy-derive v0.7.35 + Compiling zerocopy v0.7.35 + Compiling ppv-lite86 v0.2.20 + Compiling rand_chacha v0.3.1 + Compiling rand v0.8.5 + Compiling guessing_game v0.1.0 (file:///projects/guessing_game) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.48s +``` + +Woho wait a minute. I have only added one dependency! Just a random +number generator! Why is it building 15 packages now? Where does all +of this come from? Surely these are not dependencies dragged in by +the `rand` crate, *right?* + +Well, apparently they are. If we want to generate a random number, we +need 15 external packages. I would imagine this makes Rust completely +unusable in a professional setting, because nobody would want to audit +15 separate crates just to include a random number generator. And no +professional programmer would include dependencies in a serious project +without first auditing them, *right?* *RIGHT?!* + +I mean, each dependency introduces an extra liability in your project; +it is code that you can't control and you never know when it is going to +break. And this is why serious programmers, select dependencies carefully +and only use them when absolutely necessary... *right?* + +Ahah, of course they do not. Remember the +[letf-pad incident](https://en.wikipedia.org/wiki/Npm_left-pad_incident)? +Apparently Cargo is of the same breed as npm. And then we wonder why +the software industry is such a shitshow. *Just keep building on top +of the house of cards, man. It's going to be fine, man, don't bother +implementing low-level stuff, that's hard. Just trust random people on +the internet and ship their code, man.* + +Bleah. + +## The Rust language + +So far I talked mostly about the tooling around Rust, but I said nothing +about the language itself. I want to write some code before making a +more informed opinion about it, but the first impression is that it is +really nice! + +I love the type system, especially +[enums](https://doc.rust-lang.org/book/ch06-00-enums.html) +and `match`. I like that this and stuff like `Option` are +first-class citizens; you can do something similar in C++ with +[`std::variant`](https://doc.rust-lang.org/book/ch06-00-enums.html) and +[`std::optional`](https://en.cppreference.com/w/cpp/utility/optional.html), +but the syntax quickly gets messy. I guess this shows the advantage of +making a new language from scratch instead of being forced to live with +decades-old syntax for backwards compatibility. + +I especially like this mechanism - Rust's enums, or `std::variant`, or +tagged unions, whatever you like to call them - for error handling. I find +them a better solution than exception, because they enforce correctness: +if your function can fail, your coding must handle it; you can't just +let an error message bubble up from the depths of Hell, wreak havoc in +your control flow and face the user with an "Object reference not set +to an instance of an object". I think it is great that Rust endorses +this more thorough way of error handling from the start. + +On the less-nice side of things, apparently +[integer overflow](https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=overflow#integer-overflow) +is still an issue. Eh. At least when compiling in debug mode overflows +are caught, which is nice. But in release mode the program is +going to "panic", which I was not expecting. I mistakenly thought +this was one of the issues that Rust was solving at compile time, +or maybe even at the level of language specification. + +This brings me to another problem that I thought Rust would solve, +but it does not: accessing a out-of-bound index of an array also leads +to a panic. I was expecting "don't allow indexes out of bound" to be +included in Rust's compile-time checks, somehow. For example in Ada you +can declare an array to accept only values of a specific +[range type](https://en.wikibooks.org/wiki/Ada_Programming/Types/range), +so that out-of-bound errors are completely eliminated. Apparently a +"panic" is memory safe behavior, but it is definitely not *correct* +behavior. I guess this was a big misunderstanding from my side: memory +*safety* does not mean memory *correctness* - Rust still allows you to +make mistakes related to memory. + +## Documentation + +A quick note on the documentation: it is very nice. The book is well +written, and I am enjoying reading through it, even though it is very +basic for me. It is also available offline - at least when installing +rust via `rustup` - which is something I always appreciate. + +The compiler error messages, which I consider to be part of the +documentation as well, are simply amazing. Not only they are usually very +precise, not only they often suggest a fix, but they also point you out to +some piece of documentation so that you can learn why your code is wrong. +For example, if you try to compile this code with `rustc filename.rs`: + +``` +fn main() { + println("Hello, world!"); +} +``` + +You get: + +``` +error[E0423]: expected function, found macro `println` + --> hello.rs:2:2 + | +2 | println("Hello, world!"); + | ^^^^^^^ not a function + | +help: use `!` to invoke the macro + | +2 | println!("Hello, world!"); + | + + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0423`. +``` + +And if you run `rustc --explain E0423` as suggested: + +``` +An identifier was used like a function name or a value was expected and the identifier exists but it belongs to a different namespace. + +Erroneous code example: + +struct Foo { a: bool }; + +let f = Foo(); +// error: expected function, tuple struct or tuple variant, found `Foo` +// `Foo` is a struct name, but this expression uses it like a function name + +Please verify you didn't misspell the name of what you actually wanted to use here. Example: + +fn Foo() -> u32 { 0 } + +let f = Foo(); // ok! + +It is common to forget the trailing ! on macro invocations, which would also yield this error: + +println(""); +// error: expected function, tuple struct or tuple variant, +// found macro `println` +// did you mean `println!(...)`? (notice the trailing `!`) + +Another case where this error is emitted is when a value is expected, but something else is found: + +pub mod a { + pub const I: i32 = 1; +} + +fn h1() -> i32 { + a.I + //~^ ERROR expected value, found module `a` + // did you mean `a::I`? +} +``` + +I would not be surprised if the main reason why Rustaceans are so +enthusiastic about the language was actually how nice `rustc` is. +I expect working with this tool will be very pleasant. + +## Moving on! + +Apart from the +[culture shock](https://en.wikipedia.org/wiki/Culture_shock) of the +installation process and the dependency management, my first impression +of Rust is quite positive. But as I said, I am just getting started +and my judgement is very superficial. I do want to write some small +project in Rust, and I think I'll start from re-writing a simple [math +library for modular arithmetic](../2025-01-21-taming-cpp-templates) +that I wrote in C++ some time ago. + +Stay tuned for more 🦀 -- cgit v1.3 From 4473fea430fbaddaf67afca77ed331067882a638 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Fri, 13 Jun 2025 15:26:47 +0200 Subject: Update link and fix target --- src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md | 2 +- src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/blog') diff --git a/src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md b/src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md index fe1ca5d..bce440f 100644 --- a/src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md +++ b/src/blog/2025-01-21-taming-cpp-templates/taming-cpp-templates.md @@ -269,7 +269,7 @@ A classic example is [`std::tuple`](https://en.cppreference.com/w/cpp/utility/tuple), which works similarly to `std::pair`, but accepts any number of items. -## Contraints and concepts +## Constraints and concepts To explain the last features I want to talk about, I am going to use a simlpe, albeit slightly unusual, example: let's implement a class diff --git a/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md index e17f89d..094c88c 100644 --- a/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md +++ b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md @@ -325,8 +325,8 @@ Apart from the installation process and the dependency management, my first impression of Rust is quite positive. But as I said, I am just getting started and my judgement is very superficial. I do want to write some small -project in Rust, and I think I'll start from re-writing a simple [math -library for modular arithmetic](../2025-01-21-taming-cpp-templates) +project in Rust, and I think I'll start from re-writing a simple +[math library for modular arithmetic](../2025-01-21-taming-cpp-templates/#constraints-and-concepts) that I wrote in C++ some time ago. Stay tuned for more 🦀 -- cgit v1.3 From 1b3e3705b83d49dafe67b325e314dfc83a493b98 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Sat, 14 Jun 2025 15:47:12 +0200 Subject: Fix typo --- src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/blog') diff --git a/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md index 094c88c..270ceee 100644 --- a/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md +++ b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md @@ -52,7 +52,7 @@ random shit from the internet? (Answer: yes, it is!) Is it going to fuck up my environment by puking configuration lines into my .bashrc? (Answer: yes, it is!) -But ok, what do I know. Maybe I am just leaving in the past, I should +But ok, what do I know. Maybe I am just living in the past, I should embrace the future, because package managers and `make && make install` *are so 1999, man*. So I took a deep breath an run the command. Luckily the script kindly asked "do you want me to fuck up your -- cgit v1.3 From 5fe4b5bce18ede354b29620bbdfff179300710f1 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Sat, 14 Jun 2025 16:14:31 +0200 Subject: Remove highlight from link --- src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/blog') diff --git a/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md index 270ceee..1c65e39 100644 --- a/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md +++ b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md @@ -218,7 +218,7 @@ to an instance of an object". I think it is great that Rust endorses this more thorough way of error handling from the start. On the less-nice side of things, apparently -[integer overflow](https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=overflow#integer-overflow) +[integer overflow](https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-overflow) is still an issue. Eh. At least when compiling in debug mode overflows are caught, which is nice. But in release mode the program is going to "panic", which I was not expecting. I mistakenly thought -- cgit v1.3 From c225088f94f6efa3a6f670011596691b862448f4 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Thu, 26 Jun 2025 11:43:41 +0200 Subject: Blog post --- .../2025-06-26-borrow-checker/borrow-checker.md | 150 +++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/blog/2025-06-26-borrow-checker/borrow-checker.md (limited to 'src/blog') diff --git a/src/blog/2025-06-26-borrow-checker/borrow-checker.md b/src/blog/2025-06-26-borrow-checker/borrow-checker.md new file mode 100644 index 0000000..b22d741 --- /dev/null +++ b/src/blog/2025-06-26-borrow-checker/borrow-checker.md @@ -0,0 +1,150 @@ +# Stunned by the borrow checker 🦀 + +As I mentioned in my [last post](../2025-06-13-cargo-culture-shock), +in the last couple of weeks I have been learning Rust. I have written +[a small library](https://git.tronto.net/zmodn-rs/file/README.md.html) for +[integers modulo N](https://en.wikipedia.org/wiki/Modular_arithmetic) +(the original C++ version was mentioned in +[this post](../2025-01-21-taming-cpp-templates), rewritten +[my implementation](https://git.tronto.net/ecm/file/README.md.html) +of the +[ECM algorithm](https://en.wikipedia.org/wiki/Lenstra_elliptic-curve_factorization) +(mentioned in [this other post](../2025-02-27-elliptic-curves-javascript)) +and I am now playing around with some past +[Advent of Code](https://adventofcode.com/) problems. + +But today I won't talk about any of the above. Instead, I just want to show +you a small example of code that kept me confused for a couple of hours. + +First, I need to very briefly explain Rust's concept of ownership. + +## The borrow checker + +The *borrow checker* is a unique feature of Rust that prevents certain +kinds of memory errors and data races. Without going into too much +detail, the borrow checker is a compile-time mechanism that ensures that, +at any given point, a given object is owned by at most one reference, +unless all references to it are *immutable* (that is, they don't allow +modifying the object). + +As a simple example, the following code is not valid: + +``` +fn main() { + let mut x = 2; // mut means mutable, without it x would be a constant + let y = &x; // & means reference + x = 3; + println!("{x} {y}"); +} +``` + +And the compiler gives a clear explanation: + +``` +error[E0506]: cannot assign to `x` because it is borrowed + --> t.rs:4:5 + | +3 | let y = &x; + | -- `x` is borrowed here +4 | x = 3; + | ^^^^^ `x` is assigned to here but it was already borrowed +5 | println!("{x} {y}"); + | --- borrow later used here +``` + +What happens is that creating a (mutable) reference `y` that refers to +`x`, *borrows* the object referred to by the name `x`, so `x` cannot be +used directly anymore until `y` goes out of scope. + +If you want to know more, check out the +[ownership chapter in the book](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html). + +## A tricky example + +Let's say we have a vector of vectors, and we want to copy an element from +one of the internal vectors to another. We could try something like this: + +``` +fn main() { + let mut v = vec![vec![23], vec![42]]; // v is now {{23}, {42}} + v[0].push(v[1][0]); +} +``` + +But this won't compile. Indeed we get: + +``` +error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable + --> a.rs:3:15 + | +3 | v[0].push(v[1][0]); + | - ---- ^ immutable borrow occurs here + | | | + | | mutable borrow later used by call + | mutable borrow occurs here + | + = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices +``` + +However, the following works just fine: + +``` +fn main() { + let mut v = vec![vec![23], vec![42]]; + let x = v[1][0]; + v[0].push(x); +} +``` + +And at this point I was very confused. Whatever the borrow checker does, +shouldn't the two pieces of code do exactly the same? I got stuck for +a while thinking that for some reason the function argument of `push()` +was passed by reference in the first case, while it was copied in the +second, but this is not where the problem lies. + +I was finally able to understand the problem when I realized that my first +piece of code is equivalent to the following, which also does not compile: + +``` +fn main() { + let mut v = vec![vec![23], vec![42]]; + let mut v0 = &mut v[0]; + let x = v[1][0]; + v0.push(x); +} +``` + +Can you see the issue now? + +## Explanation + +Like in C++ and many other languages, the square +bracket operator is a method on the vector object. +More precisely, in Rust it is syntactic sugar for either the +[`index()`](https://doc.rust-lang.org/std/ops/trait.Index.html) or the +[`index_mut()`](https://doc.rust-lang.org/std/ops/trait.IndexMut.html) +functions, depending if mutability is requested in our usage. In our +example, when we call `v[0].push(...)` this will be translated to a call +to `index_mut()`, because `push()` requires a mutable reference; when +we do e.g. `let x = v[1][0]`, the immutable version will be call instead. + +But the details of `[]` are not important for us. The cause of the problem +is that `let mut v0 = &mut v[0]` creates a mutable reference to *part +of* the object `v`. At this point, `v` is borrowed and cannot be used +directly anymore, even if we just want to immutably access some other +parts of it to make a copy. Thus, when we try to do `let x = v[1][0]`, +the borrow checker complains. + +In the first version of my code all of this happens in the same line, +and this makes it confusing, because the order in which the various +statements of that line are executed is very important. + +## Solution + +Solving the problem is easy, I can just use the second version of my code. +Alternatively I could also try to use `split_at_mut()` as suggested +by the compiler, but this seems overkill in this case; good to keep in +mind though. + +But sometimes understanding a problem is more important than finding +a solution. -- cgit v1.3 From afec8caa5b74e81e7427cdf1240711429e2d9ed8 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Sat, 16 Aug 2025 20:35:54 +0200 Subject: New blog post --- .../alpine-declarative.md | 106 +++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/blog/2025-08-16-alpine-declarative/alpine-declarative.md (limited to 'src/blog') diff --git a/src/blog/2025-08-16-alpine-declarative/alpine-declarative.md b/src/blog/2025-08-16-alpine-declarative/alpine-declarative.md new file mode 100644 index 0000000..f186ea2 --- /dev/null +++ b/src/blog/2025-08-16-alpine-declarative/alpine-declarative.md @@ -0,0 +1,106 @@ +# Declarative package management with Alpine Linux + +It has been almost two months since my last update, and it feels a +bit weird because I have done plenty of tinkering that would be a +good fit for this blog! But for one reason or another, I did not +feel like writing about any of this in the usual level of detail. + +However, one of the last things I did is quite simple to explain, so here +is a short post about it. + +## Alpine linux + +A couple of days ago I decided to try out +[Alpine Linux](https://www.alpinelinux.org), a lightweight distro that +uses [musl libc](https://www.musl-libc.org/) instead of GNU libc and +[busybox](https://busybox.net) instead of GNU coreutils. + +Alpine's package manager is called Alpine Package Keeper (APK, not to +be confused with Android Package, which also goes by APK). It is very +fast and simple to use, and it has an especially cool feature: the +list of all manually installed packages is kept in the plain text file +`/etc/apk/world`. This file can be edited by hand, and running `apk fix` +will then add or remove packages to satisfy the list in the file. Neat! + +## Declarative package management + +Unfortunately, APK will also overwrite `/etc/apk/world` by removing +empty lines and sorting the packages in alphabetical order, one per +line. Moreover, there is no option to write comments in this file. +So one cannot really use it as a (commented) list of the packages they +want to keep installed. + +At first I thought I could keep such a list in a separate file, and write +a script to parse this file, remove the comments, write the result to +`/etc/apk/world` and then run `apk fix`. + +But then I thought it would be even better to merge the two, and here +is the result: + +``` +#!/bin/sh + +# Make a backup copy of the previous /etc/apk/world +cp /etc/apk/world /etc/apk/world.backup + +echo " + +# Base system packages (installed by default) +alpine-base busybox-mdev-openrc doas grub-efi openssh openssl + +# Firmware (installed by default, system-dependent) +linux-firmware-i915 linux-firmware-intel linux-firmware-mediatek linux-firmware-other +linux-firmware-rtl_bt linux-firmware-rtl_nic linux-firmware-xe linux-lts + +# Documentation +docs mandoc-apropos + +# Wifi and other hardware control +iwd openresolv pciutils bluez + +# Audio +pulseaudio pulseaudio-bluez pulseaudio-alsa alsa-plugins-pulse pulseaudio-utils pulsemixer + +# Core tools (non-X) +coreutils-fmt curl imagemagick ffmpeg tmux ghostscript ncurses shellcheck +kbd # For console keyboard configuration +syncthing fzf sfeed yt-dlp mblaze msmtp oath-toolkit + +# Development tools +build-base git gdb valgrind clang20 python3 rust cargo hare +lowdown darkhttpd # Both used for updating my website +libx11-dev libxft-dev libxinerama-dev + +# Xorg xorg-server +xinit eudev mesa-dri-gallium xf86-video-intel xf86-input-libinput xf86-input-synaptics +setxkbmap xsel xbanish xsetroot xwallpaper xev slock + +# X applications +firefox libreoffice telegram-desktop vlc imv-x11 +zathura-djvu zathura-pdf-mupdf zathura-ps +arandr + +# Fonts, but like a gazillion of them +font-terminus font-noto font-noto-extra font-arabic-misc +font-misc-cyrillic font-mutt-misc font-screen-cyrillic +font-winitzki-cyrillic font-cronyx-cyrillic font-noto-arabic +font-noto-armenian font-noto-cherokee font-noto-devanagari +font-noto-ethiopic font-noto-georgian font-noto-hebrew font-noto-lao +font-noto-malayalam font-noto-tamil font-noto-thaana font-noto-thai + +" | sed 's/#.*//' | grep -v '^[:space:]*$' > /etc/apk/world +apk fix +``` + +The bulk of the file is a +[here document](https://en.wikipedia.org/wiki/Here_document) with a long +list of all the packages I want installed - Alpine is really minimal! The +few lines of code above and below this list are there to make a backup a +copy of the old `/etc/apk/world`, filter out the comment from the new list +using [sed](../2023-12-03-sed) and [grep](../2023-08-20-grep), +copy the new list to `/etc/apk/world` and finally run `apk fix`. + +Now when I want to add a new piece of software to my system +I edit this file and then run it as root. A dead simple +way to do declarative package management - take that, +[Nix](https://en.wikipedia.org/wiki/Nix_(package_manager))! -- cgit v1.3