Stranger Than Usual

Advertising is the graffiti of the rich.

Bilder im Darkmode

Ich habe ja Ende Februar einen Darkmode für dieses Blog eingeführt. Weil ein paar Bilder hier aber transparente Teile haben und nur auf einem hellen Hintergrund funktionieren, habe ich für die Bilder grundsätzlich einen hellen Hintergrund eingestellt.

Das hat natürlich ein paar Nachteile. Insbesondere werden halt Bilder, die keinen hellen Hintergrund brauchen, trotzdem mit einem dargestellt. Das widerspricht der Idee des Darkmode.

Die meisten meiner Bilder hier haben sowieso keine transparenten Teile. Die Bilder mit transparenten Teilen brauchen nicht alle einen hellen Hintergrund. Also habe ich mich entschieden, den hellen Hintergrund optional zu machen. Bilder, die einen hellen Hintergrund benötigen, werden in den Metadatan speziell markiert, ihre <img>-Tags kriegen eine besondere Klasse und können dadurch im CSS anders dargestellt werden.

Man kann das zum Beispiel im Post über das Winkekatzen-Favicon gut sehen. Die Winkekatze ohne Rahmen hat dort einen hellen Hintergrund, die Winkekatze mit Rahmen, die sowohl bei hellen als auch bei dunklen Hintergründen funktionieren soll, hat keinen hellen Hintergrund.

Für den nicht-Darkmode ändert sich übrigens nichts.

Strengeres Clippy

Es gibt da ein Zitat, dessen Urheber ich nicht mehr finde, deswegen gebe ich es hier sinngemäß wieder (das Original war meine ich auf Englisch):

cargo clippy ist für diejenigen, die eine gewisse Leere fühlen, wenn der Rust-Compiler endlich ihren Code akzeptiert.

Der Rust-Compiler ist notorisch dafür, pingelig zu sein (was ich allerdings in diesem Fall für eine gute Eigenschaft halte). Cargo clippy (benannt nach der berüchtigten MS-Office-Büroklammer aus den 90ern) ist ein Linter für Rust, der unschöne unsaubere oder einfach uneinheitliche Codestellen findet, auflistet, und einige davon auch (nach Aufforderung) selbst korrigieren kann.

Clippy hat schon per default einen Haufen Regeln, und ich benutze es schon seit vielen Jahren. Bisher aber immer nur mit den Standardeinstellungen. Bis ich dann letzte Woche durch „This Week in Rust“ auf zwei Blogposts aufmerksam geworden bin: Your Clippy Config Should Be Stricter und als Antwort darauf, von einer anderen Person Your Clippy Config Should Be Stricter-er.

Im ersten Post geht es hauptsächlich darum, welche Lints man aktivieren sollte, wenn man nicht aus Versehen Stellen, die ein panic!() machen können, im Code haben will, plus ein paar anderer Lints für potentielle fallen. Der zweite Post ist eine Antwort darauf, und der Autor vertritt die Ansicht, man solle einfach ganze Listen von Lints freischalten und die, die man nicht haben will, wieder deaktivieren. So hat man eine allow-list und kann keine potentiell nützlichen Lints übersehen.

Ich wollte das einfach einmal ausprobieren und habe dazu direkt mein größtes aktives Rust-Projekt genommen: Den Generator für dieses Blog.

Pedantisch

Als erstes habe ich die „pedantischen“ lints aktiviert, indem ich diese Zeilen in meine Cargo.toml eingefügt habe:

[lints.clippy]
pedantic = { level = "warn", priority = -1 }

Alle weiteren Änderungen, die ich hier an der Cargo.toml vornehme, werden auch in dem lints.clippy-Block stehen.

Die pedantischen Regeln haben sofort zu einem Haufen Fehler geführt. Für einige davon gab es automatische Fixes, für andere nicht. Ich gehe jetzt nicht genauer auf alle Regeln ein, aber ich habe zum Beispiel ein paar hilfreiche Funktionen entdeckt. So kann man map().unwrap_or_else() an einer Option durch ein map_or_else() ersetzen.

Es waren auch ein paar Lints dabei, die theoretisch ein Problem sein könnten, aber praktisch nicht. Wie zum Beispiel ein type casting mit as von einem u64 in ein usize. Hier ging es um Dateigrößen. Praktisch gesehen werde ich keine Dateien haben, die mehr als 4 GiB groß sind (falls usize nur 32 bit groß ist), aber ich habe den Lint trotzdem drin gelassen, grundsätzlich ist er nützlich. Auch ein casting von u64 auf f64 wurde wegen möglichen Präzisionsverlustes bemäkelt. In diesem Fall habe ich die Regel ignoriert, weil ich die Zahl sowieso gerundet habe. Nicht global ignoriert, sondern nur für diese Stelle, damit ich auch an anderen Stellen gewarnt werde. Dazu habe ich am Anfang der Funktion, in der die Warnung auftrat, diese Zeile hinterlassen:

#[expect(clippy::cast_precision_loss, reason = "precision loss not a problem here")]

Ich habe expect genutzt anstatt allow, damit ich eine Warnung kriege, falls diese Ausnahme jemals überflüssig wird (z.B. weil ich den Code geändert habe). Ich finde ich es praktisch, dass man direkt einen Grund angeben kann. Auf diese beiden Details bin ich aber erst durch weitere Lints aus einem anderen Set aufmerksam geworden, aber dazu weiter unten mehr.

Die einzige pedantische Regel, die ich komplett deaktiviert habe, war die Längenbeschränkung für Funktionen. Meiner Erfahrung nach bringen willkürliche Längenbeschränkungen für Funktionen mehr Ärger als Nutzen. Eine lange, aber übersichtliche Funktion möchte ich nicht aufteilen, nur um dem Linter gerecht zu werden. In meinem Code war zwar auch nur eine Funktion davon betroffen, aber ich habe die Regel lieber global deaktiviert, mit folgender Zeile in der Cargo.toml:

too_many_lines = "allow"

Abgesehen davon waren die Regeln eigentlich allesamt recht brauchbar.

Restriktionen

Als nächstes habe ich die restrictions lints aktiviert:

restriction = { level = "warn", priority = -2 }

Und meine Güte. Die sind wirklich nicht dafür geeignet. Die erste Warnung, die man bekommt, ist, dass man diese Regeln nicht en bloc aktivieren sollte. Ich habe das mal ignoriert. Insgesamt sind bei mir seitenweise Warnungen aufgetaucht, von zusammengenommen 40 Regeln (darunter auch die Regel, dass man diese Regeln nicht en bloc aktivieren soll). Nur 11 von den Regeln habe ich behalten, den Rest habe ich deaktiviert. Ich habe die Regeln, die ich behalten habe, auch explizit aktiviert, falls mir die restrictions-Liste irgendwann mal zu nervig werden sollte, dann behalte ich wenigstens die Regeln, die ich für brauchbar befunden habe.

Brauchbare Regeln

Diese Regeln habe ich behalten, teilweise weil ich sie wirklich gut fand, teilweise weil ich sie nicht zu nervig fand:

  • allow_attributes verhindert, dass Regelausnahmen mit [allow()] mache, ich muss stattdessen except nehmen
  • allow_attributes_without_reason erzwingt, dass ich bei Regelausnahmen einen Grund angeben muss
  • ref_patterns erzwingt, statt etwas wie if let Some(ref a) = b etwas wie if let Some(a) = &b zu schreiben
  • default_numeric_fallback verhindert, dass bei Integerliteralen der Fallbacktyp verwendet wird, man muss immer einen Typ angeben
  • str_to_string statt str::to_string() soll lieber str::to_owned() verwendet werden
  • doc_paragraphs_missing_punctuation sorgt für einheitliche Strukturen in Doc-Kommentaren (in diesem Projekt eher unwichtig, schadet aber nicht)
  • create_dir statt create_dir lieber create_dir_all verwenden, das äquivalent zu mkdir -p. Würde ich nicht immer empfehlen, aber für dieses Projekt schon
  • arithmetic_side_effects markiert Stellen mit potentiellen Overflow-Effekten, Divisionen durch 0 bei arithmetischen Operatoren (+, -, *, /). Ich habe es mal aktiviert, obwohl meine Zahlen klein genug sind, dass eigentlich keine Überläufe passieren können. Ich habe stattdessen strict_add und vergleichbare Funktionen verwendet, das panicked bei Overflows. Für anderen Code würde ich vielleicht eher checked_add oder overflowing_add verwenden, je nachdem welches Verhalten gewünscht ist. Ein Nachteil: Arithmetische Ausdrücke sind danach viel schwieriger zu lesen.
  • wildcard_enum_match_arm: Wenn man kein catch-all beim Enum-Matching haben will. Hilft, damit neu hinzugefügte Enum-Varianten nicht übergangen werden. Für die Fälle, wo ich wirklich catch-alls haben wollte, habe ich Ausnahmen gesetzt.
  • as_conversions verhindert Typkonversionen mit as, die potentiell zu Fehlern führen. Auch hier habe ich mich entschieden, dass mein Programm im Fehlerfall lieber panicken soll. Eine Ausnahme habe ich hinzugefügt, weil ich keine Ahnung hatte, die ich sonst verlustbehaftet von u64 zu f64 casten sollte.
  • unwrap_used verhindert, dass man unwrap verwendet. Definitiv eine gute Regel. Wenn überhaupt, sollte man expect verwenden, dann kriegt man noch eine Information dazu, warum man meinte, dass ein Wert Some() oder Ok() sein sollte. Ich habe tatsächlich ein paar expects im Code, weil ich z.B. einen Formatter verwende, der in einen String-Buffer schreibt, was nicht schiefgehen kann.

Ein unwrap bin ich bei der Gelegenheit auch eine schöne Art und Weise losgeworden. Ich hatte zunächst eine match-expression die ungefähr so aussah:

let description = match blogposts.len() {
    0 => "Keine Blogposts".to_string(),
    1 => format!(
        "Blogposts vom {}",
        blogposts[0].0.date.format(DATE_FORMAT_GERMAN)
    ),
    _ => format!(
        blogposts[0].0.date.format(DATE_FORMAT_GERMAN),
        blogposts.last().unwrap().0.date.format(DATE_FORMAT_GERMAN)
    )
};

last() kann unten nie None zurückgeben, weil die Länge mindestens 2 sein muss. Aber unschön ist es trotzdem. Da ist mir eingefallen, dass es ja mittlerweile slice patterns gibt. Dasselbe Problem lässt sich also viel schöner so lösen:

let description = match blogposts {
    [] => "Keine Blogposts".to_owned(),
    [only] => format!("Blogposts vom {}", only.0.date.format(DATE_FORMAT_GERMAN)),
    [first, .., last] => format!(
        "Blogposts vom {} bis zum {}",
        first.0.date.format(DATE_FORMAT_GERMAN),
        last.0.date.format(DATE_FORMAT_GERMAN)
    ),
};

Gegebenenfalls brauchbare Regeln

Das sind Regeln, die ich in manchen Situationen für brauchbar halte, aber nicht in diesem Projekt hier:

  • single_char_lifetime_names verbietet Lifetime-Namen wie 'a. Für ein neues Projekt vielleicht eine gute Idee, hier nicht.
  • unneeded_field_pattern verhindert, dass man beim destructuring eines structs unbenutzte Felder ausschreibt anstatt .. zu verwenden. Ich habe die mal deaktiviert, weil ich im Zweifelsfall sicher sein will, dass ich kein Feld übersehen habe. .. kann ich ja trotzdem nutzen, wenn ich will
  • missing_docs_in_private_items macht Doc-Kommentare für private Elemente verpflichtend. Halte ich in dieser Codebasis nicht für Sinnvoll
  • module_name_repetitions verhindert, dass man Worte in der Module-Hierarchie doppelt verwendet, z.B. file::FileData. Grundsätzlich keine schlechte Idee, aber Data als Datentyp war mir dann doch zu generisch
  • min_ident_chars verhindert Bezeichner, die nur aus einem Zeichen bestehen. Ich verwende solche aber gerne in sehr kurzen Ausdrücken, wo die Bedeutung des Namens nicht verloren gehen kann. Ich muss dann halt manuell aufpassen, dass sich die Variable i nicht doch über die ganze Funktion zieht.
  • if_then_some_else_none verlangt, dass man etwas wie if v.is_empty() { Some(42) } else { None }; stattdessen let a = v.is_empty().then(|| {42}); ausschreibt. Ich finde die erste Variante doch ein bisschen einfacher zu lesen (zumindest, wenn sie umgebrochen ist)
  • absolute_paths: verbietet absolute Pfade für Bezeichner im Code, man solle die lieber in die use-statements packen. Könnte ich drüber nachdenken, aber gerade mit den ganzen verschiedenen Result-Typen gelangt man da schnell in Namenskonflikte.
  • indexing_slicing verbietet, direkte slice-Operationen à la a[i]. Gut, um sich vor panics abzusichern. Mein Code ist aber nicht kritisch, in diesem Fall habe ich die Regel deaktiviert
  • print_stdout verbietet print! und println!. Hilfreich, wenn man keine Ausgabe mit ´println!haben möchte. In diesem Fall ist es aber eine Kommandozeilen-App mit wenig Ausgabe, und daher will ich meinprintln!` schon haben
  • expect_used analog zu unwrap_used: verbietet expect() bei Result und Option. Gut, wenn man keine panics haben möchte. Für dieses Projekt habe ich aber, wie oben beschrieben, eine Handvoll expects, mit denen ich klarkommen muss.

Regeln für Nischenfälle

Und zum Schluss noch ein paar Regeln, die ich überhaupt nicht haben möchte bzw. die für sehr enge Nischenfälle gedacht sind:

  • missing_trait_methods verhindert, dass man ein trait implementiert ohne alle Funktionen des traits zu implementieren. Eine Hölle, wenn man Iteratoren implementiert. Die Doku sagt, man solle es am besten nur für Einzelfälle aktivieren (also für einzelne Trait-Implementierungen)
  • implicit_return verbietet impliztes return am Ende der Funktion. Da implizites return aber in den Standard-Clippy-Regeln erzwungen wird, bleibe ich lieber bei den Standardregeln.
  • mod_module_files verbietet mod.rs-module files. Ich komme mit dieser Struktur aber gut klar
  • std_instead_of_alloc verpflichtet, für bestimmte use statements aus alloc und nicht aus std zu importieren. Gut für std-only-crates
  • std_instead_of_core s.o. nur, für core anstatt alloc
  • unused_trait_names möchte, dass man traits nur nicht-anonym importiert, wenn man sie beim Namen verwendet, nicht, weil man sie nur für die Nutzung des traits importiert werden. Ich sehe den Nutzen hier nicht wirklich.
  • arbitrary_source_item_ordering erzwingt, dass man im Quellcode eine bestimmte Reihenfolge einhält, z.B. alle structs for alle Funktionen. Im Zweifelsfall verschlechtert das aber imho eher die Übersichtlichkeit.
  • shadow_reuse Ich mag das shadowing-Verhalten von rust. Wem das zu gefährlich ist, kann gerne diese Regel aktivieren
  • single_call_fn warnt vor Funktionen, die in der Codebasis nur ein Mal genutzt werden. Aber für mich ist es strukturell schon manchmal sinnvoll, eine solche Funktion zu schreiben.
  • non_ascii_literal erlaubt nur Ascii-Zeichen in Stringliteralen. Falls es Leute gibt, deren Editor kein UTF-8 kann. Mal im Ernst: wir habe 2026. Wenn euer Editor nach über 30 Jahren UTF-8 immer noch kein UTF-8 unterstützt, ist das nicht mein Problem. Wäre aber vielleicht für sicherheitsrelevanten Code interessant, um Homoglraph-Attacken zu verhindern.
  • pattern_type_mismatch irgendwas mit borrows und pattern matching. Hier vertraue ich aber einfach dem Compiler, dass der sich beschwert, wenn es ein Problem gibt
  • integer_division ich weiß, dass bei Integer-Division der Rest verloren geht, danke.
  • integer_division_remainder_used ist als Schutz gegen timing-Seitenkanalangriffe gedacht. Für Kryptographiecode. Das ist eine wichtige Nischenanwendung, aber halt eine Nischenanwendung und in meinem Code hier irrelevant.
  • impl_trait_in_params verhindert die impl Trait-Syntax in Funktionsparametern. Ich finde impl Trait eigentlich ganz praktisch, deswegen: raus mit der Regel
  • unseparated_literal_suffix dazu gibt es eine Gegenregel separated_literal_suffix. Man kann sich maximal eine aussuchen.
  • iter_over_hash_type verbietet iteratoren über ungeordnete Datenstrukturen (z.B. HashSet). Ich weiß aber, wie HashSets funktionieren und will über die Werte iterieren, danke.
  • float_arithmetic verbietet Fließkommazahlrechnungen. Praktisch für Mikroprozessoren, die keine FP-Hardware haben und das alles Simulieren müssen (mit extremen Performanceeinbußen). Also wieder nur eine Nischenanwendung.

Fazit

Ich habe an einigen Stellen meinen Code stabiler und sauberer gemacht. ob ich die restriction-Regeln weiterhin en bloc drin lasse oder nicht, muss ich mir aber noch überlegen. Ein paar von den Regeln sind jedoch ganz in Ordnung.

2026: So schnell vergessen

2026 ist schon wieder zu einem Drittel um. Und was für ein Jahr war es bisher. Weltpolitisch ein Scheißjahr, womit es ich gut in die Reihe der vorherigen Jahre einreiht. Ich möchte hier noch einmal die Aufmerksamkeit auf das enfant terrible, Donald Trump, lenken. Nicht, weil der nicht genug Aufmerksamkeit bekommt (die kriegt er, mehr als genug), sondern weil er es immer wieder schafft, dass wir seinen bisherigen Bullshit aus den Augen verlieren, weil er woanders einen neuen Haufen hingesetzt hat.

Akt 1: Venezuela

Wie fing das Jahr an? Donald Trump ist in ein fremdes Land einmarschiert, hat den Präsidenten / Diktator von Venezuela Nicolás Maduro entführt und sich hingestellt und behauptet, dass das Öl in Venezuelas Boden den USA gehört und er vollen Zugriff darauf haben möchte.

Ich habe jetzt kein besoneres Mitleid mit Maduro, aber dieser Angriff war nach meinem Verständnis definitiv völkerrechtswidrig. Und die Ausbeutung des Venezuelanischen Öls ist auch nicht so einfach, wie sich Trump das vorstellt.

Oh, und eine anonyme Person hat 400000 $ damit gemacht, auf die Entführung Maduros zu wetten. Ein Schelm, wer Böses dabei denkt.

Akt 1 Grönland (zweiter Handlungsstrang)

Parallel dazu hat Donald Trump erneut seinen Bullshit mit Grönland wieder angefacht. Obwohl die USA bereits militärische Basen auf Grönland haben und sogar nocht mehr bauen dürften, behauptet er, die Sicherheit der USA hingen davon ab. Er droht praktisch mit einem Angriff auf Grönland (offiziell Teil von Dänemark), was einen Angriff auf seine eigenen Nato-Verbündeten bedeuten würde. Grönland und Dänemark waren not amused. Wie das der Sicherheit der USA nutzen soll, ist unklar.

-Möglicherweise ging es auch hier wieder um Rohstoffe: Wenn Trump den Klimawandel weiter so aggressiv vorantreibt, sind Grönlands Rohstoffvorkommen bald viel besser zu erschließen.

Am Ende haben mehrere anderen Nato-Staaten (u.a. auch Deutschland) kleinere Gruppen ihrer Armeen nach Grönland gesandt, vermutlich hauptsächlich um den Amis zu demonstrieren: Wenn ihr Grönland angreift, greift ihr uns alle an.

Akt 2: Minnesota

Noch während sich die Sache mit Grönland hinzieht, belagert Trumps SA (auch bekannt als ICE) die Stadt Minneapolis. Friedliche Demonstranten werden getötet, Menschen werden entführt, Kinder werden als Geiseln genommen, um ihre Eltern hervorzulocken. Am Ende lässt Trumps Regime die Leute abziehen, ohne allerdings irgendein Fehlverhalten einzugestehen

Akt 3: Die Epstein-Akten

Nachdem das Justizministerium unter Trump dazu verdonnert wurde, die Epstein-Akten zu veröffentlichen und das mit einiger Verzögerung und einiges an Schwärzungen auch getan hat und dann einige der Schwärzungen zurücknahm, stehen auch Vorwürfe gegen Donald Trump im Raum. Während ein Europa einige ehemalige Politiker und ein ehemaliger Prinz genau untersucht werden, passiert bei Trump jedoch nicht viel. Trotzdem ist es natürlich schlecht für sein Image, also braucht er eine Ablenkung. Und da kommt sie auch schon:

Akt 4: Der Angriffskrieg auf den Iran

Ich habe schon gesagt, dass ich nichts über diesen Krieg schreiben will. Irans herrschendes Regime kann gerne zur Hölle fahren, aber die Auslöschung einer ganzen Zivilisation anzudrohen, wie es Trump tut, ist schon ohne es durchzuführen ein Kriegsverbrechen.

Dass Trump damit die Weltwirtschaft in eine Krise gestoßen hat, ist eigentlich nur eine Nebensache. Für Trump ist es natürlich die Hauptsache, weil es ihn schlecht dastehen lässt. Also gibt er anderen die Schuld, gibt vor, zu verhandeln, stellt Ultimaten, die er immer wieder verlängert (wobei damit mal wieder Insiderhandel betrieben wird) und führt sich auf die ein Kleinkind, dass nicht die Schokolade kriegt, die es will.

Worauf ich hinaus möchte

Das alles ist noch nicht so lange her. Es ist alles in den ersten vier Monaten dieses Jahres passiert. Trotzdem ist in der breiten Berichterstattung außer von dem Irankrieg davon kaum noch zu hören. Weil Trump den stinkenden Haufen, den er hingesetzt hat, immer wieder mit einem anderen Haufen überdeckt. An den ganzen Scheiß, der letztes Jahr passiert ist, garnicht zu denken.

Wir dürfen aber nicht vergessen, was Trump alles gemacht hat. Und das gilt nicht nur für Trump, sondern für alle seiner Art. Und insbesondere müssen wir (als Welt, aber insbesondere als Europa) uns merken, dass man den USA unter Trump nicht vertrauen kann. Und dass die Politik in den USA auch ohne Trump so weit entglitten ist, dass man einer gemäßigten Regierung nur innerhalb ihrer Legislaturperiode trauen kann, weil danach wieder der nächste größenwahnsinnige Rassist an die Macht kommen kann. Wir müssen es so ähnlich halten wie die Ölkonzerne mit Bezug auf Venezuela: Dort zu investieren ist eine schlechte Idee, weil es so instabil ist, dass morgen alles verloren sein kann.

Insbesondere bedeutet das auch, das wir kein Geld in Palantir, eine Spionagesoftware geben dürfen. Nur zwei der Gründe:

Ebensowenig dürfen wir US-Behörden erlauben, politische Ansichten, sexuelle Orientierung oder „Herkunft“ von uns abzufragen. Idealerweise sollten wir diese Daten selber nicht halten. So etwas ist brandgefährlich, solche Listen haben schon die Nazis genutzt. Wenn so eine Liste erst einmal besteht und in die falschen Hände gerät, dann gute Nacht. Dann lieber kein vereinfachtes Visumsverfahren für die USA.

Aber noch einmal das Wichtigste: Wir dürfen nicht vergessen, was Trump alles angestellt hat. Und wir dürfen uns nicht daran gewöhnen, es darf nicht das neue „normal“ werden, was gerade passiert. Weder bei Trump noch bei Russland noch bei der AfD, und nicht einmal bei dem, was die CDU/CSU bei uns gerade treibt.

Rollenspielszenen: Bassbombe

Rollenspielszene. Wir sind die Band „Spice of Life“, die eigentlich eine Gruppe von Spionen der Wasted Lands ist, die jetzt auf Tournee durch verschiedene ikarische Militärlager reist.

Wir wissen seit einiger Zeit, dass das ikarische Militär (oder der Geheimdienst?) einen Anschlag auf uns plant, um publikumswirksam für den Krieg gegen die Wasted Lands zu werben. Wir sind gerade in einem Feldlager nahe der Front angekommen. Wir vermuten, dass der Anschlag hier passieren soll, weil die Gelegenheit am Günstigsten ist. Der Lead Singer ΦΦ (ausgesprochen: „Fiffi“) schafft es, mit seinen dressierten Ratten ein versiegeltes Dokument zu ergattern, in dem der Plan für unseren Tod steht: Während unseres Auftrittes soll eine Bombe auf der Bühne explodieren.

Wir machen einen Gegenplan. Wir suchen die Bombe, bringen sie ins Zelt des Lagerkommandeurs und machen uns mit dem Dokument aus dem Staub. Dazu müssen wir nur erst einmal die Bombe finden. Fiffi will schon wieder seine Ratten losschicken, doch Anis wirft nur einen Blick über die Bühne, geht zum Bassverstärker und identifiziert ihn korrekt als das Bombenversteck.

Der Spielleiter dazu:

Der Gedankengang dahinter war: Musiker überprüfen vor dem Auftritt immer ihr Equipment, aber Bassisten sind halt keine Musiker.

Kontinuierlich Schrittweise

Heute habe ich diese Illusion in den Weiten des Netzes gefunden:

Zwei kurze, waagerechte Streifen (blau und gelb) bewegen sich parallel zueinander über einen Hintergrund mit regelmäßigen senkrechten schwarz-weißen Streifen. Obwohl sie sich gleichmäßig bewegen entsteht die Illusion, dass sie sich abwechselnd schrittweise bewegen. Gegen Ende der Animation wird der Hintergrund einheitlich grau und man kann klar erkennen, dass sich die Streifen gleichmäßig bewegen.

(Die Animation steht unter der CC BY-SA 4.0-Lizenz und wurde von Rinuraeni erstellt).

Ich weiß nicht, ob das für jeden Beobachter gleich aussieht, aber für mich sieht es so aus, als ob sich die farbigen Streifen abwechselnd Schrittweise bewegen. Tatsächlich bewegen sie sich aber kontinuierlich mit gleicher Geschwindigkeit. Wenn ich einen der Streifen abdecke und mich auf den anderen konzentriere, kann ich das auch sehen, bevor es am Ende mit grauem Hintergrund offensichtlich wird.

PS: Die ursprüngliche Datei war eine 296 kiB-große GIF-Datei. Als animiertes WebP ist sie nur noch etwa 42 kiB groß.

Digital Independence Day im Mai 2026

Gestern war wieder Digital Independece Day. Im April hatte ich ja ausgesetzt (und dafür aber den kompletten Tag keinen Computer verwendet). Dieses Mal wollte ich wieder etwas machen, mir ist aber nichts eingefallen. Die Sachen auf der offiziellen DID-Seite? Habe ich entweder schon gemacht (z.B. nutze ich seit 2007 hauptsächlich Linux), betreffen mich nicht (z.B. habe ich Facebook nie benutzt) oder sind für mich ironischerweise ohne Google Playstore nicht möglich (z.B. die Umstellung auf Wero). Anderen Leuten helfen oder sie bekehren? War gestern für mich logistisch nicht möglich.

Immerhin haben sich SPD, Grüne und Linke jetzt endlich von Χitter verabschiedet. War imho lange überfällig, aber besser spät als nie. Jetzt bleibt nur noch zu hoffen, dass auch die Unionsparteien und die FDP nachziehen, dann wären alle größeren demokratischen Parteien nicht mehr dort. Die traditionellen Medien, insbesondere die öffentlich-rechtlichen, sollten sich auch von dort verabschieden, sofern sie es nicht schon getan haben.

Für mich wiederum gibt es vielleicht irgendwann sonst diesen Monat eine Chance, anderen beim Loskommen von großen Tech-Konzernen zu helfen.

Slopc: Humor oder Horror?

Es gibt ja in letzter Zeit immer wieder Diskussionen über Codegenerierung durch LLMs und „Vibe Coding“, also das LLM nicht nur kleine Teile des Codes, sondern gleich mehr oder weniger das ganze Projekt generieren zu lassen (man sollte meinen, dass zumindest letzteres das offensichtlich eine schlechte Idee ist. Ist es auch, nur ist es für manche Menschen anscheinend nicht so offensichtlich).

Um diesen Prozess jetzt zu vereinfachen, hat jemand das slopc-crate für Rust geschrieben. Einfach das #[slop]-Makro vor eine Funktion setzen, und das Makro erzeugt mithilfe eines LLMs alle todo!-Makros durch LLM-generierten code, indem es den Doc-Kommentar der Funktion als Teil eines Prompt nutzt.

Die Inspiration dafür beschreibt der Autor so:

[…] felt like discovering your car has heated seat after 2 years on a lease. But then the intrusive thoughts came in: What if those heated seats were LLM driven, on fire, and the car is now driving itself into oncoming traffic ?

The voices in my head drove me to draft up some cursed proc-macro that would make my coworkers (me and my 2 cats) loose all the respect and faith they have for my technical skills.

HTTP 3

HTTP 3. Eigentlich ist die Schreibweise „HTTP/3“, aber mich irritiert das immer. Wie soll man das denn aussprechen? „HTTP geteilt durch drei“? Oder „HTTP oder drei“? Warum ist da ein Schrägstrich?

Ist ja auch egal. Zu HTTP 3 gibt es unterschiedliche Meinungen. Auf der einen Seite gibt es Leute, die meinen, HTTP 3 sei zu komplex (und damit ein Sicherheitsrisiko), würde nur dann etwas bringen, wenn man ganz bestimmte Anforderungen hat oder sei sowieso doof, weil Google zu sehr seine Finger im Spiel hatte. Auf der anderen Seite gibt es Leute, die sagen, man müsse es unbedingt nutzen, weil es alles so viel schneller macht und ja sowieso das Neueste und Beste ist, und wer sich dagegen ausspricht sei ein zurückgebliebener Maschinenstürmer (wen überhaupt die Möglichkeit in Betracht gezogen wird, dass sich jemand dagegen aussprechen könnte). Dazwischen gibt es eine ganze Menge nuancierter Meinungen.

Ich selbst spiele schon seit einer Weile mit dem Gedanken, es für mein Blog einfach mal auszuprobieren. Es scheiterte immer wieder daran, dass die nginx-Version auf meinem Server das Feature nicht aktiviert hatte und ich definitiv keine nginx-Version installieren wollte, die nicht aus dem Paketmanager stammt (wegen des Wartungsaufwands). So ähnlich ging es mir ja auch mit Brotli, wobei ich Brotli wirklich dringend nutzen wollte, während HTTP 3 nur ein „probieren wir es mal“ ist.

Zwischenspiel: HTTP 2 und nginx

Ich nutze nginx als HTTP-Server. Um sicherzustellen, dass meine nginx-Konfiguration gültig ist, habe ich /usr/sbin/nginx -t ausgeführt. Ergebnis, ohne dass ich irgendwas geändert hätte:

the "listen ... http2" directive is deprecated, use the "http2" directive instead in [REDACTED]

Ok, kurz nachgeschaut und diese Zeilen hier

listen 443 ssl http2;
listen [::]:443 ssl http2;

Durch diese ersetzt:

listen 443 ssl;
listen [::]:443 ssl;
http2 on;

Selbst wenn das mit dem HTTP 3 also nichts werden sollte habe ich immerhin meine nginx-Config auf den aktuellen Stand gebracht.

HTTP 3 mit nginx

Ich habe nach der offiziellen nginx-Dokumentation zu HTTP 3 gearbeitet. Der wichtigste Teil ist, hinter diesen Zeilen, die für HTTP 1 und 2 zuständig sind:

listen 443 ssl;
listen [::]:443 ssl;

Diese beiden Zeilen einzufügen:

listen 443 quic reuseport;
listen [::]:443 quic reuseport;

WICHTIG: Hier habe ich ein bisschen kämpfen müssen. Ohne reuseport funktionierte es bei mir überhaupt nicht. Es wird allerdings überall davor gewarnt, reuseport mehr als ein Mal zu verwenden. Ich habe in meiner nginx-config aber mehrere virtuelle Server. Kann ich dann nicht alle mit HTTP 3 betreiben? Doch, natürlich kann ich das. Aber es muss genau einer davon reuseport definiert haben (pro port + IP-Version).

Dann kann man noch ein paar weitere Optionen setzen, zum Beispiel diese hier:

quic_retry on; 
ssl_early_data on; 
quic_gso on; 
add_header Alt-Svc 'h3=":443"; ma=86400';

Die ersten drei sind optional und sollten die Performance verbessern oder so. Speziell die dritte funktioniert nur unter Linux. Die vierte Option, der Alt-Svc header hingegen ist recht wichtig. Denn ohne den erkennen User-Agents (z.B. Browser) nicht, dass es neben der HTTP 1 bzw. 2-Verbindung auch noch eine HTTP 3-Option gibt. HTTP 3 läuft über ein komplett anderes Transport-Layer-Protokoll (UDP statt TCP). Während bei HTTP 1 also ein Update zu HTTP 2 über das Protokoll selber sehr einfach möglich ist ohne auch nur die TCP-Verbindung neu zu verhandeln, geht das bei HTTP 3 nicht. Ohne diesen Header wird also vermutlich kaum ein Browser probieren, HTTP 3 zu verwenden. ma=86400 bedeutet, dass sich der User-Agent das für 86400 Sekunden (also 24 Stunden) merken soll. Vielleicht drehe ich das später mal höher, wenn HTTP 3 bei mir über längere Zeit stabil läuft.

Testen mit curl

Bevor ich diesen Alt-Svc-Header aber gesetzt habe, wollte ich wissen, ob HTTP 3 auch stabil läuft (was gut war, denn zunächst lief es wegen der reuseport-Sachen nicht stabil). Dazu wollte ich curl verwenden. Bei meinem Ubuntu 25.10 ist curl aber leider noch ohne HTTP 3-Unterstützung gebaut. Das kann man mit curl --version herausfinden. Wenn da unter features nirgendwo HTTP 3 gelistet ist, kann dieses curl kein HTTP 3.

Ironischerweise hat ausgerechnet mein Server selber eine curl-Version mit HTTP 3-Unterstützung. Ironischerweise, weil der Server mit Debian läuft und Debian eigentlich notorisch dafür ist, alte („stable“) Versionen zu benutzen. Die curl-Version ist übrigens die gleiche wie auf Ubuntu, aber halt mit HTTP 3-Support gebaut.

So konnte ich dann mit diesen beiden Befehlen (jeweils für IPv4 bzw. IPv6) testen, ob es geht:

curl -4 --http3-only -v https://blog.strangerthanusual.de/
curl -6 --http3-only -v https://blog.strangerthanusual.de/

Bei Erfolg bekam ich eine ganz normale HTTP-Antwort. Ohne reuseport kam nur

Failed to connect to blog.strangerthanusual.de port 443 after 30055 ms: Could not connect to server

Wie geht es weiter?

Ich lasse das jetzt erst einmal eine Weile so laufen und schaue mir an, wie gut es läuft. Ich werde das allerdings nicht rigoros monitoren oder methodisch korrekt untersuchen. Wenn es gut läuft, werde ich vielleicht noch einen HTTP Resource Record in meine DNS-Config packen, so dass User-Agents schon bei der Adressaufösung mitbekommen, dass hier HTTP 3 angeboten wird.

Nicht verschweigen möchte ich, dass ich bei meinen Recherchen auf Berichte zu einem Bug gestoßen bin, der in Kombination mit reuseport und soft-reload der nginx-Konfiguration Probleme bereitet. Also sollte ich hier ein bisschen vorsichtig sein.

Aber immerhin. Firefox nutzt gerade schon mit Freude die HTTP 3-Verbindung. Wie viel mir das jetzt tatsächlich bringt sei mal dahingestellt.

Käsekuchenkarte

Ich hatte gestern Abend eine Rollenspielrunde. Perfekte Gelegenheit um etwas zu backen. Da einer der Spieler Veganer ist und ich einige Wochen vorher auf Mastodon auf ein veganes Käsekuchenrezept gestoßen bin, habe ich einen Käsekuchen gebacken. Um die Vorfreude anzufeuern habe ich ein Foto gemacht und in die Signal-Gruppe geposted.

Ein Käsekuchen in einer Springform, von oben fotografiert.

Dieses war der erste Streich, doch der zweite folgt sogleich!

Schritt 2: Ein Bildausschnitt

Dann bin ich auf eine Idee gekommen. Inspiriert dazu hat mich „Alternative Moons“. Um die Idee umzusetzen brauchte ich aber erst einmal einen Bildausschnitt mit nur dem Käsekuchen:

Ein ellipsenförmiger Ausschnitt des Käsekuchens von oben. Der Rahmen ist nicht mehr zu sehen, nur noch der Käsekuchen

Schritt 3: Vollmond

Im nächsten Schritt habe ich das Kuchenbild in Graustufen umgewandelt. Es sieht ein bisschen wie der Mond aus, oder?

Derselbe Bildausschnitt wie vorhin, aber in Graustufen

Schritt 4: Farbe

Ein Mond ist schön und gut, aber mein Ziel ist eine Weltkarte. Vielleicht für eine Fantasywelt oder so. Ich habe ein bisschen mit der „select by color“-Funktion von Gimp herumgespielt und das Wasser vom Land getrennt. Beim Land habe ich dann die Grauwerte invertiert, damit es auf beiden Seiten dunkle Flächen gibt. Den Ozean habe ich dann blau, die Landfläche bräunlich eingefärbt.

Derselbe Bildausschnitt wie vorhin, aber ein großer Teil der Fläche ist jetzt in Blautönen, der Rest in Brauntönen. Es sieht aus wie eine Weltkarte mit Ozean, einem Kontinent und ein paar Inseln.

Schritt 5: Mehr Farbe

So ganz einfarbig ist der Kontinent langweilig. Ich habe noch ein bisschen mit der Auswahlfunktion von Gimp herumgespielt und eine Teilauswahl des Landes genommen, die ich stattdessen grün eingefärbt habe. Die braunen Flächen halten sich jetzt in Grenzen, setzen aber Akzente.

Dieselbe „Weltkarte“ wie vorhin, aber die Landflächen sind jetzt größtenteils in Grüntönen. Hier und da gibt es aber auch bräunliche Flächen.

Ich habe die Bilder in voller Größe auf Pixelfed.de geposted.

Rollenspielszenen: Summen

Rollenspielszene: Nini das Ziri und Sprinkle der Orren sind angeheuert worden, um bei der Suche nach einem versteckten Schatz zu suchen. Dummerweise haben ein Haufen Scawn ebenfalls Wind von dem Schatz bekommen und den Rassimelarchäologen Cory entführt, der ihnen bei der Schatzsuche helfen soll.

Mitten in der Nacht schleicht sich Nini an das Lager der Scawn an, um Cory zu befreien. Nini kann dank eines Zaubers im Dunkeln sehen, die Scawn nicht. Nini ist noch kleiner als die Scawn. Nini hat sich mit Dreck eingerieben, um schlechter sichtbar zu sein. Nini ist nicht mehr als ein Schatten in der Dunkelheit. Nini ist cool! Nini beginnt, eine Actionmusik zu summen, um ziener Coolheit Ausdruck zu verleihen.

Die Scawn, die Cory bewachen, wundern sich über die Actionmusik und entdecken Nini. Innerhalb kürzester Zeit ist das halbe Lager auf den Beinen.