Sunday, June 21, 2020

Rust, Minecraft, and the Fragility of Software

I've been using Rust for the last year or so as the main language I've been writing in.  Recently I went back to C++ for something else, and was struck at the difference.  I had been gradually liking Rust more and more as I used it, but switching back/forth between Rust and C++ really opens your eyes to the advantages that Rust has.

Rust's type system, and the way that it's woven references, heap-allocation, stack-allocation, and such all into the type system, is really powerful, and once you've gotten used to it, makes a great deal of sense, vs. the often opaque nature of C's pointers.

Yes, C++ has std::unique_ptr<> and, and has been trying to incorporate these concepts in the standard library, but it's not nearly as simple to use as Rust's default mode of moves and borrows.

In particular, the Rust compiler is a great ally.  And it's continually getting better.  The ability to catch "use after moved", and reference lifetime issues (e.g. use after free issues) is amazing.

But that, to me, is not the best part.

The best part is a standard library that has the notion of Option<T>, and Result<T, Error> deeply embedded into it.  Option<T> is an enum type, generic on type T.  It has two variants:  None, and Some(T).  None is like null, except that it's effectively a sub-type to the compiler.  You can't quite treat an Option<T> variable like it's a T.  Because it might be None.  But it's easy to use, especially with the match operator, map(), and the like.

Checked math exists, is A Thing, especially when dealing with time.  And that's subtle, but it's what got me thinking about this (that and explaining to my 9yo why computers have limits to the sizes of numbers that they can use).

Mathematical overflow is one of those things that we tend to not think about, except in the brief moments of choosing the type for a variable/member and then much later with the realization that something has gone terribly wrong when you're suddenly seeing nonsense values when something has overflowed.

Rust has a bunch of operations that are checked, and return an Option<T>, allowing it to return None instead of nonsense.  And since that None isn't a null, but an enum variant that you're forced to contend with, the compiler won't let you pretend it's Some<T>.

Unfortunately, that can lead to some clunky code when trying to do math (say converting Fahrenheit to Celsius), if each step is being checked for overflow.

But that clunkiness lays bare the fragility that underlies a lot of software.

We assume so much in the software that we right is safe, and for the most part it is.  Until it isn't.

Another example, and what got started me on this line of thought, was my 9yo asking about the Far Lands in Minecraft, a world generation bug that occurred at high values along X and Z coordinates (the ground plane).  And it occurred to me that this was likely due to overflows, or the imprecision of floating point at large values (which also shows up in Minecraft).

I've long been aware of these issues, but also as special cases.  By making some choices early on, one can mostly ignore them.  I mean, 640KB should be enough for anyone, right?

But these, and using Rust, has really been making me re-evaluate just how often we make these assumptions, and how fragile most software is, especially if it ever faces unexpected (by the developer) inputs.  And not just user inputs.  But corrupt I/O readings, packet errors, etc. can be nefarious in embedded work.

Rust certainly isn't perfect.  As I mentioned earlier, the checked math routines are clunky to use, and for the most part, aren't the default.  Tools like proptest exist, which can help setup the bounds for limiting bad inputs to your functions, but it's still a bunch of work to always be thinking about what these limits and error potentials mean.

But as compilers get better, especially with expressive type systems like Rust has, I'm hoping that we'll get to a point where we can catch these sorts of errors at compile-time, and as a result, get closer to a place where we can categorically remove classes of errors from programs.

Monday, June 1, 2020

Keyboard Modding: Spectrograms of Pinging Springs

After the previous round of mods, and sitting here in my (sometimes) quiet home office, I realized that I could hear a long fading-out "ping", or at least an "iiiiinnnng", that was clear after I finished typing.  This is often completely drowned out by music when I'm working though, so it wasn't that loud.

But once you start paying attention, and start making things quieter, it's a slippery slope.

This evening I dug out the spare switches (Kaihua Speed Bronze) for my keyboard, held one up my ear, and tapped it.  And heard the same thing ("tap-iiiiiing").  But not quite so loud as what I hear from the keyboard itself.

So I recorded it, and poked at some other switches that I'd lubricated last week, to see if they did the same.  And they did not...

But first, here's the spectrogram (from Audacity) of the stock switch :

The span on this is, for reference, is about 320ms.  So that long smear at 4800Hz (and the bands ~1400Hz above it are really interesting).  It looks like the fundamental might be around 1200-1400Hz (and there's a few spots of energy there during the impulse), and these are harmonics that are ringing.

Also visible is a similar pattern for 2, 4, 6, and 8kHz.  Probably lots of things going on.

Now, here's the spectrogram (with the same timespan) from similarly tapping the side of a switch where I've coated the ends of the main spring, the slider's sides, and the leaf-switch contacts, in Krytox 250g0 grease (but not the click bar):

The difference is pretty stark.  Both in sound to the ear ("tap", not "tap-iiiiiiiinng"), and it's visibly different in the spectrogram.

But then I looked back at the frequency plots from the up and downstrokes that I made:

Notice the sudden jump in level of the upstroke at 4800Hz, which comes back down at 6500kHz or so, and the similar bumps at the 2nd and 3rd harmonics of those?.

I think one of the reasons it's so audible when typing is that there are a lot of keyswitches which all experienced an impulse when any of the keys was pressed (especially since I tend to bottom out the keys).

That lack of ping is also apparent when you press and release the switch (held up to my ear).

Lubing the switches is on my mod-list.  But this might move it up in overall importance.  This is really quite the difference.  But as it's hard to reverse, I'm not doing it yet (I have a few others changes I want to test first).

A note:  These are Kaihua (aka Kaihl) "Speed Bronze", a switch with a very low actuation point, and is a clicky-type switch.  But the click mechanism is very different from that of a Cherry MX Blue.  Instead of an internal piece that moves/up down against the contacts, this has a "click-bar", which is separate from the tabs on the slider that closes the contacts.

So, 3 springs total.  But in this case, the grease is only on the main spring and the contacts, not the click-bar.  Greasing the click-bar makes the click much, much quieter, and slightly mushy feeling.