Friday, December 31, 2021

Loading data asynchronously with Vue and Pinia

When working with Vue 3 Composition API and Pinia, I struggled a bit to put out an example of loading async data, where the request is handled by the Pinia store, which also owns the data.

It’s 2 AM and, finally, here is the store:

import {ref, reactive} from 'vue';
import {defineStore} from 'pinia';

const useStore = defineStore('fruits', () => {
	const fruits = reactive<string[]>([]);
	const loading = ref(false);

	return {
		fruits,
		loading,
		loadFruits(): Promise<void> {
			loading.value = true;
			return new Promise((resolve, _reject) => {
				setTimeout(() => {
					fruits.push('lemon', 'orange', 'peach', 'strawberry');
					loading.value = false;
					resolve();
				}, 2000);
			});
		},
	};
});

export default useStore;

And this is the single file component, which calls the store asynchronously:

<template>
	<div v-if="loading">Loading...<div>
	<div v-if="!loading">
		<div v-for="fruit of fruits" :key="fruit">
			{{fruit}}
		</div>
	</div>
</template>

<script setup lang="ts">
import {computed, onMounted} from 'vue';
import useStore from '@/model/store';

const store = useStore();
const loading = computed(() => store.loading);
const fruits = computed(() => store.fruits);

onMounted(() => {
	store.loadFruits();
});
</script>

Although I’m not seeing much advantage of Vue over React, Pinia is so much better than Redux insanity. Even with Redux Toolkit. I hope they don’t mess it up when Pinia becomes Vuex 5, as said.

Tuesday, December 28, 2021

Two-way binding input in React

As a follow-up to my big disappointment with Vue due to the lack of typed emits, even with TypeScript, I came back to think about a two-way binding input in React. Turns out, it’s pretty trivial to implement.

import { useState } from 'react';
	
interface TwoWayValue {
	value: string;
	setValue: (value: string) => void;
}

interface Props {
	data: TwoWayValue;
}

function TwoWayInput(props: Props) {
	return <input type="text" value={props.data.value}
		onChange={e => props.data.setValue(e.target.value)} />;
}

function useTwoWayState(initialValue: string): TwoWayValue {
	const [value, setValue] = useState(initialValue);
	return { value, setValue };
}

export { TwoWayValue, TwoWayInput, useTwoWayState };

Usage:

import { TwoWayInput, useTwoWayState } from 'two-way-input';

function App() {
	const nome = useTwoWayState('');

	return <div>
		<h1>{nome.value}</h1>
		<TwoWayInput type="text" data={nome} />
	</div>;
}

Now I’m still wondering about scoped CSS.

Tuesday, December 14, 2021

Install and uninstall Rust nightly

When developing the full-module refactoring of WinSafe, I found that it’s possible to tag the items with their respective required Cargo features. However, as many things in Rust, this is still available only in the nightly toolchain.

I want to make WinSafe available for the stable toolchain, so I needed to install nightly just to see how that stuff worked out, and then uninstall it after the tests.

Rust nightly can be installed and uninstalled with the following commands:

rustup install nightly
rustup toolchain remove nightly

To see which toolchains are installed:

rustup show

Thursday, December 2, 2021

RAII guards in Rust

While experimenting with scope rules in Rust, I ended up implementing a recurrent idea I had – a RAII guard that runs a function when the object goes out of scope. I’m well aware of scopeguard crate, but it has some stinky unsafe bits in its implementation.

I was never able to properly implement it, probably because my Rust skills weren’t good enough, but now I did. And I’m rather satisfied with it:

use std::ops::Deref;

/// Returns the object wrapped in a way that the associated closure will run
/// when it goes out of scope.
pub struct Guard<T, D: FnOnce(&mut T)> {
	asset: T,
	dropper: Option<D>,
}

impl<T, D: FnOnce(&mut T)> Drop for Guard<T, D> {
	fn drop(&mut self) {
		self.dropper.take()
			.map(|d| d(&mut self.asset));
	}
}

impl<T, D: FnOnce(&mut T)> Deref for Guard<T, D> {
	type Target = T;

	fn deref(&self) -> &Self::Target {
		&self.asset
	}
}

impl<T, D: FnOnce(&mut T)> Guard<T, D> {
	/// Creates a new `Guard` object.
	pub fn new(asset: T, dropper: D) -> Guard<T, D> {
		Self { asset, dropper: Some(dropper) }
	}
}

I though about writing stuff like this in WinSafe:

impl HWND {
	pub fn GetDC(self) -> WinResult<Guard<HDC, impl FnOnce(&mut HDC)>> {
		Ok(Guard::new(
			self.GetDC()?,
			move |hdc| {
				self.ReleaseDC(*hdc).expect("Guard crash.");
			},
		))
	}
}

But then there are big implications like CreateMenu, which may or may not require a DestroyMenu call. These dubious behaviors are very unsettling, and many questions arise:

  • Should I write another method, with a _guarded suffix?
  • Mark the unguarded original as unsafe?
  • In the example above, self is copied into the closure – what if it’s still zero?

So by now I believe I’ll leave everything as it is, and let the defer-lite crate at hand when needed.

Still, I greatly miss the idiomatic defer in Go.

Thursday, November 25, 2021

Bash script to deploy my own Rust tools

While developing my own personal tools in Rust – which seems to be “the one” language of my own stuff now, after the disappointment of the consistent Go crashes –, I often need to compile and replace the current *.exe tool. The compile line I use is rather long, plus I want to standardize the steps, so I put out a shell script to automate the tasks.

This shell script must be placed at the project root.

EXE=program-name.exe

echo "Compiling $EXE..."
RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --target x86_64-pc-windows-msvc

echo "Replacing old $EXE..."
mv ./target/x86_64-pc-windows-msvc/release/$EXE /d/Stuff/apps/_audio\ tools/.

echo "Cleaning up..."
rm -rf ./target/release
rm -rf ./target/x86_64-pc-windows-msvc

echo "Done."

I’m not versioning this script because it contains the directories on my computer, plus the command line itself is commented in the Cargo.toml file.

Wednesday, November 17, 2021

Fixing Visual Studio Code icon overlay color in Linux

A few months ago I had to dig into Visual Studio Code source until I found a change in the built-in light theme, where they changed the overlay color of the suspended autocomplete menu. This ended up being a question and an answer on StackOverflow, and it made up to my Windows patch.

There’s no patch for Linux, though. Another jab at my lack of multiplatform GUI framework – but I digress.

So, for the record, with Visual Studio Code standard *.deb installation, this is the file with the light theme:

/usr/share/code/resources/app/extensions/theme-defaults/themes/light_vs.json

The line to be commented out is:

"list.activeSelectionIconForeground": "#FFF"

Tuesday, November 9, 2021

Stateful iterators in Go

I’m writing an insane amount of Go code lately. It’s really unobtrusive, allowing you focus on the problem you have to solve. And I’ve been dabbling with iterators in Go for a while, since I started to used them in Rust – which is so much better than C++ in this regard.

Today, when researching the topic again, I found a very nice article on the matter, exploring a few possibilities. The most performant approach is a stateful iterator, which can be implemented as:

type IterFive struct {
	count int
}

func NewIterFive() IterFive {
	return Iter{}
}

func (it *IterFive) Value() int {
	it.count++
	return it.count - 1
}

func (it *IterFive) Ok() bool {
	return it.count < 5
}

func main() {
	for iter := NewIterFive(); iter.Ok(); {
		println(iter.Value())
	}
}

I’m still unsure whether I should adopt interators instead of simply allocating and returning slices for some functions, but I’m considering it. The main reason would be performance, but it could be qualified as premature in the cases I’m dealing with in Windigo.

Friday, November 5, 2021

Static linking of the C runtime in Rust

By default, Rust on Windows with MSVC toolchain compiles to a dynamically linked C runtime, apparently following the default configuration on Visual Studio, which uses the /MD compiler flag. In order to use static linking for the C runtime, we must change this to /MT, something I’m used to do in my C++ Visual Studio projects.

I found notes about Rust linkage, which hints us specifically about these /MD and /MT switches, although not citing them explicitly. The way to specify static linking is through RUSTFLAGS environment variable:

RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-pc-windows-msvc

Which is rather ugly, let me say. At least it appears to work, and this is the command line I’m using for my release builds now.

Friday, October 22, 2021

Displaying custom fields in foobar2000

Today I found the TOPE frame in the ID3v2 tag specification, which can be used in cover songs. I was simply using the comment frame so far, and having a specific frame for that is great. Now I needed to display this frame in foobar2000, my player of choice for years.

I created an account in foobar2000 forums and posted the question there. Then I just found a specific subreddit, and I posted the same question.

Much to my amusement, I got answers in both after a few minutes.

Turns out, I vaguely remember having done this before. One must navigate to Preferences > Advanced > Display > Properties dialog > Standard fields, and add:

Original Artist=ORIGINAL ARTIST;

And it worked.

Tuesday, September 28, 2021

Enhancing Chrome 94 privacy

Unfortunately Firefox is getting worse every day, specially after the introduction of the horrible v89 interface. Having to resort to Chrome again demands some precautions.

Latest Chrome 94 introduced an idle detection feature, which is a privacy nightmare. Fortunately, we still can disable it here:

chrome://settings/content/idleDetection

And don’t forget to also disable the privacy sandbox and FLoC features:

chrome://settings/privacySandbox

I really wish I could go back to ol’ good Firefox.

Friday, September 17, 2021

My Visual C++ 2019 settings

Just for the record, these are the settings I’m always using with Visual Studio 2019, when creating a new, empty C++ project:

  • General
    • Output Directory: $(SolutionDir)$(Platform)_$(Configuration)\
    • Intermediate Directory: $(Platform)_$(Configuration)\
    • C++ Language Standard: ISO C++20 Standard (/std:c++20)
  • C/C++
    • General
      • Debug Information Format: None (Release)
      • Treat Warning As Errors: Yes (/WX) (Release)
      • Multi-processor Compilation: Yes (/MP)
    • Code Generation
      • Runtime Library: Multi-threaded (/MT) (Release)
  • Linker
    • Debugging
      • Generate Debug Info: No (Release)
    • System
      • SubSystem: Windows (/SUBSYSTEM:WINDOWS)

As of April 2024, the settings above also apply to Visual Studio 2022.

Tuesday, September 14, 2021

Adding a branch from another repo in Git

This should be something rather rare, but for some reason I keep needing to do it very often: I want to add a new, unrelated branch into an existing repository, and this branch comes from another repository.

While needing to do this yet again today, I found a rather elegant solution here:

git switch --orphan NEW_BRANCH
git pull FORK_URL FORK_BRANCH

It works remarkably well and clean. Props to the guy who shared this.

Tuesday, August 3, 2021

Deep cloning a generic List in Java

Today I had to deep clone objects in Java. What a nightmare.

First, the Cloneable interface is horribly broken, and writing such a simply code becomes a laborious task. Second, if the object has a List as a field, you must manually clone the elements into a new List.

The code goes as it follows:

public class Foo implements Cloneable {

	private String name;

	private List words;

	@Override
	public Object clone() {
		try {
			Foo cloned = (Foo) super.clone();
			cloned.words = Auxiliar.cloneList(words);
			return cloned;
		} catch (CloneNotSupportedException e) {
			System.out.println("Never happens!");
			return null;
		}
	}

	// Getters and setters omitted.

}

Notice the Another.cloneList() call? This is where we manually clone all the List elements, by calling clone() in each one. This is the solution I found for a generic method:

public class Auxiliar {

	@SuppressWarnings("unchecked")
	public static <T extends Cloneable> List<T> cloneList(List<T> list) {
		try {
			List<T> newList = new ArrayList<>(list.size());
			for (T item : list) {
				newList.add((T) item.getClass().getMethod("clone").invoke(item));
			}
			return newList;
		} catch (IllegalAccessException | IllegalArgumentException
			| InvocationTargetException | NoSuchMethodException | SecurityException e) {
			System.out.println("Never happens!");
			return null;
		}
	}
}

No idea why Cloneable was not deprecated and replaced by something minimally decent yet.

Thursday, July 29, 2021

Gibson fretwire

Apparently with my Gibson Les Paul craze back again, I’ve been dabbling into the specs of the High Performance series. The specs list the fretwire as “Low”, without any further info on the actual dimensions. Also, other models are listed as having “Medium Jumbo” fretwire. What do they mean?

Fortunately Gibson Customer Service kindly answered my emails, and these are the measures:

Fretwire Height Width
Medium Jumbo .055" .090"
Low .045" .086"

This information, paired with this fretwire chart and our JP chart, gives us a good comparison with commonly known fret sizes:

Fretwire Height Width
Dunlop 6000 .058" .118"
6100 (Ibanez JPM) .055" .110"
6105 (Ibanez JEM) .055" .090"
6140 (Ibanez Universe) .039" .106"
EBMM medium jumbo (Majesty) .051" .108"
high wide (JP6) .047" .104"
Suhr jumbo .057" .110"
medium .055" .090"
heavy .051" .108"

I just can’t understand, however, why would they use vintage frets in such a modern guitar.

Sunday, July 11, 2021

Accepting both Vec or slice of String or &str in Rust

In whathever programming language, it’s rather common to have a function which accepts an array of strings. Specifically in Rust, there is the notion of slice, which can be used as a reference to a Vec. And also there is the notion of &str, which can be used as a reference to String. Putting both concepts together as an argument is not trivial, though.

Turns out I found a way to make it work by using the AsRef trait – as many things in Rust, traits lead the way. By using generics, it becomes possible:

fn foo<S: AsRef<str>>(names: &[S]) {
	for name in names.iter().map(|s| s.as_ref()) {
		println!("{}", name);
	}
}

Or alternatively using the impl keyword for the trait bound:

fn foo(names: &[impl AsRef<str>]) {
	for name in names.iter().map(|s| s.as_ref()) {
		println!("{}", name);
	}
}

The function above accepts both Vec and slice of String or &str. But notice how, in order to retrieve the string itself, the as_ref() method must be called.

Friday, July 9, 2021

About these 10 years

Hello, dear reader.

I started this blog exactly 10 years ago, in a Saturday morning, without having much idea about what I’d write here. I just wanted a safe place to vent.

Now, after 10 years, there’s quite an interesting content written down. In many ways, it’s a window to see my own maturing on various topics. Things that matter – or mattered at the time – to me, and nobody else. Well, actually some posts did generate outsiders’ interest but that’s not the point: this is a personal blog in the most actual sense.

But the thing is that it feels like it was yesterday. 10 years went by.

Thursday, July 1, 2021

Missing the ternary operator in Rust

Just out of curiosity, I wrote a Rust macro similar to Visual Basic’s IIf function:

macro_rules! iif {
	($cond:expr, $iftrue:expr, $iffalse:expr) => {
		if $cond { $iftrue } else { $iffalse }
	};
}

Usage:

let n = iif!(1 == 1, "yes", "no");

Close to the ternary operator, which I’m sorely missing in these Rust days.

Wednesday, June 16, 2021

Fixing the horrible Firefox 89 interface

In searching for a fix to the Firefox 89 Proton layout catastrophe, I found a very neat site which tries to summarize all the fixes that can be made in “userChrome.css” file, which can be found in about:profiles internal URL, and enabled with toolkit.legacyUserProfileCustomizations.stylesheets in about:config.

Unfortunately, it’s not possible to restore the menu icons, as of yet, but it’s still a relief from that disaster.

This is my hack for Linux:

/*** Tighten up drop-down/context/popup menu spacing ***/

menupopup > menuitem, menupopup > menu {
	padding-block: 4px !important;
}
:root {
	--arrowpanel-menuitem-padding: 4px 8px !important;
}

/*** Proton Tabs Tweaks ***/

/* Adjust tab corner shape, optionally remove space below tabs */

#tabbrowser-tabs {
	--user-tab-rounding: 0px;
}
@media (-moz-proton) {
	.tab-background {
		border-radius: var(--user-tab-rounding) var(--user-tab-rounding) 0px 0px !important;
		margin-block: 1px 0 !important;
	}
	#scrollbutton-up, #scrollbutton-down { /* 6/10/2021 */
		border-top-width: 1px !important;
		border-bottom-width: 0 !important;
	}
	/* Container color bar visibility */
	.tabbrowser-tab[usercontextid] > .tab-stack > .tab-background > .tab-context-line {
		margin: 0px max(calc(var(--user-tab-rounding) - 3px), 0px) !important;
	}
}

/* Inactive tabs: Separator line style */

@media (-moz-proton) {
	.tabbrowser-tab:not([selected=true]):not([multiselected=true]):not([beforeselected-visible="true"]) .tab-background {
		border-right: 1px solid rgba(0, 0, 0, .20) !important;
	}
	/* For dark backgrounds */
	[brighttext="true"] .tabbrowser-tab:not([selected=true]):not([multiselected=true]):not([beforeselected-visible="true"]) .tab-background {
		border-right: 1px solid var(--lwt-selected-tab-background-color, rgba(255, 255, 255, .20)) !important;
	}
	.tabbrowser-tab:not([selected=true]):not([multiselected=true]) .tab-background {
		border-radius: 0 !important;
	}
	/* Remove padding between tabs */
	.tabbrowser-tab {
		padding-left: 0 !important;
		padding-right: 0 !important;
	}
}

Wednesday, June 2, 2021

Go’s defer in Rust

One of my all-time favorite features of Go is the defer mechanism. It’s so incredibly simple, and so incredibly ingenious and useful. The fact the deferred statements are called during a panic unwind makes me want to use it in other languages, like Rust.

As for Rust, I’m aware of the scopeguard crate, but it seems to be an overkill to such a simple concept. Plus, it does some fishy workaround to make the FnOnce.

So I decided to write my own Rust version of the defer mechanism, which turns out to be a macro that hides the creation of an object. This object holds a FnOnce which runs when at the end of the scope via Drop trait:

/// Internal struct used by the `defer!` macro.
pub struct Defer<F: FnOnce()> {
	func: Option<F>,
}

impl<F: FnOnce()> Defer<F> {
	pub fn new(func: F) -> Self {
		Self { func: Some(func) }
	}
}

impl<F: FnOnce()> Drop for Defer<F> {
	fn drop(&mut self) {
		self.func.take().map(|f| f());
	}
}

/// Defers the execution of a block until the surrounding scope ends.
macro_rules! defer {
	( $($tt:tt)* ) => {
		let _deferred = crate::defer::Defer::new(|| { $($tt)* });
	};
}

Usage is straightforward:

#[macro_use] mod defer; // considering defer.rs at crate's root

fn main() {
	defer! { println!("hi"); }
}

Edit: I published the code above as a crate: defer-lite.

Saturday, May 29, 2021

Sum types in Go

One thing I always missed in Go was sum types. I’ve seen some discussion before. Although Go doesn’t have this as an explicit, native feature, I found a pattern that suits my needs by defining types and interfaces.

In the example below, I simulate a function that would load application resources, which can be extracted by ID, position or a string identifier. First, we define the interface and the subtypes:

type (
	// Variant type for: ResId, ResPos, ResStr.
	Res interface{ implRes() }

	ResId  uint32
	ResPos uint32
	ResStr string
)

func (ResId)  implRes() {}
func (ResPos) implRes() {}
func (ResStr) implRes() {}

The isRes() function acts like a “tag” for each subtype.

Now, a function that receives the variant type:

func LoadResource(variant Res) {
	switch v := variant.(type) {
	case ResId:
		println("ID", uint32(v))
	case ResPos:
		println("Position", uint32(v))
	case ResStr:
		println("String", string(v))
	default:
		panic("Res does not accept a nil value.")
	}
}

Usage is now trivial:

LoadResource(ResId(2001))
LoadResource(ResPos(4))
LoadResource(ResStr("MY_ICON"))

It’s clean and it works remarkably well. I applied this technique in my Windigo library.

Monday, May 17, 2021

Registering file associations in Windows

I always used FileTypesMan to register the Windows file associations. But after Windows 7 or so, some associations simply refused to work, leaving them wrong. In Windows 10, this problem worsened.

Today, after digging the associations created by Reaper, I figured out one of the correct ways to register a file association – there are other exotic ways too, which I couldn’t figure out yet.

These are the Windows Registry entries that must be created:

HKEY_CLASSES_ROOT\.fuu\
    (Default) REG_SZ Fuu.File
    Content Type REG_SZ text/plain
    PerceivedType REG_SZ text
HKEY_CLASSES_ROOT\Fuu.Type\
    (Default) REG_SZ A fuu file.
    DefaultIcon\
        (Default) REG_SZ C:\Temp\fuu.exe,0
    shell\
        (Default) REG_SZ open64
        open64\
            (Default) REG_SZ Open file in Fuu
            command\
                (Default) REG_SZ "C:\Temp\fuu.exe" "%1"

And these entries map correctly in FileTypesMan, which can be used to further changes.

Note that Content Type and PerceivedType will automatically create “Open” entries mapped to default applications, so you probably won’t want those. In its simplest form, this will work:

HKEY_CLASSES_ROOT\.fuu\
    (Default) REG_SZ Fuu.File
HKEY_CLASSES_ROOT\Fuu.Type\
    (Default) REG_SZ A fuu file.
    DefaultIcon\
        (Default) REG_SZ C:\Temp\fuu.exe,0
    shell\
        open\
            command\
                (Default) REG_SZ "C:\Temp\fuu.exe" "%1"

Thursday, May 13, 2021

The ghastly button focus

During my 20 years or so writing Win32 programs, I always relied on SetFocus to focus a control. However, I always noticed a weird behavior on push buttons regarding the BS_DEFPUSHBUTTON style, which was never right. In fact, I remember one of the past WinLamb incarnations which I implemented some trick involving changing the button style when setting the focus.

Anyway, now I found out how to properly set the focus on a control, and it does not involve SetFocus, but rather the WM_NEXTDLGCTL message:

SendMessage(hParent, WM_NEXTDLGCTL, (WPARAM)hButton, MAKELPARAM(TRUE, 0));

And the push button magically sets its own style correctly.

Wednesday, March 31, 2021

Fixing Cargo authentication on GitHub

Today, while performing tests before publishing the first version of WinSafe, I stumbled across a problem I had with Go a few weeks ago. It was caused because GitHub no longer accepts user/password authentications, so everything must be done via SSH. I solved the problem by adding some lines to my ~/.gitconfig file.

But Cargo still cannot fetch packages.

After a lot of digging, I found a setting that finally worked. It involved adding another filter to ~/.gitconfig, which now looks like this:

# Force SSH instead of HTTP
# https://stackoverflow.com/a/27501039/6923555
[url "ssh://git@github.com/"]
	insteadOf = https://github.com/
	
# But crates.io needs to pass
# https://github.com/rust-lang/cargo/issues/8172#issuecomment-659066173
[url "https://github.com/rust-lang/crates.io-index"]
	insteadOf = https://github.com/rust-lang/crates.io-index

The “crates.io” directory was updated and I could use an external crate, something that will be needed when I publish my own.

As a side note, all downloaded crates are stored in ~/.cargo/registry/ directory, which can be safely wiped out to clean the cache.

Thursday, March 25, 2021

Displaying current Git branch in Bash prompt

I decided to show the current Git branch at my bash prompt whenever I’m at a directory which contains a repository. I found a rather convoluted Bash-esque solution, which I adapted to myself:

gitbranch() {
    git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ \1/'
}
#export PS1="\u@\h \[\e[32m\]\w \[\e[91m\]\$(gitbranch)\[\e[00m\]$ "
export PS1="\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\[\e[33m\]\$(gitbranch)\[\e[00m\]\$ "

It’s compact and it works as intended.

Thursday, February 18, 2021

Using local dependencies with Go modules

Go 1.16, released this week, deprecated GOPATH. I used it extensively to develop my libs before publishing them, but now I’m forced to convert them to modules, which work with remote repos by default. However, there’s a way to work with local dependencies.

As I found here, we can instruct Go toolchain to search for a local repo, instead of a remote one by using this command:

go mod edit -replace github.com/username/repo=../repo

This changes the go.mod file. Now, to clean up the go.sum file, run:

go mod tidy

After that, you should be able to use a local dependency just like the old GOPATH days.

The downloaded files are still cached, though. To finally clean the entire cache, run:

go clean -cache -modcache

Modules eventually needed by other applications will be downloaded again when due.

Tuesday, January 5, 2021

Downcasting boxed errors in Rust

Today I was trying to write polymorphic error handling in Rust, something achievable with dynamic_cast in C++, and incredibly easy in Go. In Rust, of course, it’s overcomplicated.

After a couple frustrating hours of searching with no success, I stumbled upon the downcast_ref method of the Error trait, which finally allowed me to write what I wanted. Oddly enough, I couldn’t find this solution anywhere.

Note that the String concrete type cannot be retrieved.

Playground link here.